sigsuspend()阻塞:异步信号SIGIO为什么会被截胡?
关键词:fcntl、fasync、signal、sigsuspend、pthread_sigmask、trace events。
此文主要是解决问题过程中的记录,内容有较多冗余。但也反映解决问题中用到的方法和思路。
简单的描述问题就是:snap线程在pthread_sigmask()和sigsuspend()之间调度出去,然后此时中断发送SIGIO信号。
但此时snap线程是阻塞SIGIO信号的,所以内核选择唤醒其他进程来处理信号。
在内核返回用户空间的时候,AiApp处理了SIGIO信号。而snap并没有得到唤醒,一直处于sigsuspend()中。
解决的方法就是讲SIGIO信号发送和snap线程绑定,而不是和snap线程所在的进程组绑定。保证SIGIO只发送到snap。
/* asynchronus notification enable */ owner_ex.pid = syscall(SYS_gettid); owner_ex.type = F_OWNER_TID; fcntl(enc->fd_enc, F_SETOWN_EX, &owner_ex); //fcntl(enc->fd_enc, F_SETOWN, syscall(SYS_gettid)); /* this thread will receive SIGIO */ oflags = fcntl(enc->fd_enc, F_GETFL); fcntl(enc->fd_enc, F_SETFL, oflags | FASYNC); /* set ASYNC notification flag */
下面首先描述一下问题,然后记录问题排查过程,以及原因分析,最后给出解决方法。
1. 问题描述
创建一个线程snap,snap在sigsuspend()处等待SIGIO信号。这个信号由中断4从内核发送,指定发送给snap线程。
下面首先梳理关键API,然后简要介绍一下发现的问题。
1.1 关键API解释
1.1.1 fasync和kill_fasync()
void kill_fasync(struct fasync_struct **fp, int sig, int band)
fasync是为了使驱动通过kill_fasync()异步发送SIGIO信号给应用,应用通过fcntl将自身和SIGIO信号绑定。
当中断或者数据到达时,调用kill_fasync()发送SIGIO,应用接收到信号后,进行SIGIO的handler处理。
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
fasync_helper()是内核驱动中初始化fasync队列的函数,包括分配内存和设置属性。
在实际使用中,内核需要做的有:
1. 在设备抽象数据结构中增加一个struct fasync_struct指针;
2. 实现struct file_operations中的fasync成员,通常就是调用内核的fasync_helper()函数
3. 在需要向应用发送统治的地方(比如中断中)调用内核的kill_fasync()函数,发送SIGIO信号。
4. 在struct file_operations的release成员中,调用fasync(-1, filp, 0);
在应用中,需要做的有:
1. fcntl(fd, F_SETOWN, getpid())来指定一个进程作为文件的属主,这样内核就知道SIGIO信号发送给哪个进程。如果指定线程,避免进程中其他线程处理则需要使用fcntl(fd, F_SETOWN_EX, &f_owner_ex)。
2. 设置文件标志,添加FASYNC标志:fcntl(fd, F_SETFL, f_flags | FASYNC)。驱动中就会调用struct file_operations的fasync成员。
3. 调用signal()或者sigaction()设置SIGIO信号的处理函数。
1.1.2 sigaction()
#include <signal.h> int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);
sigaction()系统调用是设置信号处置的另一选择。
sig参数标识想要获取或改变的信号编号,该参数可以是除去SIGKILL和SIGSTOP之外的任何信号。
acti指向描述信号新处置的数据结构。oldact参数是指向同一结构类型的指针,用来返回之前信号处置的相关信息。
struct sigaction的sa_handler是指定信号的处理函数。
详细请参考:《Linux/UNIX系统编程手册》 第20.13章。
1.1.3 sigemptyset()
int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set);
sigemptyset()函数初始化一个未包含任何成员的信号集。
sigfillset()则初始化一个信号集,使其包含所有信号。
1.1.4 sigaddset()
int sigaddset(sigset_t *set, int sig); int sigdelset(sigset_t *set, int sig);
sigaddset()和sigdelset()函数想一个集合中添加或者移除单个信号。
1.1.5 pthread_sigmask()
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
刚刚创建的新线程会从其创建者处继承信号掩码的一份拷贝。
线程可以使用pthread_sigmask()来改变或/并获取当前的信号掩码。
除了所操作的是线程信号掩码之外,pthread_sigmask()和司股票荣成mask() 用法完全相同。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
内核为每个进程维护一个信号掩码,并将阻塞其针对该进城的传递。
如果将遭阻塞的信号发送给某进程,那么对该信号的传递将延后,直至从进程信号掩码中移除该信号,从而解除阻塞位置。
1.1.6 sigsuspend()
int sigsuspend(const sigset_t *mask);
sigsuspend()系统调用将以mask所指向的信号集来替换进程的信号掩码,然后挂起进程的执行,直到其捕获到信号,并从信号handler中返回。
一旦handler返回, sigsuspend()会将进程信号掩码恢复为调用前的值。
调用sigsuspend(),相当于亦不可中断方式执行下列操作:
sigprocmask(SIG_SETMASK, &maks, &prevMask);
pause();
sigprocmask(SIG_SETMASK, &prevMask, NULL);
sigsuspend()对第一个sigprocmask()和pause()之间的竟态保护。
详细解释见《Linux/UNIX系统编程手册》第22.9章。
1.2 发现问题
如下HandleSIGIO()注册SIGIO信号的行为,sig_handler()是SIGIO的信号处理函数。
EWLWaitHwRdy()是snap进程用于和中断进行同步,表现为等待SIGIO信号。
正常的流程log为B->C->A->D,出现问题的时候B->C->A即停止,没有出现D。说明中断kill_fasync()发送的信号并没有丢失。
说明snap进程卡在sigsuspend()处,但是log A表明SIGIO信号收到并进行了处理。这是疑点。
static volatile sig_atomic_t sig_delivered = 0; /* SIGIO handler */ static void sig_handler(int signal_number) { sig_delivered++; // printf("sig_handler func sig_delivered is %d\n",sig_delivered);--------------------------------------------------------------log A // fflush(stdout); } void HandleSIGIO(hx280ewl_t * enc) { struct sigaction sa; /* asynchronus notification handler */ memset(&sa, 0, sizeof(sa)); sa.sa_handler = sig_handler; sa.sa_flags |= SA_RESTART; /* restart of system calls */ sigaction(SIGIO, &sa, NULL); /* EWLInit might be called in a separate thread */ /* we want to register the encoding thread for SIGIO */ enc->sigio_needed = 1; /* register for SIGIO in EWLEnableHW */ } i32 EWLWaitHwRdy(const void *inst, u32 *slicesReady) { hx280ewl_t *enc = (hx280ewl_t *) inst; u32 prevSlicesReady = 0; // PTRACE("EWLWaitHw: Start\n"); printf("EWLWaitHw: Start\n"); fflush(stdout); /* Check invalid parameters */ if(enc == NULL) { assert(0); return EWL_HW_WAIT_ERROR; } sigset_t set, oldset; sigemptyset(&set); sigaddset(&set, SIGIO); if (slicesReady) { ... } else { /* Wait for frame ready signal (SIGIO) */ // printf("######## sigsuspend() %d, oldset=%08x-%08x, set=%08x-%08x\n",sig_delivered, oldset.__val[1], oldset.__val[0], set.__val[1], set.__val[0]);----log B // fflush(stdout); pthread_sigmask(SIG_BLOCK, &set, &oldset); while(!sig_delivered) { // printf("Before sigsuspend() %d, oldset=%08x-%08x, set=%08x-%08x\n",sig_delivered, oldset.__val[1], oldset.__val[0], set.__val[1], set.__val[0]);---log C // fflush(stdout); sigsuspend(&oldset);---------------------------------------------------------------------------------------------------------------------------------在此处睡眠,异常的时候没有正确唤醒。 // printf("After sigsuspend() %d, oldset=%08x-%08x, set=%08x-%08x\n",sig_delivered, oldset.__val[1], oldset.__val[0], set.__val[1], set.__val[0]);----log D // fflush(stdout); } sig_delivered = 0; pthread_sigmask(SIG_UNBLOCK, &set, NULL); } asic_status = enc->pRegBase[1]; /* update the buffered asic status */ ... return EWL_OK; }
2. 问题排查过程
经过上面可以大致知道问题点,在于sigsuspend()没有正确的退出,进而snap进程阻塞,流程停止。
所以首先从信号和进程的关系着手。
2.1 SIGIO信号和snap进程关系
由于SIGIO已经发出,并且其handler已经被执行。
对SIGIO信号的发送和执行,通过signal_generate()和signal_deliver()跟踪,对这两个events加filter "sig==29"进行过滤。
对snap进程,通过sched_wakeup()和sched_switch()进行跟踪,同时只跟踪唤醒snap、切换到snap、从snap切换出的动作。
echo > /sys/kernel/debug/tracing/trace echo 0 > /sys/kernel/debug/tracing/events/enable
echo "sig==29" > /sys/kernel/debug/tracing/events/signal/signal_deliver/filter echo "sig==29" > /sys/kernel/debug/tracing/events/signal/signal_generate/filter echo 1 > /sys/kernel/debug/tracing/events/signal/enable echo "prev_comm==snap || next_comm==snap" > /sys/kernel/debug/tracing/events/sched/sched_switch/filter echo "comm==snap" > /sys/kernel/debug/tracing/events/sched/sched_wakeup/filter echo 1 > /sys/kernel/debug/tracing/events/sched/sched_switch/enable echo 1 > /sys/kernel/debug/tracing/events/sched/sched_wakeup/enable cat /sys/kernel/debug/tracing/trace_pipe > temp.txt
下面第一部分是正常流程,sched_wakeup(唤醒snap)->signal_generate(发送SIGIO到snap)->sched_switch(切换到snap)->signal_deliver()。
第二部分异常在于没有sched_wakeup()唤醒snap和sched_switch()唤醒snap,而且最重要的一点是为什么AiApp进程处理了SIGIO!
由于只显示snap进程,这里可能有sched_wakeup()其他进程。
...
coreComm-223 [000] d... 72.754955: sched_switch: prev_comm=coreComm prev_pid=223 prev_prio=120 prev_state=S ==> next_comm=snap next_pid=203 next_prio=120
snap-203 [000] d... 72.755360: sched_switch: prev_comm=snap prev_pid=203 prev_prio=120 prev_state=S ==> next_comm=rtpjitterbuffer next_pid=250 next_prio=120
udpsrc1:src-247 [000] dnh. 72.874125: sched_wakeup: comm=snap pid=203 prio=120 target_cpu=000------------------------------------------------------------------------------------wakeup snap thread
udpsrc1:src-247 [000] dnh. 72.874141: signal_generate: sig=29 errno=0 code=128 comm=snap pid=203 grp=1 res=0---------------------------------------------------------------------generate SIGIO signal
udpsrc1:src-247 [000] d... 72.874161: sched_switch: prev_comm=udpsrc1:src prev_pid=247 prev_prio=120 prev_state=R ==> next_comm=snap next_pid=203 next_prio=120------------------switch to snap thread
snap-203 [000] d... 72.875114: signal_deliver: sig=29 errno=0 code=128 sa_handler=2b9ff450 sa_flags=10000000--------------------------------------------------------------snap thread waked by SIGIO
snap-203 [000] d... 72.875195: sched_switch: prev_comm=snap prev_pid=203 prev_prio=120 prev_state=S ==> next_comm=cat next_pid=219 next_prio=120
IFMS_Open-231 [000] d... 73.065332: sched_wakeup: comm=snap pid=203 prio=120 target_cpu=000
...
AiApp-228 [000] d... 73.221721: sched_switch: prev_comm=AiApp prev_pid=228 prev_prio=120 prev_state=S ==> next_comm=snap next_pid=203 next_prio=120
snap-203 [000] d... 73.222124: sched_switch: prev_comm=snap prev_pid=203 prev_prio=120 prev_state=S ==> next_comm=coreComm next_pid=223 next_prio=120
<idle>-0 [000] dnh. 73.380965: signal_generate: sig=29 errno=0 code=128 comm=snap pid=203 grp=1 res=0-----------------------------------------------------------------------Need to open all sched_wakeup and sched_switch.
AiApp-198 [000] d... 73.381628: signal_deliver: sig=29 errno=0 code=128 sa_handler=2b9ff450 sa_flags=10000000----------------------------------------------------------------Why AiApp got SIGIO
<idle>-0 [000] dns. 73.559623: sched_wakeup: comm=snap pid=203 prio=120 target_cpu=000
<idle>-0 [000] d... 73.559671: sched_switch: prev_comm=swapper prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=snap next_pid=203 next_prio=120
snap-203 [000] d... 73.559837: sched_switch: prev_comm=snap prev_pid=203 prev_prio=120 prev_state=S ==> next_comm=adapter next_pid=201 next_prio=120
copy-202 [000] d... 93.421913: sched_wakeup: comm=snap pid=203 prio=120 target_cpu=000
adapter-201 [000] d... 93.422067: sched_switch: prev_comm=adapter prev_pid=201 prev_prio=120 prev_state=D ==> next_comm=snap next_pid=203 next_prio=120
小结:这里说明了为什么最后SIGIO信号handler会被处理,但是没有换新snap进程。因为异常情况下,SIGIO被AiApp进程处理了。
2.2 SIGIO、snap线程、中断关系
光有SIGIO和snap线程的关系还不够,还需要查看一下中断。
增加irq_handler_entry和irq_handler_exit的跟踪,其中sched_wakeup()和signal_generate()是在中断处理handler中进行的。
现象和上面的一致,同时也理顺了从中断出发,发送信号,进程处理三者之间的关系。
echo > /sys/kernel/debug/tracing/trace echo 0 > /sys/kernel/debug/tracing/events/enable echo "sig==29" > /sys/kernel/debug/tracing/events/signal/signal_deliver/filter echo "sig==29" > /sys/kernel/debug/tracing/events/signal/signal_generate/filter echo "comm==snap" > /sys/kernel/debug/tracing/events/signal/signal_blocked/filter echo 1 > /sys/kernel/debug/tracing/events/signal/enable echo "prev_comm==snap || next_comm==snap" > /sys/kernel/debug/tracing/events/sched/sched_switch/filter echo "comm==snap" > /sys/kernel/debug/tracing/events/sched/sched_wakeup/filter echo 1 > /sys/kernel/debug/tracing/events/sched/sched_switch/enable echo 1 > /sys/kernel/debug/tracing/events/sched/sched_wakeup/enable echo "irq==4" > /sys/kernel/debug/tracing/events/irq/irq_handler_entry/filter echo "irq==4" > /sys/kernel/debug/tracing/events/irq/irq_handler_exit/filter echo 1 > /sys/kernel/debug/tracing/events/irq/irq_handler_entry/enable echo 1 > /sys/kernel/debug/tracing/events/irq/irq_handler_exit/enable cat /sys/kernel/debug/tracing/trace_pipe
中断产生后,在中断处理函数中先进行snap线程的sched_wakeuo(),然后发送SIGIO信号给snap线程;中断处理结束后唤醒snap线程,处理SIGIO信号的handler。
但是异常情况中断处理函数中并没有给snap线程放入RunQ中,难道sched_wakeup()其他线程了?中断处理结束之后,signal_deliver()表明SIGIO信号handler被AiApp处理了。
AiApp-228 [000] d... 102.576745: sched_switch: prev_comm=AiApp prev_pid=228 prev_prio=120 prev_state=S ==> next_comm=snap next_pid=209 next_prio=120 snap-209 [000] d... 102.577042: sched_switch: prev_comm=snap prev_pid=209 prev_prio=120 prev_state=R ==> next_comm=coreComm next_pid=223 next_prio=120 coreComm-223 [000] d... 102.577104: sched_switch: prev_comm=coreComm prev_pid=223 prev_prio=120 prev_state=S ==> next_comm=snap next_pid=209 next_prio=120 snap-209 [000] d... 102.577286: sched_switch: prev_comm=snap prev_pid=209 prev_prio=120 prev_state=S ==> next_comm=rtpjitterbuffer next_pid=250 next_prio=120 rtpjitterbuffer-250 [000] d.h. 102.588617: irq_handler_entry: irq=4 name=hx280enc rtpjitterbuffer-250 [000] dnh. 102.773291: sched_wakeup: comm=snap pid=209 prio=120 target_cpu=000------------------------中断处理中sched_wakeup()snap进程,表明snap进程被选中。 rtpjitterbuffer-250 [000] dnh. 102.773299: signal_generate: sig=29 errno=0 code=128 comm=snap pid=209 grp=1 res=0 rtpjitterbuffer-250 [000] dnh. 102.773303: irq_handler_exit: irq=4 ret=handled rtpjitterbuffer-250 [000] d... 102.773860: sched_switch: prev_comm=rtpjitterbuffer prev_pid=250 prev_prio=120 prev_state=R ==> next_comm=snap next_pid=209 next_prio=120 snap-209 [000] d... 102.773898: signal_deliver: sig=29 errno=0 code=128 sa_handler=2b9ff450 sa_flags=10000000 snap-209 [000] d... 102.773948: sched_switch: prev_comm=snap prev_pid=209 prev_prio=120 prev_state=S ==> next_comm=coreComm next_pid=223 next_prio=120... coreComm-223 [000] d... 102.838285: sched_switch: prev_comm=coreComm prev_pid=223 prev_prio=120 prev_state=S ==> next_comm=snap next_pid=209 next_prio=120 snap-209 [000] d... 102.839026: sched_switch: prev_comm=snap prev_pid=209 prev_prio=120 prev_state=S ==> next_comm=rtpjitterbuffer next_pid=250 next_prio=120 AiApp-228 [000] d.h. 102.850469: irq_handler_entry: irq=4 name=hx280enc AiApp-228 [000] dnh. 102.856927: signal_generate: sig=29 errno=0 code=128 comm=snap pid=209 grp=1 res=0 AiApp-228 [000] dnh. 102.856931: irq_handler_exit: irq=4 ret=handled AiApp-198 [000] d... 102.857479: signal_deliver: sig=29 errno=0 code=128 sa_handler=2b9ff450 sa_flags=10000000 <idle>-0 [000] dns. 103.132781: sched_wakeup: comm=snap pid=209 prio=120 target_cpu=000 <idle>-0 [000] d... 103.132833: sched_switch: prev_comm=swapper prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=snap next_pid=209 next_prio=120 snap-209 [000] d... 103.132995: sched_switch: prev_comm=snap prev_pid=209 prev_prio=120 prev_state=S ==> next_comm=rtpjitterbuffer next_pid=250 next_prio=120
所以有必要全部打开sched_wakeup()查看在中断处理函数中是否sched_wakeup()了AiApp?
因为sched_switch()太多,所以没有打开。关键点在于中断处理函数中sched_wakeup()了哪个线程。
echo > /sys/kernel/debug/tracing/trace echo 0 > /sys/kernel/debug/tracing/events/enable echo "sig==29" > /sys/kernel/debug/tracing/events/signal/signal_deliver/filter echo "sig==29" > /sys/kernel/debug/tracing/events/signal/signal_generate/filter echo 1 > /sys/kernel/debug/tracing/evnnnnn/ents/signal/enable echo > /sys/kernel/debug/tracing/events/sched/sched_wakeup/filter echo 1 > /sys/kernel/debug/tracing/events/sched/sched_wakeup/enable echo "irq==4" > /sys/kernel/debug/tracing/events/irq/irq_handler_entry/filter echo "irq==4" > /sys/kernel/debug/tracing/events/irq/irq_handler_exit/filter echo 1 > /sys/kernel/debug/tracing/events/irq/irq_handler_entry/enable echo 1 > /sys/kernel/debug/tracing/events/irq/irq_handler_exit/enable cat /sys/kernel/debug/tracing/trace_pipe > /tmp/temp.txt
可以看出在正常情况下,sched_wakeup()了snap线程;异常情况sched_wakeup()了AiApp进程。这也导致了后面AiApp响应了SIGIO的handler。
... cat-218 [000] d.h. 59.216451: sched_wakeup: comm=AiApp pid=236 prio=120 target_cpu=000 kworker/u2:1-76 [000] d... 59.216815: sched_wakeup: comm=sshd pid=211 prio=120 target_cpu=000 sshd-211 [000] d.h. 59.217162: irq_handler_entry: irq=4 name=hx280enc sshd-211 [000] dnh. 59.217210: sched_wakeup: comm=snap pid=203 prio=120 target_cpu=000 sshd-211 [000] dnh. 59.217220: signal_generate: sig=29 errno=0 code=128 comm=snap pid=203 grp=1 res=0 sshd-211 [000] dnh. 59.217224: irq_handler_exit: irq=4 ret=handled snap-203 [000] d... 59.217281: signal_deliver: sig=29 errno=0 code=128 sa_handler=2b9ff450 sa_flags=10000000 snap-203 [000] dnh. 59.217380: sched_wakeup: comm=omx_main pid=248 prio=120 target_cpu=000 omx_main-248 [000] d... 59.217580: sched_wakeup: comm=omx_g1_output pid=258 prio=120 target_cpu=000 omx_main-248 [000] d... 59.217637: sched_wakeup: comm=omxdec:src pid=251 prio=120 target_cpu=000... cat-218 [000] d... 59.413571: sched_wakeup: comm=kworker/u2:1 pid=76 prio=120 target_cpu=000 udpsrc0:src-254 [000] dnh. 59.414333: sched_wakeup: comm=AiApp pid=236 prio=120 target_cpu=000 sshd-211 [000] dnh. 59.414615: sched_wakeup: comm=coreComm pid=223 prio=120 target_cpu=000 sshd-211 [000] d.h. 59.414790: irq_handler_entry: irq=4 name=hx280enc sshd-211 [000] dnh. 59.533611: sched_wakeup: comm=AiApp pid=198 prio=120 target_cpu=000----------------------------表明AiApp进程被选中用于处理SIGIO信号。 sshd-211 [000] dnh. 59.533620: signal_generate: sig=29 errno=0 code=128 comm=snap pid=203 grp=1 res=0 sshd-211 [000] dnh. 59.533623: irq_handler_exit: irq=4 ret=handled AiApp-198 [000] d.h. 59.533696: sched_wakeup: comm=cat pid=218 prio=120 target_cpu=000 AiApp-198 [000] d.h. 59.533719: sched_wakeup: comm=coreComm pid=223 prio=120 target_cpu=000 AiApp-198 [000] d.h. 59.533726: sched_wakeup: comm=AiApp pid=236 prio=120 target_cpu=000 AiApp-198 [000] d... 59.534558: signal_deliver: sig=29 errno=0 code=128 sa_handler=2b9ff450 sa_flags=10000000 cat-218 [000] dn.. 59.535430: sched_wakeup: comm=kworker/u2:1 pid=76 prio=120 target_cpu=000 cat-218 [000] dnh. 59.535769: sched_wakeup: comm=coreComm pid=223 prio=120 target_cpu=000 cat-218 [000] dn.. 59.535786: sched_wakeup: comm=kworker/u2:1 pid=76 prio=120 target_cpu=000 cat-218 [000] dn.. 59.535930: sched_wakeup: comm=kworker/u2:1 pid=76 prio=120 target_cpu=000 cat-218 [000] dn.. 59.536006: sched_wakeup: comm=kworker/u2:1 pid=76 prio=120 target_cpu=000 cat-218 [000] dn.. 59.536059: sched_wakeup: comm=kworker/u2:1 pid=76 prio=120 target_cpu=000 cat-218 [000] dn.. 59.536112: sched_wakeup: comm=kworker/u2:1 pid=76 prio=120 target_cpu=000
小结:这里面清晰的看到中断处理中,异常情况下sched_wakeup()选择的是AiApp进程。这就是问题所在,为什么会选择AiApp,而不是期望的snap线程。这也能解释为什么在单项测试的时候不出现异常,因为单项测试只有一个进程,没有其他线程。
2.3 新增signal_blocked()跟踪handle_signal()、sigprocmask()、sigsuspend()中blocked.sig[0]状态
在内核硬件中断处理函数中调用kill_fasync()来给snap进程发送SIGIO信号,kill_fasync()->kill_fasync_rcu()->send_sigio()->send_sigio_to_task()->do_send_sig_info->send_signal()->__send_signal()->complete_signal()->wants_signal()。
增加signal_blocked()跟踪snap进程的task_struct->blocked.sig[0],在中断handler入口和出口打印其值。
增加signal_blocked()的目的是为了和其它trace events协同显示,在同一时间轴显示流程。
使用的地方只需要trace_signal_blocked(snap_task, __func__, __LINE__);
TRACE_EVENT(signal_blocked, TP_PROTO(struct task_struct *tsk, const char *func, unsigned int line), TP_ARGS(tsk, func, line), TP_STRUCT__entry( __array( char, comm, TASK_COMM_LEN ) __field( pid_t, pid ) __field( int, blocked ) __field( const char *, func ) __field( int, line ) ), TP_fast_assign( memcpy(__entry->comm, tsk->comm, TASK_COMM_LEN); __entry->pid = tsk->pid; __entry->blocked = tsk->blocked.sig[0]; __entry->func = func; __entry->line = line; ), TP_printk("%s %d comm=%s pid=%d blocked.sig[0]=0x%08X", __entry->func, __entry->line, __entry->comm, __entry->pid, __entry->blocked) );
可以看出正常情况下,irq_handler_entry()的blocked.sig[0]为0x00000000,异常情况irq_handler_entry()的blocked.sig[0]为0x1000000。
0x1000000表示block SIGIO这个信号。
snap-180 [000] .... 2950.211703: signal_blocked: handle_signal 217 comm=snap pid=180 blocked.sig[0]=0x10000000 snap-180 [000] .... 2951.882924: signal_blocked: sigsuspend 3535 comm=snap pid=180 blocked.sig[0]=0x10000000 snap-180 [000] .... 2951.882940: signal_blocked: sigsuspend 3537 comm=snap pid=180 blocked.sig[0]=0x00000000 rtpjitterbuffer-225 [000] d.h. 2951.894413: signal_blocked: __handle_irq_event_percpu 145 comm=snap pid=180 blocked.sig[0]=0x00000000---------------可以看出此次中断handler中,给snap进程不会被block。 rtpjitterbuffer-225 [000] d.h. 2951.894426: irq_handler_entry: irq=4 name=hx280enc rtpjitterbuffer-225 [000] d.h. 2951.894464: signal_generate: sig=29 errno=0 code=128 comm=snap pid=180 grp=1 res=0 rtpjitterbuffer-225 [000] d.h. 2951.894468: irq_handler_exit: irq=4 ret=handled rtpjitterbuffer-225 [000] d.h. 2951.894471: signal_blocked: __handle_irq_event_percpu 149 comm=snap pid=180 blocked.sig[0]=0x00000000 snap-180 [000] .... 2951.894519: signal_blocked: sigsuspend 3543 comm=snap pid=180 blocked.sig[0]=0x00000000 snap-180 [000] .... 2951.894526: signal_blocked: sigsuspend 3545 comm=snap pid=180 blocked.sig[0]=0x00000000 snap-180 [000] d... 2951.894546: signal_deliver: sig=29 errno=0 code=128 sa_handler=2b983410 sa_flags=10000000---------------------------snap进程在返回用户空间的时候,得到执行SIGIO handler的机会。 snap-180 [000] .... 2951.894551: signal_blocked: handle_signal 199 comm=snap pid=180 blocked.sig[0]=0x00000000 snap-180 [000] .... 2951.894563: signal_blocked: handle_signal 209 comm=snap pid=180 blocked.sig[0]=0x00000000 snap-180 [000] d... 2951.894566: signal_blocked: handle_signal 214 comm=snap pid=180 blocked.sig[0]=0x10000000 snap-180 [000] .... 2951.894569: signal_blocked: handle_signal 217 comm=snap pid=180 blocked.sig[0]=0x10000000 udpsrc0:src-222 [000] d.h. 2952.194958: signal_blocked: __handle_irq_event_percpu 145 comm=snap pid=180 blocked.sig[0]=0x10000000---------------此次中断handler中snap的SIGIO被block。 udpsrc0:src-222 [000] d.h. 2952.194973: irq_handler_entry: irq=4 name=hx280enc udpsrc0:src-222 [000] dnh. 2952.202000: signal_generate: sig=29 errno=0 code=128 comm=snap pid=180 grp=1 res=0 udpsrc0:src-222 [000] dnh. 2952.202004: irq_handler_exit: irq=4 ret=handled udpsrc0:src-222 [000] dnh. 2952.202008: signal_blocked: __handle_irq_event_percpu 149 comm=snap pid=180 blocked.sig[0]=0x10000000 AiApp-176 [000] d... 2952.202264: signal_deliver: sig=29 errno=0 code=128 sa_handler=2b983410 sa_flags=10000000---------------------------SIGIO信号handler被AiApp进程处理。 AiApp-176 [000] .... 2952.202280: signal_blocked: handle_signal 199 comm=snap pid=180 blocked.sig[0]=0x10000000 AiApp-176 [000] .... 2952.202294: signal_blocked: handle_signal 209 comm=snap pid=180 blocked.sig[0]=0x10000000 AiApp-176 [000] d... 2952.202297: signal_blocked: handle_signal 214 comm=snap pid=180 blocked.sig[0]=0x10000000 AiApp-176 [000] .... 2952.202301: signal_blocked: handle_signal 217 comm=snap pid=180 blocked.sig[0]=0x10000000 snap-180 [000] .... 2952.203748: signal_blocked: sigsuspend 3535 comm=snap pid=180 blocked.sig[0]=0x10000000 snap-180 [000] .... 2952.203759: signal_blocked: sigsuspend 3537 comm=snap pid=180 blocked.sig[0]=0x00000000-----------------------------开始执行sigsuspend()。 snap-180 [000] .... 3243.710836: signal_blocked: sigsuspend 3543 comm=snap pid=180 blocked.sig[0]=0x00000000-----------------------------一段时间过后整个进程出错,关闭退出。 snap-180 [000] .... 3243.710850: signal_blocked: sigsuspend 3545 comm=snap pid=180 blocked.sig[0]=0x00000000 sh-150 [000] .... 3244.812561: signal_blocked: handle_signal 199 comm=snap pid=180 blocked.sig[0]=0x00000000 sh-150 [000] .... 3244.812584: signal_blocked: handle_signal 209 comm=snap pid=180 blocked.sig[0]=0x00000000 sh-150 [000] d... 3244.812588: signal_blocked: handle_signal 214 comm=snap pid=180 blocked.sig[0]=0x00000000 sh-150 [000] .... 3244.812591: signal_blocked: handle_signal 217 comm=snap pid=180 blocked.sig[0]=0x00000000
那么究竟是哪些地方改变blocked.sig[0]呢?上面对于pthread_sigmask()的执行没有反应,所以这里需要使用signal_blocked()在sigsuspend()、sigprocmask()、handle_signal()中打印blocked.sig[0]值得改变。
snap-174 [000] d... 687.919424: sched_switch: prev_comm=snap prev_pid=174 prev_prio=120 prev_state=S ==> next_comm=udpsrc1:src next_pid=209 next_prio=120 filter-194 [000] d... 688.494547: sched_wakeup: comm=snap pid=174 prio=120 target_cpu=000 filter-194 [000] d... 688.494657: sched_switch: prev_comm=filter prev_pid=194 prev_prio=120 prev_state=S ==> next_comm=snap next_pid=174 next_prio=120 snap-174 [000] .... 688.498629: signal_blocked: sigprocmask 2518 comm=snap pid=174 blocked.sig[0]=0x00000000 snap-174 [000] .... 688.498639: signal_blocked: sigprocmask 2533 comm=snap pid=174 blocked.sig[0]=0x00000000 snap-174 [000] .... 688.498645: signal_blocked: sigprocmask 2536 comm=snap pid=174 blocked.sig[0]=0x10000000 snap-174 [000] .... 688.498650: signal_blocked: sigprocmask 2518 comm=snap pid=174 blocked.sig[0]=0x10000000 snap-174 [000] .... 688.498652: signal_blocked: sigprocmask 2533 comm=snap pid=174 blocked.sig[0]=0x10000000 snap-174 [000] .... 688.498655: signal_blocked: sigprocmask 2536 comm=snap pid=174 blocked.sig[0]=0x00000000 snap-174 [000] d... 688.499279: sched_switch: prev_comm=snap prev_pid=174 prev_prio=120 prev_state=R ==> next_comm=cat next_pid=245 next_prio=120 rtpjitterbuffer-212 [000] d... 688.503295: sched_switch: prev_comm=rtpjitterbuffer prev_pid=212 prev_prio=120 prev_state=S ==> next_comm=snap next_pid=174 next_prio=120 snap-174 [000] .... 688.503493: signal_blocked: sigprocmask 2518 comm=snap pid=174 blocked.sig[0]=0x00000000---------------------------------------------------------第一个pthread_sigmask() snap-174 [000] .... 688.503497: signal_blocked: sigprocmask 2533 comm=snap pid=174 blocked.sig[0]=0x00000000 snap-174 [000] .... 688.503503: signal_blocked: sigprocmask 2536 comm=snap pid=174 blocked.sig[0]=0x10000000 snap-174 [000] .... 688.503548: signal_blocked: sigsuspend 3539 comm=snap pid=174 blocked.sig[0]=0x10000000----------------------------------------------------------sigsuspend(),以阻塞SIGIO信号状态进入。然后解除阻塞进入进程可唤醒模式。 snap-174 [000] .... 688.503551: signal_blocked: sigsuspend 3541 comm=snap pid=174 blocked.sig[0]=0x00000000 snap-174 [000] d... 688.503562: sched_switch: prev_comm=snap prev_pid=174 prev_prio=120 prev_state=S ==> next_comm=udpsrc1:src next_pid=209 next_prio=120------------snap进入休眠 udpsrc1:src-209 [000] d.h. 688.503888: signal_blocked: __handle_irq_event_percpu 145 comm=snap pid=174 blocked.sig[0]=0x00000000--------------------------------------------产生中断 udpsrc1:src-209 [000] d.h. 688.503898: irq_handler_entry: irq=4 name=hx280enc udpsrc1:src-209 [000] d.h. 688.503930: sched_wakeup: comm=snap pid=174 prio=120 target_cpu=000------------------------------------------------------------------------------snap进程放入RunQ udpsrc1:src-209 [000] d.h. 688.503935: signal_generate: sig=29 errno=0 code=128 comm=snap pid=174 grp=1 res=0 udpsrc1:src-209 [000] d.h. 688.503939: irq_handler_exit: irq=4 ret=handled udpsrc1:src-209 [000] d.h. 688.503942: signal_blocked: __handle_irq_event_percpu 149 comm=snap pid=174 blocked.sig[0]=0x00000000 rtpjitterbuffer-212 [000] d... 688.504528: sched_switch: prev_comm=rtpjitterbuffer prev_pid=212 prev_prio=120 prev_state=S ==> next_comm=snap next_pid=174 next_prio=120--------进程被唤醒。 snap-174 [000] .... 688.504542: signal_blocked: sigsuspend 3547 comm=snap pid=174 blocked.sig[0]=0x00000000 snap-174 [000] .... 688.504546: signal_blocked: sigsuspend 3549 comm=snap pid=174 blocked.sig[0]=0x00000000 snap-174 [000] d... 688.504564: signal_deliver: sig=29 errno=0 code=128 sa_handler=2b983410 sa_flags=10000000--------------------------------------------------------信号发送到snap进程 snap-174 [000] .... 688.504571: signal_blocked: handle_signal 199 comm=snap pid=174 blocked.sig[0]=0x00000000--------------------------------------------------------进行SIGIO sig_handler()处理,在handle_signal()结尾恢复对SIGIO的阻塞。 snap-174 [000] d... 688.504583: signal_blocked: handle_signal 211 comm=snap pid=174 blocked.sig[0]=0x00000000 snap-174 [000] d... 688.504585: signal_blocked: handle_signal 214 comm=snap pid=174 blocked.sig[0]=0x10000000 snap-174 [000] .... 688.504588: signal_blocked: handle_signal 217 comm=snap pid=174 blocked.sig[0]=0x10000000 snap-174 [000] .... 688.504713: signal_blocked: sigprocmask 2518 comm=snap pid=174 blocked.sig[0]=0x10000000---------------------------------------------------------第二个pthread_sigmask(),解除对SIGIO的阻塞。 snap-174 [000] .... 688.504716: signal_blocked: sigprocmask 2533 comm=snap pid=174 blocked.sig[0]=0x10000000 snap-174 [000] .... 688.504719: signal_blocked: sigprocmask 2536 comm=snap pid=174 blocked.sig[0]=0x00000000 snap-174 [000] d... 688.504988: sched_switch: prev_comm=snap prev_pid=174 prev_prio=120 prev_state=R ==> next_comm=NotifySrvlResul next_pid=190 next_prio=120 rtpjitterbuffer-212 [000] d... 688.506152: sched_switch: prev_comm=rtpjitterbuffer prev_pid=212 prev_prio=120 prev_state=S ==> next_comm=snap next_pid=174 next_prio=120... filter-194 [000] d... 691.058210: sched_switch: prev_comm=filter prev_pid=194 prev_prio=120 prev_state=S ==> next_comm=snap next_pid=174 next_prio=120 snap-174 [000] d... 691.059290: sched_switch: prev_comm=snap prev_pid=174 prev_prio=120 prev_state=R ==> next_comm=cat next_pid=245 next_prio=120 filter-194 [000] d... 691.060795: sched_switch: prev_comm=filter prev_pid=194 prev_prio=120 prev_state=S ==> next_comm=snap next_pid=174 next_prio=120 snap-174 [000] d... 691.061464: sched_switch: prev_comm=snap prev_pid=174 prev_prio=120 prev_state=R ==> next_comm=omx_main next_pid=203 next_prio=120 sshd-216 [000] d... 691.064415: sched_switch: prev_comm=sshd prev_pid=216 prev_prio=120 prev_state=S ==> next_comm=snap next_pid=174 next_prio=120 snap-174 [000] d... 691.065170: sched_switch: prev_comm=snap prev_pid=174 prev_prio=120 prev_state=R ==> next_comm=coreComm next_pid=187 next_prio=120 coreComm-187 [000] d... 691.065249: sched_switch: prev_comm=coreComm prev_pid=187 prev_prio=120 prev_state=S ==> next_comm=snap next_pid=174 next_prio=120 snap-174 [000] d... 691.067633: sched_switch: prev_comm=snap prev_pid=174 prev_prio=120 prev_state=R ==> next_comm=NotifySrvlResul next_pid=190 next_prio=120 rtpjitterbuffer-212 [000] d... 691.071517: sched_switch: prev_comm=rtpjitterbuffer prev_pid=212 prev_prio=120 prev_state=S ==> next_comm=snap next_pid=174 next_prio=120 snap-174 [000] .... 691.071615: signal_blocked: sigprocmask 2518 comm=snap pid=174 blocked.sig[0]=0x00000000--------------------------------------------------------被SIGIO以外信号唤醒,但是sig_delivered还是为0,还在while中继续等待。 snap-174 [000] .... 691.071618: signal_blocked: sigprocmask 2533 comm=snap pid=174 blocked.sig[0]=0x00000000 snap-174 [000] .... 691.071624: signal_blocked: sigprocmask 2536 comm=snap pid=174 blocked.sig[0]=0x10000000 snap-174 [000] .... 691.071627: signal_blocked: sigprocmask 2518 comm=snap pid=174 blocked.sig[0]=0x10000000 snap-174 [000] .... 691.071630: signal_blocked: sigprocmask 2533 comm=snap pid=174 blocked.sig[0]=0x10000000 snap-174 [000] .... 691.071632: signal_blocked: sigprocmask 2536 comm=snap pid=174 blocked.sig[0]=0x00000000 snap-174 [000] d... 691.071709: sched_switch: prev_comm=snap prev_pid=174 prev_prio=120 prev_state=R ==> next_comm=coreComm next_pid=187 next_prio=120 coreComm-187 [000] d... 691.071760: sched_switch: prev_comm=coreComm prev_pid=187 prev_prio=120 prev_state=S ==> next_comm=snap next_pid=174 next_prio=120 snap-174 [000] d... 691.071933: sched_switch: prev_comm=snap prev_pid=174 prev_prio=120 prev_state=R ==> next_comm=NotifySrvlResul next_pid=190 next_prio=120 NotifySrvlResul-190 [000] d... 691.072021: sched_switch: prev_comm=NotifySrvlResul prev_pid=190 prev_prio=120 prev_state=S ==> next_comm=snap next_pid=174 next_prio=120 snap-174 [000] .... 691.072510: signal_blocked: sigprocmask 2518 comm=snap pid=174 blocked.sig[0]=0x00000000--------------------------------第一个pthread_sigmask()。 snap-174 [000] .... 691.072514: signal_blocked: sigprocmask 2533 comm=snap pid=174 blocked.sig[0]=0x00000000 snap-174 [000] .... 691.072518: signal_blocked: sigprocmask 2536 comm=snap pid=174 blocked.sig[0]=0x10000000 snap-174 [000] d... 691.072567: sched_switch: prev_comm=snap prev_pid=174 prev_prio=120 prev_state=S ==> next_comm=udpsrc1:src next_pid=209 next_prio=120-----------snap进程切换出去,这是问题关键点。切换出去snap的SIGIO是处于blocked状态。 udpsrc1:src-209 [000] d.h. 691.084054: signal_blocked: __handle_irq_event_percpu 145 comm=snap pid=174 blocked.sig[0]=0x10000000-------------------中断触发,但是此时blocked.sig[0]为0x1000000,屏蔽SIGIO。 udpsrc1:src-209 [000] d.h. 691.084069: irq_handler_entry: irq=4 name=hx280enc udpsrc1:src-209 [000] dnh. 691.102600: signal_generate: sig=29 errno=0 code=128 comm=snap pid=174 grp=1 res=0 udpsrc1:src-209 [000] dnh. 691.102605: irq_handler_exit: irq=4 ret=handled udpsrc1:src-209 [000] dnh. 691.102608: signal_blocked: __handle_irq_event_percpu 149 comm=snap pid=174 blocked.sig[0]=0x10000000 AiApp-170 [000] d... 691.102845: signal_deliver: sig=29 errno=0 code=128 sa_handler=2b983410 sa_flags=10000000-------------------------------AiApp中进行的SIGIO处理。 AiApp-170 [000] .... 691.102859: signal_blocked: handle_signal 199 comm=snap pid=174 blocked.sig[0]=0x10000000-------------------------------SIGIO sig_handler()处理。 AiApp-170 [000] d... 691.102871: signal_blocked: handle_signal 211 comm=snap pid=174 blocked.sig[0]=0x10000000 AiApp-170 [000] d... 691.102874: signal_blocked: handle_signal 214 comm=snap pid=174 blocked.sig[0]=0x10000000 AiApp-170 [000] .... 691.102877: signal_blocked: handle_signal 217 comm=snap pid=174 blocked.sig[0]=0x10000000 <idle>-0 [000] dns. 691.303189: sched_wakeup: comm=snap pid=174 prio=120 target_cpu=000 <idle>-0 [000] d... 691.303267: sched_switch: prev_comm=swapper prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=snap next_pid=174 next_prio=120 snap-174 [000] .... 691.303552: signal_blocked: sigsuspend 3539 comm=snap pid=174 blocked.sig[0]=0x10000000---------------------------------第二个sigsuspend()处理,等待SIGIO信号handler对sig_delivered才会退出while。而流程卡住,不会发送编码请求,也不会有中断及SIGIO。 snap-174 [000] .... 691.303564: signal_blocked: sigsuspend 3541 comm=snap pid=174 blocked.sig[0]=0x00000000 snap-174 [000] d... 691.303584: sched_switch: prev_comm=snap prev_pid=174 prev_prio=120 prev_state=S ==> next_comm=rtpjitterbuffer next_pid=212 next_prio=120 copy-173 [000] dn.. 988.790892: sched_wakeup: comm=snap pid=174 prio=120 target_cpu=000 adapter-172 [000] d... 988.791081: sched_switch: prev_comm=adapter prev_pid=172 prev_prio=120 prev_state=D ==> next_comm=snap next_pid=174 next_prio=120 snap-174 [000] .... 988.791094: signal_blocked: sigsuspend 3547 comm=snap pid=174 blocked.sig[0]=0x00000000---------------------------------进程异常准备退出。 snap-174 [000] .... 988.791098: signal_blocked: sigsuspend 3549 comm=snap pid=174 blocked.sig[0]=0x00000000 snap-174 [000] d... 988.791111: sched_switch: prev_comm=snap prev_pid=174 prev_prio=120 prev_state=D ==> next_comm=AiApp next_pid=188 next_prio=120 copy-173 [000] dn.. 988.793177: sched_wakeup: comm=snap pid=174 prio=120 target_cpu=000 AiApp-188 [000] d... 988.794392: sched_switch: prev_comm=AiApp prev_pid=188 prev_prio=120 prev_state=x ==> next_comm=snap next_pid=174 next_prio=120 snap-174 [000] d... 988.794428: sched_switch: prev_comm=snap prev_pid=174 prev_prio=120 prev_state=x ==> next_comm=adapter next_pid=172 next_prio=120 sh-144 [000] .... 989.892972: signal_blocked: handle_signal 199 comm=snap pid=174 blocked.sig[0]=0x00000000 sh-144 [000] d... 989.892997: signal_blocked: handle_signal 211 comm=snap pid=174 blocked.sig[0]=0x00000000 sh-144 [000] d... 989.893001: signal_blocked: handle_signal 214 comm=snap pid=174 blocked.sig[0]=0x00000000 sh-144 [000] .... 989.893005: signal_blocked: handle_signal 217 comm=snap pid=174 blocked.sig[0]=0x00000000
小结:这里snap进程切换出去的点在sigprocmask()和sigsuspend()之间。在中断handler处理前,SIGIO信号已经被blocked了。send_sidio_to_task()在group为1的情况下,不得不选择其他合适的进程。
3. 原因分析
3.1 SIGIO被blocked情况下,如何选择进程发送信号
在complete_signal()中选择一个合适的task来发送SIGIO,优先选择snap线程。
但是此时snap进程的SIGIO处于blocked状态,及wants_signal()返回0。
然后complete_signal()转而选择其他合适线程,就选择了AiApp。
static inline int wants_signal(int sig, struct task_struct *p) { if (sigismember(&p->blocked, sig))-----------------------------------------异常情况下此处返回0。 return 0; if (p->flags & PF_EXITING) return 0; if (sig == SIGKILL) return 1; if (task_is_stopped_or_traced(p)) return 0; return task_curr(p) || !signal_pending(p); } static void complete_signal(int sig, struct task_struct *p, int group) { struct signal_struct *signal = p->signal; struct task_struct *t; if (wants_signal(sig, p))--------------------------------------------------由上面的异常log可以看出,此时进程p对应的blocked.sig[0]为0x1000000。所以t不会选择当前进程p。 t = p; else if (!group || thread_group_empty(p))----------------------------------如果group为0,表示只发给p进程;或者如果p进程所在组只有一个,那么就无法选择其他进程。 return; else { t = signal->curr_target; while (!wants_signal(sig, t)) {----------------------------------------判断当前进程t是否被SIGIO blocked。 t = next_thread(t);------------------------------------------------选择t所在thread_group的其他没有被blocked的进程。 if (t == signal->curr_target) return; } signal->curr_target = t; } ... signal_wake_up(t, sig == SIGKILL);-------------------------首先将进程设置为TIF_SIGPENDING标志,说明该进程有延迟的信号要等待处理。然后调用wake_up_state()唤醒目标进程。此后当该进程被调度时,在进程返回用户空间前,会调用do_notify_resume()处理该进程的信号。 return; }
3.2 SIGIO被阻塞的竟态
在正常情况下,sigsuspend()进入睡眠,如果此时产生中断,SIGIO会选择snap进程进行唤醒。然后调度到snap进程返回到用户空间的时候,执行SIGIO handler,sigsuspend()返回。
pthread_sigmask() =========================>A sigsuspend()===========>B =========================>C pthread_sigmask()
在A处,SIGIO是被blocked的;如果B处执行了SIGIO handler,那么C处SIGIO也是被blocked的。
C处出现被调度,进而导致SIGIO被blocked的机会很小。
但是A处在进程调度频繁的情况下,很容易出现上面的情况。
然后kill_fasync()就会选择thread_group中其他合适的线程。
4. 解决方法
出现问题的关键是SIGIO信号发送出现在了第一个pthread_sigmask()之后,sigsuspend()之前。然后SIGIO被AiApp处理。
所以一是从避免出现SIGIO被blocked状态;二是SIGIO不要选择其他进程。
第3中方法是较好的解决方法,将SIGIO和snap线程绑定,而不是阻塞其他进程对SIGIO的响应。
4.1 尽量减少SIGIO被blocked时隙
由于当前系统进程任务重,尤其snap线程占用时间较多,被切换出去后就会很长时间才会被调度。
如果在A处被调度出去,恰好此时产生信号,那么则造成异常。
可以提高snap进程优先级,那也只是降低了出现的概率。此方法不可取。
4.2 阻塞线程组所有其他线程SIGIO信号
可以在主线程中,sigprocmask()阻塞SIGIO信号。那么其他线程则不会替代snap线程对pending的SIGIO信号进行处理。
由于snap进程会继承对SIGIO的阻塞,所以需要关闭对SIGIO的阻塞。
然后即使上述同样的pthread_sigmask()->sigsuspend()->pthread_sigmask(),中间即使出现SIGIO被阻塞的情况,也只是pending,还会等待snap进程处理。
下面是一个临时workaround,如果发送SIGIO到snap线程,并且此时SIGIO被阻塞了。那么group为0表示只发送给snap线程,不选择其他线程。
static void send_sigio_to_task(struct task_struct *p, struct fown_struct *fown, int fd, int reason, int group) { /* * F_SETSIG can change ->signum lockless in parallel, make * sure we read it once and use the same value throughout. */ int signum = ACCESS_ONCE(fown->signum); if (!sigio_perm(p, fown, signum)) return;
if(!strcmp(p->comm, "snap"))
{
if(p->blocked.sig[0] == 0x10000000)
{
group = 0;
}
}
switch (signum) { siginfo_t si; default: si.si_signo = signum; si.si_errno = 0; si.si_code = reason; BUG_ON((reason & __SI_MASK) != __SI_POLL); if (reason - POLL_IN >= NSIGPOLL) si.si_band = ~0L; else si.si_band = band_table[reason - POLL_IN]; si.si_fd = fd; if (!do_send_sig_info(signum, &si, p, group)) break; case 0: do_send_sig_info(SIGIO, SEND_SIG_PRIV, p, group); } }
4.3 fcntl设置F_SETOWN_EX线程属性
有上面的分析可知SIGIO处于blocked的状态无法避免,在出现后group为1,则选择其他的进程接收信号。
如果group为0,则可以避免这个异常。
下面看看group是如何影响SIGIO和线程之间的关系的,以及如何限定SIGIO只发送给相关的线程。
void kill_fasync(struct fasync_struct **fp, int sig, int band) { if (*fp) { rcu_read_lock(); kill_fasync_rcu(rcu_dereference(*fp), sig, band); rcu_read_unlock(); } } static void kill_fasync_rcu(struct fasync_struct *fa, int sig, int band) { while (fa) { struct fown_struct *fown; unsigned long flags; ... spin_lock_irqsave(&fa->fa_lock, flags); if (fa->fa_file) { fown = &fa->fa_file->f_owner; if (!(sig == SIGURG && fown->signum == 0)) send_sigio(fown, fa->fa_fd, band); } spin_unlock_irqrestore(&fa->fa_lock, flags); fa = rcu_dereference(fa->fa_next); } } void send_sigio(struct fown_struct *fown, int fd, int band) { struct task_struct *p; enum pid_type type; struct pid *pid; int group = 1; read_lock(&fown->lock); type = fown->pid_type;-------------------由fown_struct可知,pid是SIGIO将要发送的进程号或者进程组号;pid_typeSIGIO将要发送的进程组类型。PIDTYPE_PID表示进程PID,PIDTYPE_TGID表示线程组领头的进程PID,PIDTYPE_PGID表示进程组领头的进程PID,PIDTYPE_SID表示会话组领头进程ID。 if (type == PIDTYPE_MAX) {---------------当pid_type为PIDTYPE_MAX的时候,group为0,表示SIGIO只给次pid发送。 group = 0; type = PIDTYPE_PID; } pid = fown->pid; if (!pid) goto out_unlock_fown; read_lock(&tasklist_lock); do_each_pid_task(pid, type, p) { send_sigio_to_task(p, fown, fd, band, group); } while_each_pid_task(pid, type, p); read_unlock(&tasklist_lock); out_unlock_fown: read_unlock(&fown->lock); }
关于group为0或者1,对于SIGIO的处理有很大影响。
在send_sgio_to_task()中,做了一个workaround,改变group的值。下面看看group究竟是如何影响进程对SIGIO的相应的。
send_sgio_to_task()->do_send_sig_info()->send_signal()->__send_signal()中可以看出groupt的作用。
static int __send_signal(int sig, struct siginfo *info, struct task_struct *t, int group, int from_ancestor_ns) { struct sigpending *pending; struct sigqueue *q; int override_rlimit; int ret = 0, result; assert_spin_locked(&t->sighand->siglock); result = TRACE_SIGNAL_IGNORED; if (!prepare_signal(sig, t, from_ancestor_ns || (info == SEND_SIG_FORCED))) goto ret; pending = group ? &t->signal->shared_pending : &t->pending;-----------------------这里group决定后面SIGIO信号的处理时放入t->signal->shared_pending还是t->pending。如果group为0,则只放入当前进程的pending,其他进程不会处理SIGIO。 ... result = TRACE_SIGNAL_ALREADY_PENDING; if (legacy_queue(pending, sig)) goto ret; result = TRACE_SIGNAL_DELIVERED; ... if (info == SEND_SIG_FORCED) goto out_set; if (sig < SIGRTMIN) override_rlimit = (is_si_special(info) || info->si_code >= 0); else override_rlimit = 0; q = __sigqueue_alloc(sig, t, GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE, override_rlimit);------------------------------------------------------------分配信号sig的sigqueue。 if (q) { list_add_tail(&q->list, &pending->list);-------------------------------------将sig信号放入pending->list列表。 switch ((unsigned long) info) { case (unsigned long) SEND_SIG_NOINFO: q->info.si_signo = sig; q->info.si_errno = 0; q->info.si_code = SI_USER; q->info.si_pid = task_tgid_nr_ns(current, task_active_pid_ns(t)); q->info.si_uid = from_kuid_munged(current_user_ns(), current_uid()); break; case (unsigned long) SEND_SIG_PRIV: q->info.si_signo = sig; q->info.si_errno = 0; q->info.si_code = SI_KERNEL; q->info.si_pid = 0; q->info.si_uid = 0; break; default: copy_siginfo(&q->info, info); if (from_ancestor_ns) q->info.si_pid = 0; break; } userns_fixup_signal_uid(&q->info, t); } else if (!is_si_special(info)) { ... } out_set: signalfd_notify(t, sig); sigaddset(&pending->signal, sig); complete_signal(sig, t, group); ret: trace_signal_generate(sig, info, t, group, result);-------------------------------打印signal_generate()。 return ret; }
从上面的分析可知,当pid_type为PIDTYPE_MAX的时候,group为0,则不会出现sig被其他进程处理的情况。
那么何时pid_type会被设置为PIDTYPE_MAX呢?
通过分析fcntl系统调用,可以看出F_SETOWN_EX命令,如果当前是线程F_OWNER_TID,则设置PIDTYPE_MAX。
SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg) { struct fd f = fdget_raw(fd); long err = -EBADF; if (!f.file) goto out; if (unlikely(f.file->f_mode & FMODE_PATH)) { if (!check_fcntl_cmd(cmd)) goto out1; } err = security_file_fcntl(f.file, cmd, arg); if (!err) err = do_fcntl(fd, cmd, arg, f.file); out1: fdput(f); out: return err; } static long do_fcntl(int fd, unsigned int cmd, unsigned long arg, struct file *filp) { long err = -EINVAL; switch (cmd) { ... case F_GETOWN: err = f_getown(filp); force_successful_syscall_return(); break; case F_SETOWN: f_setown(filp, arg, 1); err = 0; break; case F_GETOWN_EX: err = f_getown_ex(filp, arg); break; case F_SETOWN_EX: err = f_setown_ex(filp, arg); break; ... } return err; } static int f_setown_ex(struct file *filp, unsigned long arg) { struct f_owner_ex __user *owner_p = (void __user *)arg; struct f_owner_ex owner; struct pid *pid; int type; int ret; ret = copy_from_user(&owner, owner_p, sizeof(owner)); if (ret) return -EFAULT; switch (owner.type) { case F_OWNER_TID: type = PIDTYPE_MAX; break; ... } rcu_read_lock(); pid = find_vpid(owner.pid); if (owner.pid && !pid) ret = -ESRCH; else __f_setown(filp, pid, type, 1); rcu_read_unlock(); return ret; } static void f_modown(struct file *filp, struct pid *pid, enum pid_type type, int force) { write_lock_irq(&filp->f_owner.lock); if (force || !filp->f_owner.pid) { put_pid(filp->f_owner.pid); filp->f_owner.pid = get_pid(pid); filp->f_owner.pid_type = type; if (pid) { const struct cred *cred = current_cred(); filp->f_owner.uid = cred->uid; filp->f_owner.euid = cred->euid; } } write_unlock_irq(&filp->f_owner.lock); }
设置线程F_SETOWN_EX的属性如下:
struct f_owner_ex owner_ex; owner_ex.pid = syscall(SYS_gettid); owner_ex.type = F_OWNER_TID; fcntl(enc->fd_enc, F_SETOWN_EX, &owner_ex); //fcntl(enc->fd_enc, F_SETOWN, syscall(SYS_gettid)); /* this thread will receive SIGIO */ oflags = fcntl(enc->fd_enc, F_GETFL); fcntl(enc->fd_enc, F_SETFL, oflags | FASYNC); /* set ASYNC notification flag */
F_SETOWN_EX相比于F_SETOWN多设置了一个进程属性,这就告诉内核此文件SIGIO信号绑定的对象是线程,而不是进程。
后面kill_fasync()发送SIGIO信号的group就是线程范围的0,而不是进程范围的1。
综上所述,将SIGIO和snap线程绑定是一个较好的方法。通过在snap线程中设置 file->f_owner的属性。
5. 信号何时被处理
在系统调用、异常、中断等返回用户空间前,内核都会检查是否有信号在当前进程中挂起。
如果有信号处于pending状态,即通过TIF_SIGPENDING,就调用do_notify_resume()处理信号。
asmlinkage void do_notify_resume(unsigned int thread_flags, struct pt_regs *regs, int syscall) { if (thread_flags & _TIF_SIGPENDING)------------------------------------如果进程标志位TIF_SIGPENDING置位,表示进程有未处理的信号。 do_signal(regs, syscall); ... } static void do_signal(struct pt_regs *regs, int syscall) { unsigned int retval = 0, continue_addr = 0, restart_addr = 0; struct ksignal ksig; if (!user_mode(regs)) return; ... if (try_to_freeze()) goto no_signal; if (get_signal(&ksig)) {----------------------------------------------从当前进程task_struct->pending或者task_struct->signal->shared_pending获取pending的信号。 sigset_t *oldset; if (regs->pc == restart_addr) { if (retval == -ERESTARTNOHAND || (retval == -ERESTARTSYS && !(ksig.ka.sa.sa_flags & SA_RESTART))) { regs->a0 = -EINTR; regs->pc = continue_addr; } } ... if (handle_signal(ksig.sig, &ksig.ka, &ksig.info, oldset, regs) == 0) { if (test_thread_flag(TIF_RESTORE_SIGMASK)) clear_thread_flag(TIF_RESTORE_SIGMASK); } return; } ... } int get_signal(struct ksignal *ksig) { struct sighand_struct *sighand = current->sighand; struct signal_struct *signal = current->signal; int signr; ... for (;;) { struct k_sigaction *ka; ... signr = dequeue_signal(current, ¤t->blocked, &ksig->info);-----------------先从task_struct->pending列表取信号,其次从task_struct->signal->shared_pending上取信号。 if (!signr) break; /* will return 0 */ if (unlikely(current->ptrace) && signr != SIGKILL) { signr = ptrace_signal(signr, &ksig->info); if (!signr) continue; } ka = &sighand->action[signr-1]; trace_signal_deliver(signr, &ksig->info, ka);-------------------------------------对应signal_deliver()trace events,这里表示真正将信号发送到了进程,将要进行处理。 if (ka->sa.sa_handler == SIG_IGN) /* Do nothing. */ continue; if (ka->sa.sa_handler != SIG_DFL) { ksig->ka = *ka; if (ka->sa.sa_flags & SA_ONESHOT) ka->sa.sa_handler = SIG_DFL; break; /* will return non-zero "signr" value */ } ... do_group_exit(ksig->info.si_signo); } spin_unlock_irq(&sighand->siglock); ksig->sig = signr; return ksig->sig > 0; } static int handle_signal(int sig, struct k_sigaction *ka, siginfo_t *info, sigset_t *oldset, struct pt_regs *regs) { struct task_struct *tsk = current; int ret; /* set up the stack frame, regardless of SA_SIGINFO, and pass info anyway. */ ret = setup_rt_frame(sig, ka, info, oldset, regs);---------------------------------为执行信号handler进行栈准备。 if (ret != 0) { force_sigsegv(sig, tsk); return ret; } spin_lock_irq(¤t->sighand->siglock); sigorsets(¤t->blocked, ¤t->blocked, &ka->sa.sa_mask); if (!(ka->sa.sa_flags & SA_NODEFER)) sigaddset(¤t->blocked, sig); recalc_sigpending(); spin_unlock_irq(¤t->sighand->siglock); return 0; } static int setup_rt_frame (int sig, struct k_sigaction *ka, siginfo_t *info, sigset_t *set, struct pt_regs *regs) { struct rt_sigframe *frame; int err = 0; struct csky_vdso *vdso = current->mm->context.vdso; frame = get_sigframe(ka, regs, sizeof(*frame)); ... /* Set up registers for signal handler */ regs->usp = (unsigned long) frame; regs->pc = (unsigned long) ka->sa.sa_handler;-------------------------------------准备pc指针以及返回值等。 regs->lr = (unsigned long)vdso->rt_signal_retcode; adjust_stack: regs->a0 = sig; /* first arg is signo */ regs->a1 = (unsigned long)(&(frame->info)); /* second arg is (siginfo_t*) */ regs->a2 = (unsigned long)(&(frame->uc));/* third arg pointer to ucontext */ return err; give_sigsegv: if (sig == SIGSEGV) ka->sa.sa_handler = SIG_DFL; force_sig(SIGSEGV, current); goto adjust_stack; }
6. 使用信号的局限性
但是其实在使用信号作为驱动和应用之间异步通知,存在一定局限性。
6.1 信号丢失
在《Linux/UNIX系统编程手册》 20.13 改变信号处置:sigaction()中有这么一段话,说明在信号处理期间如果同一信号收到多次,那么只处理一次。这就存在丢失信号的可能性。
sa_mask字段定义了一组新号,在调用由sa_handler所定义的处理器程序时将阻塞该组信号。
当调用信号处理程序时,会在调用信号处理程序之前,将该组信号中当前未处于进程掩码之列的任何信号自动添加到进程掩码中。这些信号将保留在进程掩码中,直至信号处理程序返回,届时将自动删除这些信号。
利用sa_mask字段可指定一组信号,不允许它们中断此处理程序的执行。-------------------------------------------------sa_mask指定handler期间屏蔽的信号
此外,引发处理程序调用的信号将自动添加到进程信号掩码中。-------------------------------------------------------------handler处理期间自动阻塞本身信号
handler处理期间屏蔽本身信号,意味着,当正在执行handler时,如果同一个信号实例第二次抵达,信号handler将不会递归中断自己。由于不会对在遭阻塞的信号进行排队处理,如果在handler执行过程中重复产生这些信号中的任何信号,(稍后)对信号的传递将是一次性的。
struct sigaction结构体如下:
struct sigaction {
unsigned int sa_flags;
__sighandler_t sa_handler;
__sigrestore_t sa_restorer;
sigset_t sa_mask; /* mask last for extensibility */
};
6.2 信号handler的进程属性
在一个多线程环境下,sa_mask是线程属性的,意味着每个线程都有自己的掩码,信号也可以和线程绑定。
但是信号handler是进程属性的,也即一个进程范围内,一个信号只能有一个handler。
这就造成不同线程设置signal handler,会覆盖,这就造成不确定性。
所以在多线程环境下,同一信号无法具备多handler,也即无法使用同一信号达到不同目的。
经过研究后,发觉这两个局限性都可以通过fcntl(fd, F_SETSIG, sig)来解决,一是可以指定特定的sig来指定不同handler,从而避开同一SIGIO带来的问题;而是自定义的实时信号,还具备queue的功能,同时还具备优先级概念,除非队列溢出。
参考文档:
1. 《Linux/UNIX系统编程手册》 第63.3章 信号驱动I/O,尤其63.3.2 优化信号驱动I/O的使用。
2. 《Linux/UNIX系统编程手册》第20章 信号:基本概念、第21章 信号:信号处理函数、第22章 信号:高级特性。