在系统调用过程中,如果请求不能被立即满足,请求进程会进入TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE状态。如果进程在TASK_INTERRUPTIBLE状态,那么如果进程收到一个信号,该系统调用会被终止,并返回相应的错误码(可能是EINTR, ERESTARTNOHAND,ERESTART_RESTARTBLOCK,ERESTARTSYS或者ERESTARTNOINTR中的一个)。用户态程序只能收到EINTR,其他的返回码都是被内核使用(具体使用情况请参考《深入理解Linux内核》中的“信号处理”章节)。
本文描述整个过程中信号和阻塞的进程究竟是如何交互已完成上面的功能。
当一个系统调用发生时,以socket接受tcp消息为例,
1. 进程调用sys_recv()服务例程,
2. sys_recv()调用socket层的sys_recvfrom()例程,
3. sys_recvfrom()调用sock_recvmsg(), 后者再调__sock_recvmsg()
4. 针对tcp,其socket结构会用想用的回调函数初始化,其中处理recv的回调函数是recv_stream()
5. recv_stream()创建从sk_receive_queue中查看是否有skb,如果有责返回给用户,否则调用wait_event_interruptible()等待驱动程序通知新包的到来,
6. 当网卡驱动收到新包,会一次调用IP层,TCP层函数,把受到的数据拷贝到用户指定的缓存区,最后解除调用进程的阻塞(具体过程可以参考《深入理解Linux网络》),
7. 调用进程把受到的数据返回给用户并清理环境。
过程中如果在第5步等待的过程中调用进程受到信号,系统会把进程编程TASK_RUNNING状态,当进程随后被调度的时候会返回EINTR,告诉用户调用被信号中断,用户决定是否再次发起系统调用。
当系统调用返回后,中断仍可以继续接收包并放到接受queue中,如果用户没有及时把数据取走,则数据新来的包会被丢失。
上面的过程使用与大部分系统调用,因为驱动大部分都是用wait_event_interruptible()来阻塞进程。