Linux下的进程信号

本文详细介绍了Linux下进程信号的产生方式,包括键盘输入、程序异常和kill命令等。讨论了信号在内核中的表示,如pending、block和hander表,并阐述了进程收到信号后的处理动作,如设置信号屏蔽字、修改信号处理动作。特别地,文章还讲解了SIGCHLD信号及其在子进程退出时的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

信号的产生

信号的产生方式

  • 键盘产生

    • 键盘产生的信号只能发送给前台进程。例如:[Ctrl+C]…
  • 程序异常

    • 除0错误。除0错误会导致硬件错误。
    • core dumped(核心转储):当进程异常退出时,操作系统会将该进程发生异常退出之前在内存中的数据存储至硬盘上。
      • 但是通常发生程序异常退出时,用户并不会发现程序产生了一个core dumped文件,这是因为操作系统当前给用户可产生core file size大小为0,因为core文件的大小不是很小的,所以一般系统会将可产生core文件的大小为0,即通常不产生core文件。
      • ulimit -a:查看系统当前分配给用户设置资源方面的限制条件。
      • core文件前面是core,后面是引起产生该core文件的进程pid。
      • 使用core文件调试代码:在Linux环境下,进入使用gdb调试代码,在gdb内部,使用core-file [core文件名],就会直接定位到引发core文件产生的对应行。
      • 2、9号信号不会产生core文件。
  • 使用kill命令

  • 通过系统调用接口给特定进程发送信号

    • #include<signal.h>
      int kill(pid_t pid, int signo);
      //向特定进程发送特定信号;成功返回0;失败返回-1
      
    • #include<signal.h>
      int raise(int signo);
      //向当前进程发送特定信号;成功返回0;失败返回-1
      
    • #include<stdlib.h>
      void abort(void);
      //使当前进程收到信号而异常终止;就像exit()函数一样,abort()函数总是会成功的,所以没有返回值
      
  • 由软件条件发送信号

    • SIGPIPE:SIGPIPE是一种由软件条件产生的信号,当一个管道的读端被关闭时,这时候操作系统就会检测到该管道中写入的数据不会在有人来管道内读文件了,操作系统会认为该管道的存在会造成内存资源的极大浪费,则操作系统就会向写端对应的目标进程发送SIGPIPE信号。

    • #include<unistd.h>
      unsigned int alarm(unsigned int seconds);
      //调用alarm函数可以对当前进程设置一个闹钟,也就是告诉操作系统在seconds秒之后对当前进程发送SIGALRM信号,该信号的默认处理动作是终止当前进程。
      
      • 返回值:该函数的返回值是0或者是以前设定的闹钟时间还剩余的秒数。如果second的值为0,则表示取消以前设定的闹钟。

