除了做视频网站还能做什么网站,求网站都懂得,网址导航网站制作工具,漂亮的网站建设中静态页TCP socket 接收函数 recv 发出 recvfrom 系统调用。 进⼊系统调⽤后#xff0c;⽤户进程就进⼊到了内核态#xff0c;通过执⾏⼀系列的内核协议层函数#xff0c;然后到 socket 对象的接收队列中查看是否有数据#xff0c;没有的话就把⾃⼰添加到 socket 对应的等待队列⾥…TCP socket 接收函数 recv 发出 recvfrom 系统调用。 进⼊系统调⽤后⽤户进程就进⼊到了内核态通过执⾏⼀系列的内核协议层函数然后到 socket 对象的接收队列中查看是否有数据没有的话就把⾃⼰添加到 socket 对应的等待队列⾥。最后让出CPU操作系统会选择下⼀个就绪状态的进程来执⾏。 假如我们没有使⽤ O_NONBLOCK 标记等待接收的过程会阻塞进程但是我们先探究阻塞的过程。
//file: net/socket.c
SYSCALL_DEFINE6(recvfrom, int, fd, void __user *, ubuf, size_t,size, unsigned int, flags, struct sockaddr __user *, addr,int __user *, addr_len)
{struct socket *sock;//根据⽤户传⼊的 fd 找到 socket 对象sock sockfd_lookup_light(fd, err, fput_needed);......err sock_recvmsg(sock, msg, size, flags);......
}sock_recvmsg - __sock_recvmsg - __sock_recvmsg_nosec
static inline int __sock_recvmsg_nosec(struct kiocb *iocb, struct socket *sock,struct msghdr *msg, size_t size, int flags)
{......return sock-ops-recvmsg(iocb, sock, msg, size, flags);
}在之前的 socket 对象图中从图中看到 recvmsg 指向的是 inet_recvmsg 方法。
//file: net/ipv4/af_inet.c
int inet_recvmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size, int flags)
{...err sk-sk_prot-recvmsg(iocb, sk, msg, size, flags MSG_DONTWAIT,flags ~MSG_DONTWAIT, addr_len);这里又出现了一个 recvmsg 函数指针不过这个是socket 对象中的 recvmsg 方法对应 TCP 协议的 tcp_recvmsg 方法。
//file: net/ipv4/tcp.c
int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,size_t len, int nonblock, int flags, int *addr_len)
{int copied 0;...do {//遍历接收队列接收数据skb_queue_walk(sk-sk_receive_queue, skb) {...}...}if (copied target) {release_sock(sk);lock_sock(sk);} else //没有收到⾜够数据启⽤ sk_wait_data 阻塞当前进程sk_wait_data(sk, timeo);
}可以看到消息量不够一样也会阻塞。 如果没有收到数据或者收到不⾜够多则调⽤ sk_wait_data 把当前进程阻塞掉。
//file: net/core/sock.c
int sk_wait_data(struct sock *sk, long *timeo)
{//当前进程(current)关联到所定义的等待队列项上DEFINE_WAIT(wait);// 调⽤ sk_sleep 获取 sock 对象下的 wait// 并准备挂起将进程状态设置为可打断 INTERRUPTIBLEprepare_to_wait(sk_sleep(sk), wait, TASK_INTERRUPTIBLE);set_bit(SOCK_ASYNC_WAITDATA, sk-sk_socket-flags);// 通过调⽤schedule_timeout让出CPU然后进⾏睡眠rc sk_wait_event(sk, timeo, !skb_queue_empty(sk-sk_receive_queue));...sk_wait_data 阻塞进程的实现 做完排队工作后给所在进程改个状态位即可。 ⾸先在 DEFINE_WAIT 宏下定义了⼀个等待队列项 wait。 在这个新的等待队列项上注册了回调函数 autoremove_wake_function并把当前进程描述符 current 关联到其 .private 成员上。
//file: include/linux/wait.h
#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)
#define DEFINE_WAIT_FUNC(name, function) \wait_queue_t name { \.private current, \.func function, \.task_list LIST_HEAD_INIT((name).task_list), \}紧接着在 sk_wait_data 中 调⽤ sk_sleep 获取 sock 对象下的等待队列列表头 wait_queue_head_t。 sk_sleep 源代码如下
//file: include/net/sock.h
static inline wait_queue_head_t *sk_sleep(struct sock *sk)
{BUILD_BUG_ON(offsetof(struct socket_wq, wait) ! 0);return rcu_dereference_raw(sk-sk_wq)-wait;
}接着调⽤ prepare_to_wait 来把新定义的等待队列项 wait 插⼊到 sock 对象的等待队下。
//file: kernel/wait.c
void prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
{unsigned long flags;wait-flags ~WQ_FLAG_EXCLUSIVE;spin_lock_irqsave(q-lock, flags);if (list_empty(wait-task_list))__add_wait_queue(q, wait);set_current_state(state);spin_unlock_irqrestore(q-lock, flags);
}这样后⾯当内核收完数据产⽣就绪时间的时候就可以查找 socket 等待队列上的等待项进⽽就可以找到回调函数和在等待该 socket 就绪事件的进程了。 最后再调⽤ sk_wait_event 让出 CPU进程将进⼊睡眠状态这会导致⼀次进程上下⽂的开销。