- Today
- Total
Byeo
accept system call 1 (__sys_accept4) 본문
accept system call의 개요와 관련된 내용은 이전 포스트에 정리되어 있습니다.: https://byeo.tistory.com/entry/accept-system-call-Intro
언제나 그렇듯, __sys_accept에서 시작합니다. 단, 함수 명의 끝에 4가 붙어있습니다.
0. Parameter
accpet의 parameter는 조금 특이합니다.
1. fd: 늘 그렇듯 socket fd를 첫 번째 인자로 받습니다.
2. *addrlen: 연결 수립 시, 상대방의 주소를 userspace에게 전달하기 위하여 struct sockaddr* 구조체를 하나 받습니다. 이 공간은 userspace에서 할당받은 공간이어야 하며 (구조체를 선언하여 &를 전달하는 방식 or malloc), 구조체는 kernel에 의해 값이 기록됩니다.
3. *addrlen: addrlen도 마찬가지로 kernel이 기록해줍니다.
4. flags: flag를 사용하지 않으려면 accept을 사용하면 됩니다. flag를 주고싶다면 accept4를 사용하면 됩니다.
1. sys_accept
// net/socket.c/SYSCALL_DEFINE(accept) :1855
SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,
int __user *, upeer_addrlen, int, flags)
{
return __sys_accept4(fd, upeer_sockaddr, upeer_addrlen, flags);
}
SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,
int __user *, upeer_addrlen)
{
return __sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0);
}
accept system call을 정의합니다. userspace에서는 accept, accept4를 모두 사용할 수 있습니다. 차이는 accept4는 standard가 아니며, flags라는 4번째 인자를 추가로 받습니다.
두 함수는 동일하게 __sys_accept4를 호출합니다. 단, 4번째 인자를 flags로 주냐, 0으로 주냐의 차이입니다.
2. __sys_accept4
// net/socket.c/__sys_accept4() :1826
/*
* For accept, we attempt to create a new socket, set up the link
* with the client, wake up the client, then return the new
* connected fd. We collect the address of the connector in kernel
* space and move it to user at the very end. This is unclean because
* we open the socket then return an error.
*
* 1003.1g adds the ability to recvmsg() to query connection pending
* status to recvmsg. We need to add that support in a way thats
* clean when we restructure accept also.
*/
int __sys_accept4(int fd, struct sockaddr __user *upeer_sockaddr,
int __user *upeer_addrlen, int flags)
{
int ret = -EBADF;
struct fd f;
f = fdget(fd);
if (f.file) {
ret = __sys_accept4_file(f.file, 0, upeer_sockaddr,
upeer_addrlen, flags,
rlimit(RLIMIT_NOFILE));
fdput(f);
}
return ret;
}
struct socket* sock을 얻어오기 위해서 sockfd_lookup_light를 호출했던 bind, listen때와는 조금 다르게, __to_fd까지 처리하는 fdget을 얻어와 struct fd에 fd 관련 정보를 저장합니다.
// include/linux/file.h :36
struct fd {
struct file *file;
unsigned int flags;
};
__to_fd 함수는 다음과 같이 생겼습니다.
// include/linux/file.h/__to_fd() :58
static inline struct fd __to_fd(unsigned long v)
{
return (struct fd){(struct file *)(v & ~3),v & 3};
}
그리고 f.file이 존재한다면 __sys_accept4_file을 실행하기 시작합니다.
3. __sys_accept4_file
// net/socket.c/__sys_accept4_file() :1798
int __sys_accept4_file(struct file *file, unsigned file_flags,
struct sockaddr __user *upeer_sockaddr,
int __user *upeer_addrlen, int flags,
unsigned long nofile)
{
struct file *newfile;
int newfd;
if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
return -EINVAL;
if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
newfd = __get_unused_fd_flags(flags, nofile);
if (unlikely(newfd < 0))
return newfd;
newfile = do_accept(file, file_flags, upeer_sockaddr, upeer_addrlen,
flags);
if (IS_ERR(newfile)) {
put_unused_fd(newfd);
return PTR_ERR(newfile);
}
fd_install(newfd, newfile);
return newfd;
}
__sys_accept4_file은 parameter를 여러 개 받습니다.
struct file *file: f.file
unsigned file_flags: 0
struct sockaddr __user *upeer_sockaddr: userspace의 sockaddr 구조체 포인터
int __user *upeer_addrlen: userspace의 addrlen integer 포인터
flags: accept system call을 호출 시 넘긴 flags
nofile: resource limit에 지정된 file 생성 limit 값
if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
return -EINVAL;
첫 if문은 flags가 SOCK_CLOEXEC 와 SOCK_NONBLOCK 외에 다른 bit가 있는지를 체크합니다. 즉, 이 두 flags 외에는 받지 않습니다.
if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
두 번째 if문은 SOCK_NONBLOCK과 O_NONBLOCK의 value가 다른 경우에 O_NONBLOCK 값으로 바꿔줍니다. linux kernel 5.18기준으로는 두 값은 같으므로 해당되지 않습니다.
// include/linux/net.h :79
#define SOCK_NONBLOCK O_NONBLOCK
그 다음으로는 새로운 fd를 할당합니다. 이 함수는 socket system call에서 봤었죠. (https://byeo.tistory.com/entry/socket-system-call-4)
newfd = __get_unused_fd_flags(flags, nofile);
if (unlikely(newfd < 0))
return newfd;
일단 blocking을 하기 전에 fd부터 먼저 할당을 해놓는 모습입니다. (kernel로부터 자원을 얻을 때에는 get, 반환할 때는 put)
그 다음 함수는 do_accept입니다.
4. do_accept
do_accept의 인자는 RLIMIT으로 걸려있던 nofile을 제외하고 전부 넘어갑니다. 참고로 새로 할당받았던 newfd는 넘어가지 않음에 유의하세요!
// net/socket.c/do_accept() :1739
struct file *do_accept(struct file *file, unsigned file_flags,
struct sockaddr __user *upeer_sockaddr,
int __user *upeer_addrlen, int flags)
{
struct socket *sock, *newsock;
struct file *newfile;
int err, len;
struct sockaddr_storage address;
sock = sock_from_file(file);
if (!sock)
return ERR_PTR(-ENOTSOCK);
newsock = sock_alloc();
if (!newsock)
return ERR_PTR(-ENFILE);
newsock->type = sock->type;
newsock->ops = sock->ops;
/*
* We don't need try_module_get here, as the listening socket (sock)
* has the protocol module (sock->ops->owner) held.
*/
__module_get(newsock->ops->owner);
newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);
if (IS_ERR(newfile))
return newfile;
err = security_socket_accept(sock, newsock);
if (err)
goto out_fd;
err = sock->ops->accept(sock, newsock, sock->file->f_flags | file_flags,
false);
if (err < 0)
goto out_fd;
if (upeer_sockaddr) {
len = newsock->ops->getname(newsock,
(struct sockaddr *)&address, 2);
if (len < 0) {
err = -ECONNABORTED;
goto out_fd;
}
err = move_addr_to_user(&address,
len, upeer_sockaddr, upeer_addrlen);
if (err < 0)
goto out_fd;
}
/* File flags are not inherited via accept() unlike another OSes. */
return newfile;
out_fd:
fput(newfile);
return ERR_PTR(err);
}
1. 'sock_from_file'은 struct file에서 struct sock* sock을 가져오는 함수입니다. 우리는 socket system call을 호출할 때, file->private_data에 struct socket pointer를 보관한다는 것을 확인했었습니다.
2. 다음으로, 'sock_alloc()'을 호출합니다. 이는 새로운 socket fd를 제공하기 위하여 sock을 할당받습니다. alloc_inode를 통해서 inode 공간을 받고, inode에 관련 값들을 초기화 합니다.
3. 할당받은 struct socket에 type과 ops를 동일하게 대입합니다. type은 SOCK_STREAM, ops는 inet_stream_ops가 입력됩니다.
4. sock_alloc_file: socket 할당 때와 유사하게, 새로운 file을 할당합니다. sock->file과 file->private_data에 서로가 기록됩니다. 또한, struct file의 내용물이 sock_alloc_file 함수 내에서 초기화됩니다.
// net/socket.c/do_accept() :1773
err = sock->ops->accept(sock, newsock, sock->file->f_flags | file_flags,
false);
이제 sock에 연결된 accept함수를 호출합니다. ops는 inet_stream_ops고, 해당 구조체에서 accept은 inet_accept인 것을 확인할 수 있습니다.
현재까지 흐름도
'프로그래밍 (Programming) > 네트워크 스택' 카테고리의 다른 글
[Linux Kernel] Blocking I/O (0) | 2024.05.17 |
---|---|
accept system call 2 (inet_csk_accept) (0) | 2024.05.15 |
accept system call 0 - Intro (0) | 2024.05.14 |
listen system call 3 (inet_hash) (0) | 2024.05.12 |
listen system call 2 (inet_listen) (0) | 2024.05.11 |