关键词: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 = ;

/* 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, , 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 = ; /* register for SIGIO in EWLEnableHW */
} i32 EWLWaitHwRdy(const void *inst, u32 *slicesReady)
{
hx280ewl_t *enc = (hx280ewl_t *) inst;
u32 prevSlicesReady = ; // PTRACE("EWLWaitHw: Start\n"); printf("EWLWaitHw: Start\n");
fflush(stdout); /* Check invalid parameters */
if(enc == NULL)
{
assert();
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 = ;
pthread_sigmask(SIG_UNBLOCK, &set, NULL);
} asic_status = enc->pRegBase[]; /* 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 > /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 > /sys/kernel/debug/tracing/events/sched/sched_switch/enable
echo > /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 > /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 > /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 > /sys/kernel/debug/tracing/events/sched/sched_switch/enable
echo > /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 > /sys/kernel/debug/tracing/events/irq/irq_handler_entry/enable
echo > /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-   [] d...   102.576745: sched_switch: prev_comm=AiApp prev_pid= prev_prio= prev_state=S ==> next_comm=snap next_pid= next_prio=
snap- [] d... 102.577042: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=R ==> next_comm=coreComm next_pid= next_prio=
coreComm- [] d... 102.577104: sched_switch: prev_comm=coreComm prev_pid= prev_prio= prev_state=S ==> next_comm=snap next_pid= next_prio=
snap- [] d... 102.577286: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=S ==> next_comm=rtpjitterbuffer next_pid= next_prio=
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- [] d... 102.773860: sched_switch: prev_comm=rtpjitterbuffer prev_pid= prev_prio= prev_state=R ==> next_comm=snap next_pid= next_prio=
snap- [] d... 102.773898: signal_deliver: sig= errno= code= sa_handler=2b9ff450 sa_flags=
snap- [] d... 102.773948: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=S ==> next_comm=coreComm next_pid= next_prio=...
coreComm- [] d... 102.838285: sched_switch: prev_comm=coreComm prev_pid= prev_prio= prev_state=S ==> next_comm=snap next_pid= next_prio=
snap- [] d... 102.839026: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=S ==> next_comm=rtpjitterbuffer next_pid= next_prio=
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>- [] dns. 103.132781: sched_wakeup: comm=snap pid= prio= target_cpu=
<idle>- [] d... 103.132833: sched_switch: prev_comm=swapper prev_pid= prev_prio= prev_state=R ==> next_comm=snap next_pid= next_prio=
snap- [] d... 103.132995: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=S ==> next_comm=rtpjitterbuffer next_pid= next_prio=

所以有必要全部打开sched_wakeup()查看在中断处理函数中是否sched_wakeup()了AiApp?

因为sched_switch()太多,所以没有打开。关键点在于中断处理函数中sched_wakeup()了哪个线程。

echo > /sys/kernel/debug/tracing/trace
echo > /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 > /sys/kernel/debug/tracing/evnnnnn/ents/signal/enable echo > /sys/kernel/debug/tracing/events/sched/sched_wakeup/filter
echo > /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 > /sys/kernel/debug/tracing/events/irq/irq_handler_entry/enable
echo > /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- [] d.h. 59.216451: sched_wakeup: comm=AiApp pid= prio= target_cpu=
kworker/u2:- [] d... 59.216815: sched_wakeup: comm=sshd pid= prio= target_cpu=
sshd- [] d.h. 59.217162: irq_handler_entry: irq= name=hx280enc
sshd- [] dnh. 59.217210: sched_wakeup: comm=snap pid= prio= target_cpu=
sshd- [] dnh. 59.217220: signal_generate: sig= errno= code= comm=snap pid= grp= res=
sshd- [] dnh. 59.217224: irq_handler_exit: irq= ret=handled
snap- [] d... 59.217281: signal_deliver: sig= errno= code= sa_handler=2b9ff450 sa_flags=
snap- [] dnh. 59.217380: sched_wakeup: comm=omx_main pid= prio= target_cpu=
omx_main- [] d... 59.217580: sched_wakeup: comm=omx_g1_output pid= prio= target_cpu=
omx_main- [] d... 59.217637: sched_wakeup: comm=omxdec:src pid= prio= target_cpu=...
cat- [] d... 59.413571: sched_wakeup: comm=kworker/u2: pid= prio= target_cpu=
udpsrc0:src- [] dnh. 59.414333: sched_wakeup: comm=AiApp pid= prio= target_cpu=
sshd- [] dnh. 59.414615: sched_wakeup: comm=coreComm pid= prio= target_cpu=
sshd- [] d.h. 59.414790: irq_handler_entry: irq= name=hx280enc
sshd- [] dnh. 59.533611: sched_wakeup: comm=AiApp pid= prio= target_cpu=000----------------------------表明AiApp进程被选中用于处理SIGIO信号。
sshd- [] dnh. 59.533620: signal_generate: sig= errno= code= comm=snap pid= grp= res=
sshd- [] dnh. 59.533623: irq_handler_exit: irq= ret=handled
AiApp- [] d.h. 59.533696: sched_wakeup: comm=cat pid= prio= target_cpu=
AiApp- [] d.h. 59.533719: sched_wakeup: comm=coreComm pid= prio= target_cpu=
AiApp- [] d.h. 59.533726: sched_wakeup: comm=AiApp pid= prio= target_cpu=
AiApp- [] d... 59.534558: signal_deliver: sig= errno= code= sa_handler=2b9ff450 sa_flags=
cat- [] dn.. 59.535430: sched_wakeup: comm=kworker/u2: pid= prio= target_cpu=
cat- [] dnh. 59.535769: sched_wakeup: comm=coreComm pid= prio= target_cpu=
cat- [] dn.. 59.535786: sched_wakeup: comm=kworker/u2: pid= prio= target_cpu=
cat- [] dn.. 59.535930: sched_wakeup: comm=kworker/u2: pid= prio= target_cpu=
cat- [] dn.. 59.536006: sched_wakeup: comm=kworker/u2: pid= prio= target_cpu=
cat- [] dn.. 59.536059: sched_wakeup: comm=kworker/u2: pid= prio= target_cpu=
cat- [] dn.. 59.536112: sched_wakeup: comm=kworker/u2: pid= prio= target_cpu=

小结:这里面清晰的看到中断处理中,异常情况下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[];
__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-   [] ....  2950.211703: signal_blocked: handle_signal  comm=snap pid= blocked.sig[]=0x10000000
snap- [] .... 2951.882924: signal_blocked: sigsuspend comm=snap pid= blocked.sig[]=0x10000000
snap- [] .... 2951.882940: signal_blocked: sigsuspend comm=snap pid= blocked.sig[]=0x00000000
rtpjitterbuffer- [] d.h. 2951.894413: signal_blocked: __handle_irq_event_percpu comm=snap pid= blocked.sig[]=0x00000000---------------可以看出此次中断handler中,给snap进程不会被block。
rtpjitterbuffer- [] d.h. 2951.894426: irq_handler_entry: irq= name=hx280enc
rtpjitterbuffer- [] d.h. 2951.894464: signal_generate: sig= errno= code= comm=snap pid= grp= res=
rtpjitterbuffer- [] d.h. 2951.894468: irq_handler_exit: irq= ret=handled
rtpjitterbuffer- [] d.h. 2951.894471: signal_blocked: __handle_irq_event_percpu comm=snap pid= blocked.sig[]=0x00000000
snap- [] .... 2951.894519: signal_blocked: sigsuspend comm=snap pid= blocked.sig[]=0x00000000
snap- [] .... 2951.894526: signal_blocked: sigsuspend comm=snap pid= blocked.sig[]=0x00000000
snap- [] d... 2951.894546: signal_deliver: sig= errno= code= sa_handler=2b983410 sa_flags=10000000---------------------------snap进程在返回用户空间的时候,得到执行SIGIO handler的机会。
snap- [] .... 2951.894551: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x00000000
snap- [] .... 2951.894563: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x00000000
snap- [] d... 2951.894566: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x10000000
snap- [] .... 2951.894569: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x10000000
udpsrc0:src- [] d.h. 2952.194958: signal_blocked: __handle_irq_event_percpu comm=snap pid= blocked.sig[]=0x10000000---------------此次中断handler中snap的SIGIO被block。
udpsrc0:src- [] d.h. 2952.194973: irq_handler_entry: irq= name=hx280enc
udpsrc0:src- [] dnh. 2952.202000: signal_generate: sig= errno= code= comm=snap pid= grp= res=
udpsrc0:src- [] dnh. 2952.202004: irq_handler_exit: irq= ret=handled
udpsrc0:src- [] dnh. 2952.202008: signal_blocked: __handle_irq_event_percpu comm=snap pid= blocked.sig[]=0x10000000
AiApp- [] d... 2952.202264: signal_deliver: sig= errno= code= sa_handler=2b983410 sa_flags=10000000---------------------------SIGIO信号handler被AiApp进程处理。
AiApp- [] .... 2952.202280: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x10000000
AiApp- [] .... 2952.202294: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x10000000
AiApp- [] d... 2952.202297: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x10000000
AiApp- [] .... 2952.202301: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x10000000
snap- [] .... 2952.203748: signal_blocked: sigsuspend comm=snap pid= blocked.sig[]=0x10000000
snap- [] .... 2952.203759: signal_blocked: sigsuspend comm=snap pid= blocked.sig[]=0x00000000-----------------------------开始执行sigsuspend()。
snap- [] .... 3243.710836: signal_blocked: sigsuspend comm=snap pid= blocked.sig[]=0x00000000-----------------------------一段时间过后整个进程出错,关闭退出。
snap- [] .... 3243.710850: signal_blocked: sigsuspend comm=snap pid= blocked.sig[]=0x00000000
sh- [] .... 3244.812561: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x00000000
sh- [] .... 3244.812584: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x00000000
sh- [] d... 3244.812588: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x00000000
sh- [] .... 3244.812591: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x00000000

那么究竟是哪些地方改变blocked.sig[0]呢?上面对于pthread_sigmask()的执行没有反应,所以这里需要使用signal_blocked()在sigsuspend()、sigprocmask()、handle_signal()中打印blocked.sig[0]值得改变。

            snap-   [] d...   687.919424: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=S ==> next_comm=udpsrc1:src next_pid= next_prio=
filter- [] d... 688.494547: sched_wakeup: comm=snap pid= prio= target_cpu=
filter- [] d... 688.494657: sched_switch: prev_comm=filter prev_pid= prev_prio= prev_state=S ==> next_comm=snap next_pid= next_prio=
snap- [] .... 688.498629: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x00000000
snap- [] .... 688.498639: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x00000000
snap- [] .... 688.498645: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x10000000
snap- [] .... 688.498650: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x10000000
snap- [] .... 688.498652: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x10000000
snap- [] .... 688.498655: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x00000000
snap- [] d... 688.499279: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=R ==> next_comm=cat next_pid= next_prio=
rtpjitterbuffer- [] d... 688.503295: sched_switch: prev_comm=rtpjitterbuffer prev_pid= prev_prio= prev_state=S ==> next_comm=snap next_pid= next_prio=
snap- [] .... 688.503493: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x00000000---------------------------------------------------------第一个pthread_sigmask()
snap- [] .... 688.503497: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x00000000
snap- [] .... 688.503503: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x10000000
snap- [] .... 688.503548: signal_blocked: sigsuspend comm=snap pid= blocked.sig[]=0x10000000----------------------------------------------------------sigsuspend(),以阻塞SIGIO信号状态进入。然后解除阻塞进入进程可唤醒模式。
snap- [] .... 688.503551: signal_blocked: sigsuspend comm=snap pid= blocked.sig[]=0x00000000
snap- [] d... 688.503562: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=S ==> next_comm=udpsrc1:src next_pid= next_prio=120------------snap进入休眠
udpsrc1:src- [] d.h. 688.503888: signal_blocked: __handle_irq_event_percpu comm=snap pid= blocked.sig[]=0x00000000--------------------------------------------产生中断
udpsrc1:src- [] d.h. 688.503898: irq_handler_entry: irq= name=hx280enc
udpsrc1:src- [] d.h. 688.503930: sched_wakeup: comm=snap pid= prio= target_cpu=000------------------------------------------------------------------------------snap进程放入RunQ
udpsrc1:src- [] d.h. 688.503935: signal_generate: sig= errno= code= comm=snap pid= grp= res=
udpsrc1:src- [] d.h. 688.503939: irq_handler_exit: irq= ret=handled
udpsrc1:src- [] d.h. 688.503942: signal_blocked: __handle_irq_event_percpu comm=snap pid= blocked.sig[]=0x00000000
rtpjitterbuffer- [] d... 688.504528: sched_switch: prev_comm=rtpjitterbuffer prev_pid= prev_prio= prev_state=S ==> next_comm=snap next_pid= next_prio=120--------进程被唤醒。
snap- [] .... 688.504542: signal_blocked: sigsuspend comm=snap pid= blocked.sig[]=0x00000000
snap- [] .... 688.504546: signal_blocked: sigsuspend comm=snap pid= blocked.sig[]=0x00000000
snap- [] d... 688.504564: signal_deliver: sig= errno= code= sa_handler=2b983410 sa_flags=10000000--------------------------------------------------------信号发送到snap进程
snap- [] .... 688.504571: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x00000000--------------------------------------------------------进行SIGIO sig_handler()处理,在handle_signal()结尾恢复对SIGIO的阻塞。
snap- [] d... 688.504583: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x00000000
snap- [] d... 688.504585: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x10000000
snap- [] .... 688.504588: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x10000000
snap- [] .... 688.504713: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x10000000---------------------------------------------------------第二个pthread_sigmask(),解除对SIGIO的阻塞。
snap- [] .... 688.504716: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x10000000
snap- [] .... 688.504719: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x00000000
snap- [] d... 688.504988: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=R ==> next_comm=NotifySrvlResul next_pid= next_prio=
rtpjitterbuffer- [] d... 688.506152: sched_switch: prev_comm=rtpjitterbuffer prev_pid= prev_prio= prev_state=S ==> next_comm=snap next_pid= next_prio=...
filter- [] d... 691.058210: sched_switch: prev_comm=filter prev_pid= prev_prio= prev_state=S ==> next_comm=snap next_pid= next_prio=
snap- [] d... 691.059290: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=R ==> next_comm=cat next_pid= next_prio=
filter- [] d... 691.060795: sched_switch: prev_comm=filter prev_pid= prev_prio= prev_state=S ==> next_comm=snap next_pid= next_prio=
snap- [] d... 691.061464: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=R ==> next_comm=omx_main next_pid= next_prio=
sshd- [] d... 691.064415: sched_switch: prev_comm=sshd prev_pid= prev_prio= prev_state=S ==> next_comm=snap next_pid= next_prio=
snap- [] d... 691.065170: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=R ==> next_comm=coreComm next_pid= next_prio=
coreComm- [] d... 691.065249: sched_switch: prev_comm=coreComm prev_pid= prev_prio= prev_state=S ==> next_comm=snap next_pid= next_prio=
snap- [] d... 691.067633: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=R ==> next_comm=NotifySrvlResul next_pid= next_prio=
rtpjitterbuffer- [] d... 691.071517: sched_switch: prev_comm=rtpjitterbuffer prev_pid= prev_prio= prev_state=S ==> next_comm=snap next_pid= next_prio=
snap- [] .... 691.071615: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x00000000--------------------------------------------------------被SIGIO以外信号唤醒,但是sig_delivered还是为0,还在while中继续等待。
snap- [] .... 691.071618: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x00000000
snap- [] .... 691.071624: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x10000000
snap- [] .... 691.071627: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x10000000
snap- [] .... 691.071630: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x10000000
snap- [] .... 691.071632: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x00000000
snap- [] d... 691.071709: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=R ==> next_comm=coreComm next_pid= next_prio=
coreComm- [] d... 691.071760: sched_switch: prev_comm=coreComm prev_pid= prev_prio= prev_state=S ==> next_comm=snap next_pid= next_prio=
snap- [] d... 691.071933: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=R ==> next_comm=NotifySrvlResul next_pid= next_prio=
NotifySrvlResul- [] d... 691.072021: sched_switch: prev_comm=NotifySrvlResul prev_pid= prev_prio= prev_state=S ==> next_comm=snap next_pid= next_prio=
snap- [] .... 691.072510: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x00000000--------------------------------第一个pthread_sigmask()。
snap- [] .... 691.072514: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=0x00000000
snap- [] .... 691.072518: signal_blocked: sigprocmask comm=snap pid= blocked.sig[]=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- [] d.h. 691.084054: signal_blocked: __handle_irq_event_percpu comm=snap pid= blocked.sig[]=0x10000000-------------------中断触发,但是此时blocked.sig[0]为0x1000000,屏蔽SIGIO。
udpsrc1:src- [] d.h. 691.084069: irq_handler_entry: irq= name=hx280enc
udpsrc1:src- [] dnh. 691.102600: signal_generate: sig= errno= code= comm=snap pid= grp= res=
udpsrc1:src- [] dnh. 691.102605: irq_handler_exit: irq= ret=handled
udpsrc1:src- [] dnh. 691.102608: signal_blocked: __handle_irq_event_percpu comm=snap pid= blocked.sig[]=0x10000000
AiApp-170 [000] d... 691.102845: signal_deliver: sig=29 errno=0 code=128 sa_handler=2b983410 sa_flags=10000000-------------------------------AiApp中进行的SIGIO处理。
AiApp- [] .... 691.102859: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x10000000-------------------------------SIGIO sig_handler()处理。
AiApp- [] d... 691.102871: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x10000000
AiApp- [] d... 691.102874: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x10000000
AiApp- [] .... 691.102877: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x10000000
<idle>- [] dns. 691.303189: sched_wakeup: comm=snap pid= prio= target_cpu=
<idle>- [] d... 691.303267: sched_switch: prev_comm=swapper prev_pid= prev_prio= prev_state=R ==> next_comm=snap next_pid= next_prio=
snap- [] .... 691.303552: signal_blocked: sigsuspend comm=snap pid= blocked.sig[]=0x10000000---------------------------------第二个sigsuspend()处理,等待SIGIO信号handler对sig_delivered才会退出while。而流程卡住,不会发送编码请求,也不会有中断及SIGIO。
snap- [] .... 691.303564: signal_blocked: sigsuspend comm=snap pid= blocked.sig[]=0x00000000
snap- [] d... 691.303584: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=S ==> next_comm=rtpjitterbuffer next_pid= next_prio=
copy- [] dn.. 988.790892: sched_wakeup: comm=snap pid= prio= target_cpu=
adapter- [] d... 988.791081: sched_switch: prev_comm=adapter prev_pid= prev_prio= prev_state=D ==> next_comm=snap next_pid= next_prio=
snap- [] .... 988.791094: signal_blocked: sigsuspend comm=snap pid= blocked.sig[]=0x00000000---------------------------------进程异常准备退出。
snap- [] .... 988.791098: signal_blocked: sigsuspend comm=snap pid= blocked.sig[]=0x00000000
snap- [] d... 988.791111: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=D ==> next_comm=AiApp next_pid= next_prio=
copy- [] dn.. 988.793177: sched_wakeup: comm=snap pid= prio= target_cpu=
AiApp- [] d... 988.794392: sched_switch: prev_comm=AiApp prev_pid= prev_prio= prev_state=x ==> next_comm=snap next_pid= next_prio=
snap- [] d... 988.794428: sched_switch: prev_comm=snap prev_pid= prev_prio= prev_state=x ==> next_comm=adapter next_pid= next_prio=
sh- [] .... 989.892972: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x00000000
sh- [] d... 989.892997: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x00000000
sh- [] d... 989.893001: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=0x00000000
sh- [] .... 989.893005: signal_blocked: handle_signal comm=snap pid= blocked.sig[]=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 ;
if (p->flags & PF_EXITING)
return ;
if (sig == SIGKILL)
return ;
if (task_is_stopped_or_traced(p))
return ;
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 = ;
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 :
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 == ))
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 = ; 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 = ;
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 = , 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 >= );
else
override_rlimit = ; 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 = ;
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 = ;
q->info.si_code = SI_KERNEL;
q->info.si_pid = ;
q->info.si_uid = ;
break;
default:
copy_siginfo(&q->info, info);
if (from_ancestor_ns)
q->info.si_pid = ;
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, );
err = ;
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, );
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 = , continue_addr = , restart_addr = ;
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) == ) {
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, &current->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-]; 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 > ;
} 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 != ) {
force_sigsegv(sig, tsk);
return ret;
} spin_lock_irq(&current->sighand->siglock);
sigorsets(&current->blocked, &current->blocked, &ka->sa.sa_mask);
if (!(ka->sa.sa_flags & SA_NODEFER))
sigaddset(&current->blocked, sig);
recalc_sigpending();
spin_unlock_irq(&current->sighand->siglock);
return ;
} 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 = ; 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章 信号:高级特性。

sigsuspend()阻塞:异步信号SIGIO为什么会被截胡?的更多相关文章

  1. 【C】——利用sigsuspend函数等待信号阻塞进程

    #include<signal.h> int sigsuspend(const sigset_t *sigmask); 返回值:-,并将errno设置为EINTR 将进程的信号屏蔽字设置为 ...

  2. linux可重入、异步信号安全和线程安全

    一 可重入函数 当一个被捕获的信号被一个进程处理时,进程执行的普通的指令序列会被一个信号处理器暂时地中断.它首先执行该信号处理程序中的指令.如果从信号处理程序返回(例如没有调用exit或longjmp ...

  3. Libev源码分析06:异步信号同步化--sigwait、sigwaitinfo、sigtimedwait和signalfd

    一:信号简述 信号是典型的异步事件.内核在某个信号出现时有三种处理方式: a:忽略信号,除了SIGKILL和SIGSTOP信号不能忽略外,其他大部分信号都可以被忽略: b:捕捉信号,也就是在信号发生时 ...

  4. 【C】——sigprocmask 阻塞进程信号

    1.有时候不希望在接到信号时就立即停止当前执行,去处理信号,同时也不希望忽略该信号,而是延时一段时间去调用信号处理函数.这种情况是通过阻塞信号实现的. 2.信号阻塞和忽略信号的区别. 阻塞的概念和忽略 ...

  5. linux 异步信号的同步处理方式

    关于代码的可重入性,设计开发人员一般只考虑到线程安全,异步信号处理函数的安全却往往被忽略.本文首先介绍如何编写安全的异步信号处理函数:然后举例说明在多线程应用中如何构建模型让异步信号在指定的线程中以同 ...

  6. Linux 信号详解五(信号阻塞,信号未决)

    信号在内核中的表示 执行信号的处理动作成为信号递达(Delivery),信号从产生到递达之间的状态称为信号未决(Pending).进程可以选择阻塞(Block)某个信号. 被阻塞的信号产生时将保持在未 ...

  7. [Linux]返回被阻塞的信号集

    一.概述 在另一篇实例说到,进程可以屏蔽它不想接收的信号集. 事实上这些被屏蔽的信号只是阻塞在内核的进程表中,因为他们不能递送给进程,所以状态是未决的(pending). 利用sigpending函数 ...

  8. Node.js 回调函数 1) 阻塞 ,同步 2) 非阻塞 ,异步.

    1.阻塞. 同步. 1) 读取的文件: input.txt 菜鸟教程官网地址:www.runoob.com 2) main.js var fs = require("fs"); / ...

  9. 深入理解非阻塞同步IO和非阻塞异步IO

    这两篇文章分析了Linux下的5种IO模型 http://blog.csdn.net/historyasamirror/article/details/5778378 http://blog.csdn ...

随机推荐

  1. RDIFramework.NET ━ .NET快速信息化系统开发框架 V3.2-> “Tab”标签新增可“最大化”显示功能

    最大化工作区的功能是非常必要的,特别是当模块功能比较多时,把工作区最大的展现出来就变得很重要,RDIFramework.NET V3.2版本对工作区新增了最大功能,最大化工作区后如下图所示:  具体使 ...

  2. Magicodes.NET框架之路——V0.0.0.5 Beta版发布

    最近写代码的时间实在不多,而且今年又打算业余学习下Unity3D以及NodeJs(用于开发游戏后台),因此完善框架的时间更不多了.不过我会一直坚持下去的,同时我也希望有兴趣的同学可以加入Push你的代 ...

  3. Ubuntu18 的超详细常用软件安装

    心血来潮,在笔记本安装了Ubuntu 18 用于日常学习,于是有了下面的安装记录. Gnome-Tweak-Tool gnome-tweak-tool可以打开隐藏的设置,可以详细的对系统进行配置,以及 ...

  4. [React] 从零开始的react

    组件 1. 无状态组件 在React中,组件的名字必须用大写字母开头,而包含该组件定义的文件名也应该是大写字母(便于区分,也可以不是). 无状态组件是纯展示组件,仅仅只是用于数据的展示,只根据传入的p ...

  5. onload 和 domready

    博客地址:https://ainyi.com/46 window.onload 事件会在页面或图像加载完成后触发(即所有元素的资源都下载完毕)如果页面上有许多图片.音乐或 falsh 还没加载完成,o ...

  6. JSON 数据转换

    JSON概述      JSON(Java Script Object Notation)JS对象符号,通常JSON和XML是二选一的,JSON的数据格式很类似于JavaScript的对象 { &qu ...

  7. Java continue的特殊用法 继续当前循环

    前言 今天java练习的时候,遇到了一道有趣的题目,加深了我对cotinue的理解,所以我写个笔记,记录一下continue的特殊用法 continue作用说明 这里我使用个例子来简单说明一下: fo ...

  8. SpringMVC进行文件上传

    进行文件上传前需要添加相应的依赖 在xml文件中进行相应的文件上传解析器的配置 注意:这里有个坑,因为没注意,再排查错误的时候花了一点时间.就是给bean的id一定要是. 否者就会报如下的错误:

  9. excel使用poi操作。

    String real_path = request.getSession().getServletContext().getRealPath("/");//获取文件路径,我是通过 ...

  10. 如何使用纯CSS制作特效导航条?

    先上张图,如何使用纯 CSS 制作如下效果? 在继续阅读下文之前,你可以先缓一缓.尝试思考一下上面的效果或者动手尝试一下,不借助 JS ,能否巧妙的实现上述效果. OK,继续.这个效果是我在业务开发的 ...