0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux内核中信号相关的系统调用

jf_0tjVfeJz 来源:嵌入式ARM和Linux 2024-01-20 09:34 次阅读

正如我们所知,运行在用户态下的程序可以发送和接收信号。这意味着必须定义一组系统调用来允许这类操作。不幸的是,由于历史原因,有些系统调用可能功能相同。 因此,其中一些系统调用永远不会被调用。例如,sys_sigaction()和sys_rt_sigaction()几乎相同,因此C库中包含的sigaction()包装函数最终会调用sys_rt_sigaction()而不是sys_sigaction()。

1 kill()

kill(pid,sig)系统调用用来给常规进程或多线程应用程序发送信号,相应的服务例程是sys_kill()。pid根据值的不同具有不同意义:

pid > 0:sig信号被发送给pid指定进程所属的线程组。

pid = 0:sig信号被发送给与调用进程同一进程组内所有进程的线程组。

pid = –1:sig信号被发送给所有进程,除了swapper(PID 0)、init(PID 1和current进程。

pid < –1:信号被发送给-pid进程组中所有进程的所属线程组。

sys_kill()为信号建立一个最小siginfo_t表,然后调用kill_something_info():

info.si_signo=sig;
info.si_errno=0;
info.si_code=SI_USER;
info._sifields._kill._pid=current->tgid;
info._sifields._kill._uid=current->uid;
returnkill_something_info(sig,&info,pid);

继而,kill_something_info()既可以调用kill_proc_info()(通过group_send_sig_info()发送信号给单个线程组),也可以调用kill_pg_info()(扫描目标进程组所有进程并为每个进程调用send_sig_info()),还可以为系统中的每个进程重复调用group_send_sig_info()(pid=-1)。

kill()能够发送任何信号,包括所谓的实时信号(32~64)。但是,正如我们在信号的产生过程一节中看到的,kill()系统调用不能确保将信号添加到目标进程的挂起信号队列中,因此可能会丢失多个挂起信号。实时信号应该通过诸如rt_sigqueueinfo()之类的系统调用来发送。

System V和BSD Unix变体也有一个killpg()系统调用,它能够显式地向一组进程发送信号。在Linux中,该函数是作为使用kill()系统调用的库函数实现的。另一种变体是raise(),它向当前进程(即执行函数的进程)发送信号。在Linux中,raise()是作为库函数实现的。

2 tkill()/tgkill()

tkill()和tgkill()向线程组中的特定进程发送信号。每个兼容POSIX的pthread库的pthread_kill()函数调用其中的一个来向特定的轻量级进程发送信号。

tkill()需要2个参数:pid,目标进程的PID;sig,信号编码。内核中的sys_tkill()服务例程填充siginfo表,获取进程描述符地址,进行一些权限检查,并调用specific_send_sig_info()发送信号。

tgkill()不同于tkill(),它需要第3个参数:tgid,线程组ID,该线程组包含信号的目标进程。sys_tgkill()服务例程执行与sys_tkill()完全相同的操作,但也检查信号的目标进程是否属于线程组tgid。这个额外的检查解决了当信号被发送到一个正在被终止的进程时发生的竞争条件:如果另一个多线程应用程序创建轻量级进程的速度足够快,那么信号可能会被传递给错误的进程。tgkill()解决了这个问题,因为线程组ID在多线程应用程序的生命周期内永远不会更改。

3 更改信号行为

sigaction(sig,act,oact)系统调用允许用户为信号指定动作;当然,如果没有定义信号动作,内核将执行信号相关联的默认动作。

相应的sys_sigaction()服务例程作用于2个参数:sig,信号值;act,类型为old_sigaction的表,用以指定新行为;oact,可选输出参数,用以获取信号之前的行为动作。(old_sigaction和sigaction具有相同的数据结构,但是成员顺序不一致)

该函数首先检查act地址是否有效。然后用*act的字段填充k_sigaction类型的new_ka局部变量的sa_handler、sa_flags和sa_mask字段:

__get_user(new_ka.sa.sa_handler,&act->sa_handler);
__get_user(new_ka.sa.sa_flags,&act->sa_flags);
__get_user(mask,&act->sa_mask);
siginitset(&new_ka.sa.sa_mask,mask);

函数调用do_sigaction()将新的new_ka表复制到current->sig->action表的第sig-1项中:

k=¤t->sig->action[sig-1];
if(act){
*k=*act;
sigdelsetmask(&k->sa.sa_mask,sigmask(SIGKILL)|sigmask(SIGSTOP));
if(k->sa.sa_handler==SIG_IGN||(k->sa.sa_handler==SIG_DFL&&
(sig==SIGCONT||sig==SIGCHLD||sig==SIGWINCH||sig==SIGURG))){
rm_from_queue(sigmask(sig),¤t->signal->shared_pending);
t=current;
do{
rm_from_queue(sigmask(sig),¤t->pending);
recalc_sigpending_tsk(t);
t=next_thread(t);
}while(t!=current);
}
}

POSIX标准要求,当默认动作为ignore时,将信号动作设置为SIG_IGN或SIG_DFL会导致所有相同类型的挂起信号被丢弃。此外,请注意,无论信号处理程序请求的屏蔽信号是什么,SIGKILL和SIGSTOP都不会被屏蔽。

sigaction()系统调用还允许用户初始化sigaction表中的sa_flags字段。我们在前面曾经列出了该字段允许的值和相关含义。

旧的System V Unix变体提供了signal()系统调用,它仍然被程序员广泛使用。最近的C库通过rt_sigaction()实现了signal()。然而,Linux仍然支持旧的C库,并提供sys_signal()服务例程:

new_sa.sa.sa_handler=handler;
new_sa.sa.sa_flags=SA_ONESHOT|SA_NOMASK;
ret=do_sigaction(sig,&new_sa,&old_sa);
returnret?ret:(unsignedlong)old_sa.sa.sa_handler;

4 检查挂起的阻塞信号

sigpending()系统调用允许进程检查挂起的阻塞信号集,例如那些在阻塞时产生的信号。对应的sys_sigpending()服务例程作用于单个参数set,用户变量的地址,在其中,位数组需要被拷贝:

sigorsets(&pending,¤t->pending.signal,
¤t->signal->shared_pending.signal);
sigandsets(&pending,¤t->blocked,&pending);
copy_to_user(set,&pending,4);

5 修改阻塞信号集

sigprocmask()系统调用允许进程修改阻塞信号集; 它仅适用于常规(非实时)信号。 相应的sys_sigprocmask()服务例程作用于3个参数:

oset: 进程地址空间中,指向存储先前位掩码的位数组的指针。

set: 进程地址空间中,指向存储新位掩码的位数组的指针。

how:标志,可取的值如下所示:

SIG_BLOCK:set指向的位掩码数组必须被添加阻塞信号的位掩码数组中。

SIG_UNBLOCK:set指向的位掩码数组必须从阻塞信号的位掩码数组中移除。

SIG_SETMASK:set指向的位掩码数组设定为新的阻塞信号的位掩码数组。

该函数调用copy_from_user()将set指向的值拷贝到new_set这个局部变量中,并将current进程的阻塞的标准信号的位掩码数组拷贝到old_set这个局部变量中。然后根据how标志设置这两个变量:

if(copy_from_user(&new_set,set,sizeof(*set)))
return-EFAULT;
new_set&=~(sigmask(SIGKILL)|sigmask(SIGSTOP));
old_set=current->blocked.sig[0];
if(how==SIG_BLOCK)
sigaddsetmask(¤t->blocked,new_set);
elseif(how==SIG_UNBLOCK)
sigdelsetmask(¤t->blocked,new_set);
elseif(how==SIG_SETMASK)
current->blocked.sig[0]=new_set;
else
return-EINVAL;
recalc_sigpending(current);
if(oset&©_to_user(oset,&old_set,sizeof(*oset)))
return-EFAULT;
return0;

6 挂起进程

在阻塞了由mask参数对应的标准信号之后,sigsuspend()系统调用将进程置于TASK_INTERRUPTIBLE状态。只有当一个非忽略、非阻塞的信号被发送给进程时,进程才会被唤醒。

相应的sys_sigsuspend()服务例程执行以下内容:

mask&=~(sigmask(SIGKILL)|sigmask(SIGSTOP));
saveset=current->blocked;
siginitset(¤t->blocked,mask);
recalc_sigpending(current);
regs->eax=-EINTR;
while(1){
current->state=TASK_INTERRUPTIBLE;
schedule();
if(do_signal(regs,&saveset))
return-EINTR;
}

将进程设置为可中断的挂起状态后,调用schedule()函数选择其它进程来运行。 当发出sigsuspend()系统调用的进程再次执行时,sys_sigsuspend()调用do_signal()函数来传递唤醒进程的信号。 如果该函数返回值1,则不会忽略该信号。因此,系统调用通过返回错误代码-EINTR来终止。

其实,这个功能完全可以通过组合sigprocmask()和sleep()来实现。但是,sigsuspend()解决了一个竞态问题:因为进程随时会交叉执行,先通过系统调用执行A动作,然后通过系统调用执行B动作,并不等价于通过单个系统调用直接执行A和B两个动作。

这种情况下,sigprocmask()可能会在调用sleep()之前对信号解除阻塞。如果这个发生,因为唤醒信号已经传递过,从而进程得不到唤醒而永远留在TASK_INTERRUPTIBLE状态。相反,sigsuspend()不允许在解除阻塞之后和schedule()调用之前发送信号,因为其它进程在这个时间段不可能抢占CPU时间。

7 实时信号的系统调用

前面介绍的系统调用都是针对标准信号的,对于实时信号有专门的的系统调用。

实时信号的系统调用,如rt_sigaction()、rt_sigpending()、rt_sigprocmask()、rt_sigsuspend()与前面的标准信号对应的系统调用类似,不再赘述。简单介绍一下实时信号队列相关的两个系统调用:

rt_sigqueueinfo()

发送实时信号,以便将其添加目标进程的共享挂起信号队列中。通常通过标准库函数中的sigqueue()实现。

rt_sigtimedwait()

将阻塞的挂起信号从队列中取出而不发送它,并将信号值返回给调用者;如果没有阻塞信号挂起,则将当前进程挂起一段固定的时间。通常通过标准库函数sigwaitinfo()和sigtimedwait()调用。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内核
    +关注

    关注

    3

    文章

    1309

    浏览量

    39850
  • Linux
    +关注

    关注

    87

    文章

    10990

    浏览量

    206738
  • 线程
    +关注

    关注

    0

    文章

    489

    浏览量

    19498
  • 系统调用
    +关注

    关注

    0

    文章

    27

    浏览量

    8285

原文标题:Linux内核-信号相关的系统调用

文章出处:【微信号:嵌入式ARM和Linux,微信公众号:嵌入式ARM和Linux】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux内核系统调用详解

    Linux内核中设置了一组用于实现各种系统功能的子程序,称为系统调用。用户可以通过系统
    发表于 08-23 10:37 619次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b>中<b class='flag-5'>系统</b><b class='flag-5'>调用</b>详解

    Linux内核中信号详解

      1 信号的角色 1.1 x86/64架构信号定义 1.2 ARM架构信号定义 1.3 RISC-V架构信号定义 1.4 信号
    的头像 发表于 01-13 09:40 762次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b><b class='flag-5'>中信号</b>详解

    Linux内核中信号的传递过程

    前面我们已经介绍了内核注意到信号的到来,调用相关函数更新进程描述符以便进程接收处理信号。但是,如果目标进程此时没有运行,
    的头像 发表于 01-17 09:51 464次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b><b class='flag-5'>中信号</b>的传递过程

    Linux内核系统调用

    Linux内核系统调用1. 应用程序通过API而不是直接调用系统
    发表于 02-21 10:49

    什么是Linux系统调用,包括哪些内容

    所谓系统调用是指操作系统提供给用户程序调用的一组“特殊”接口,用户程序可以通过这组“特殊”接口获得操作系统
    发表于 08-23 11:55

    ARM linux系统调用的实现原理

    大家都知道linux的应用程序要想访问内核必须使用系统调用从而实现从usr模式转到svc模式。下面咱们看看它的实现过程。
    发表于 05-30 11:24 2181次阅读

    Linux内核系统调用扩展研究

    系统凋用是操作系统内核提供给用户使用内核服务的接口。LinuX操作系统由于其自由开放性,用户可在
    发表于 07-25 16:09 40次下载
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b><b class='flag-5'>系统</b><b class='flag-5'>调用</b>扩展研究

    编译Linux2.6内核并添加一个系统调用

    本文以实例来详细描述了从准备一直到使用新内核Linux2.6 内核编译过程,然后介绍了添加系统调用的实现步骤,最后给实验结果。
    发表于 12-01 15:54 46次下载

    透了解系统调用助你成为Linux下编程高手

    Linux内核中设置了一组用于实现各种系统功能的子程序,称为系统调用。用户可以通过系统
    的头像 发表于 05-11 11:27 3130次阅读
    透了解<b class='flag-5'>系统</b><b class='flag-5'>调用</b>助你成为<b class='flag-5'>Linux</b>下编程高手

    你知道Linux系统调用的原理

    系统调用是应用程序与操作系统内核之间的接口,它决定了程序如何与内核打交道的。无论程序是直接进行系统
    发表于 05-16 16:21 1324次阅读
    你知道<b class='flag-5'>Linux</b><b class='flag-5'>系统</b><b class='flag-5'>调用</b>的原理

    Linux系统调用的技巧

    前以及大部分中断服务返回前,都会跳转至此处入口地址。 该段程序不仅仅为系统调用服务,它还处理中断嵌套、CPU调度、信号等事务。  2.通过修改内核源代码添加
    发表于 04-02 14:36 297次阅读

    如何区分xenomai、linux系统调用/服务

    对于同一个POSIX接口应用程序,可能既需要xenomai内核提供服务(xenomai 系统调用),又需要调用linux
    的头像 发表于 05-10 10:28 1695次阅读

    Linux内核系统调用概述及实现原理

    本文介绍了系统调用的一些实现细节。首先分析了系统调用的意义,它们与库函数和应用程序接口(API)有怎样的关系。然后,我们考察了Linux
    的头像 发表于 05-14 14:11 1917次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b><b class='flag-5'>系统</b><b class='flag-5'>调用</b>概述及实现原理

    系统调用:用户栈与内核栈的切换(上)

    当发生系统调用、产生异常,外设发生中断等事件时,会发生用户栈和内核栈之间的切换, 本文从系统调用角度分析用户栈与
    的头像 发表于 07-31 11:27 596次阅读
    <b class='flag-5'>系统</b><b class='flag-5'>调用</b>:用户栈与<b class='flag-5'>内核</b>栈的切换(上)

    Linux系统调用的具体实现原理

    文我将基于 ARM 体系结构角度,从 Linux 应用层例子到内核系统调用函数的整个过程来梳理一遍,讲清楚linux
    的头像 发表于 09-05 17:16 760次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>系统</b><b class='flag-5'>调用</b>的具体实现原理