多进程侦听同一端口 多进程竞争连接请求
内核实现部分其实并不重要,也没什么好说的,只是比较好奇,就大致看一下相关实现。
1、等待队列头创建
最原始的等待队列在sock_alloc--->>>
static struct inode *sock_alloc_inode(struct super_block *sb)
{
init_waitqueue_head(&ei->socket.wait);
}
中实现,这里其实没有什么初始化,就是初始化了一个自旋锁,并且初始化为可获取状态,它并没有初始化方法成员。
然后在__sock_create--->>inet_create--->>>sock_init_data
sk->sk_sleep = &sock->wait;
这里将sk结构中的等待队列头指向socket中的wait成员,而这个sk_sleep将会是accept的等待队列头地址。
2、accept阻塞
sys_accept---->>>inet_accept--->>inet_csk_accept---->>>inet_csk_wait_for_connect--->>prepare_to_wait_exclusive(sk->sk_sleep, &wait,TASK_INTERRUPTIBLE)
wait->flags |= WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
if (list_empty(&wait->task_list))
__add_wait_queue_tail(q, wait);
/*
在加入等待队列之后,通过timeo = schedule_timeout(timeo);让出调度权。
这 里比较特殊的是这里的唤醒是互斥的,也就是那个 WQ_FLAG_EXCLUSIVE标志,这个标志会在唤醒函数中使用,当遇到这个标志并且唤醒互斥进程个数为1(默认情况)时只唤醒一个进程,其中的 prepare_to_wait_exclusiv的wait是通过下面宏创建的
DEFINE_WAIT(wait);
3、连接到来时唤醒
tcp_v4_do_rcv--->>>tcp_child_process
/* Wakeup parent, send SIGIO */
if (state == TCP_SYN_RECV && child->sk_state != state)
parent->sk_data_ready(parent, 0);
inet_create--->>>sock_init_data
sk->sk_state_change = sock_def_wakeup;
sk->sk_data_ready = sock_def_readable;
sk->sk_write_space = sock_def_write_space;
sk->sk_error_report = sock_def_error_report;
sk->sk_destruct = sock_def_destruct;
也就是执行的sk_data_ready即为sock_def_readable函数,在该函数中,其执行操作为
static void sock_def_readable(struct sock *sk, int len)
{
read_lock(&sk->sk_callback_lock);
if (sk->sk_sleep && waitqueue_active(sk->sk_sleep))
wake_up_interruptible(sk->sk_sleep);
sk_wake_async(sk,1,POLL_IN);
read_unlock(&sk->sk_callback_lock);
}
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
可以看到,通过sk->sk_sleep唤醒了正在accept的接收套接口,并且其中__wake_up的唤醒互斥任务个数为1,所以只会唤醒一个进程,这次连接的到来对其它任务透明。