信号的在内核中的表示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lyWtWQ2r-1608611148071)(https://s1.ax1x.com/2018/11/19/FSqxRH.png)]

进程的PCB包含了进程的一切信息,所以它理所应当的要保存进程的信号信息。操作系统使用了两张位图&一张函数指针数组表来控制进程的信号

  • 实际执行信号的处理动作称为信号递达。
  • 信号从产生到递达之间的状态,称为信号未决。
  • 被阻塞的信号产生时将保存在未决状态,直到进程解除对该信号的阻塞,才能执行递达的动作。
  • 阻塞和忽略不同,只要信号被阻塞就不会递达;而忽略是在递达之后,可选的一种处理动作。

pending(未决信号表)

  • pending表用来表示进程收到的未决信号。当进程收到一个信号时,就会将该进程PCB中的pending表中对应的表示该信号的位图置为1。当信号被递达时,对应位置会被置为0。
    • 常规信号在递达之前产生多次只记一次,而实时信号在递达之前产生多次可以依次放在一个队列里

block(阻塞信号表)

  • block表用来表示进程的哪些信号被阻塞。当进程的PCB中的block表中的一个信号对应的位图被置为1时,代表该进程阻塞了对应的信号,即当该进程收到对应信号时,对应信号的pending位一直为1,无法被递达。

hander(函数指针数组)

  • 该表对应了相应信号的处理方式,保存了处理信号方式的函数指针。该处保存的是信号的默认处理动作还是用户自定义的处理动作,忽略信号如何定义处理动作?

用户自定义进程收到信号后的处理动作

sigset_t

当需要得知进程的pending表或block表中的信息时,需要定义一个特有的结构来存储进程中的信号信息。sigset_t是专门用来存储进程信号信息的结构,虽然进程中的保存信号的方式是以位图来保存,但这里禁止直接使用位操作来操作sigset_t的数据,必须通过特定的函数接口堆sigset_t进行操作。

  • 当使用sigset_t结构来接收进程的block信号集时,通常称为进程的信号屏蔽字。
  • 当使用sigset_t结构来接收进程的pending信号集时,通常称为进程的pending信号集。

信号集操作函数

#include<signal.h>

int sigemptyset(sigset_t *set);
//初始化set所指向的信号集,使其中所有信号对应的比特位清零,表示该信号集不包含任何信号

int sigfillset(sigset_t *set);
//初始化set所指向的信号集,将其中所有信号对应的比特位置1,表示该信号集的有效信号包括系统支持的所有信号

int sigaddset(sigset_t *set, int signo);
//表示将set所指向的信号集中的signo信号置1

int sigdelset(sigset_t *set, int signo);
//表示将set所指向的信号集中的signo信号清零

int sigismember(const sigset_t *set, int signo);
//用来判断set所指向的信号集的有效信号中是否包含signo信号,包含返回1,不包含返回0,出错返回-1
  • 注意:在使用sigset_t类型的变量前,一定要调用sigemptyset或sigfillset进行初始化,使信号集处于某种确定的状态,初始化之后就可以调用sigaddset或sigdelset在信号集中添加或删除某种有效信号。

设置\修改进程的信号屏蔽字(block表)

#include<signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
  • int how:
    • SIG_BLOCK:set包含了用户希望添加到当前信号屏蔽字的信号,即就是在老的信号屏蔽字中添加上新的信号。相当于:mask=mask|set
    • SIG_UNBLOCK:set包含了用户希望从当前信号屏蔽字中解除阻塞的信号,即就是在老的信号屏蔽字中取消set表中的信号。相当于:mask=mask&~set
    • SIG_SETMASK:设置当前进程的信号屏蔽字为set所指向的信号集。相当于:mask=set
  • const sigset_t *set:将要设置为进程block表的信号集。
  • sigset_t *oset:用来保存进程旧的block表。
    • 若无需保存进程旧的block表,传递空指针即可。

获取进程的pending信号集

#include<signal.h>

int sigpending(sigset_t *set);
  • 成功返回0;失败返回-1

修改进程收到信号的处理动作

进程收到信号的处理方式
  • 忽略
    • SIG_IGN:进程收到信号忽略的处理方式。
  • 默认处理方式
    • SIG_DFL:进程收到信号的默认处理方式。
  • 自定义处理方式
sigaction
#include<signal.h>

struct sigaction
{
    void (*sa_handler)(int);//指向信号处理对应的函数
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;//当在处理所收到信号时,想要附带屏蔽的其他普通信号,当不需要屏蔽其他信号时,需要使用sigemptyset初始化sa_mask
    int sa_flags;
    void (*sa_restorer)(void);
};

int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
  • int signo:指定的信号编号。
  • const struct sigaction *act:若该act指针非空,则根据act指针来修改进程收到signo信号的处理动作。
  • struct sigaction *oact:若oact指针非空,则使用oact来保存信号旧的处理动作。

信号的捕捉

FSLGYF.png

处理信号的时机

进程收到一个信号时,并不会立即就去处理这个信号,而是先将收到的信号保存下来,并在合适的时候对信号进行处理,操作系统会在进程进入了内核态并从内核态返回用户态时,检测进程中可以进行处理的信号,并进行处理

用户写好的代码会在什么情况下进入内核态呢?
  • 调用系统调用接口
  • 异常
  • 中断

SIGCHLD信号

使用fork函数创建出一个子进程,当子进程退出时会向父进程发送一个SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理方式。

代码验证子进程退出时,向父进程发送SIGCHLD信号
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/types.h>

//在屏幕模拟打印出进程的pending表
void showpending(const sigset_t* set)
{
    size_t i = 0;
    for(i = 0; i < 32; ++i)
    {
        if(sigismember(set, i))
            printf("1");
        else
            printf("0");
    }
    printf("\n");
}

int main()
{
    pid_t id = fork();
    if(-1 == id)//创建子进程失败
        exit(-1);
    else if(0 == id)//子进程
    {
        int count = 5;
        while(count--)//子进程约五秒后退出
        {
            printf("i am child...\n");
            sleep(1);
        }
        printf("i quit...\n");
    }
    else
    {
 		//因为子进程收到SIGCHLD信号的默认处理方式是忽略,所以我们需要屏蔽SIGCHLD信号,使父进程收到SIGCHLD信号之后,SIGCHLD信号会一直保存在父进程的pending表中
        sigset_t set;//用来接受父进程的pending表
        sigset_t newblock;//用来屏蔽父进程的SIGCHLD信号
        sigemptyset(&set);
        sigemptyset(&newblock);
        sigaddset(&newblock, SIGCHLD);
        sigprocmask(SIG_SETMASK, &newblock, NULL);
        while(1)
        {
            sigpending(&set);
            showpending(&set);
            sleep(1);
        }
    }
}

//该程序仅供测试使用,父进程未进行回收子进程资源

运行结果:F9JMpq.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值