进程--信号

目录

什么是信号

信号的种类

信号处理流程

信号的发送

kill

raise

等待信号

信号处理

用户自定义处理

信号定时器

子进程退出信号


什么是信号

信号是在软件层面上是一种通知机制,对中断机制的一种模拟,是一种异步通信方式。一般具有如下特点:
进程在运行过程中,随时可能被各种信号打断
进程可以忽略或者去调用相应的函数去处理信号
进程无法预测信号到达的精准时间

在 Linux 中信号一般的来源如下:
程序执行错误,如内存访问越界,数学运算除0
由其他进程发送
通过控制终端发送,如ctrl + c
子进程结束时向父进程发送的SIGCHLD信号
程序中设定的定时器产生的SIGALRM信号

信号的种类

在Linux系统中 kill -l 可查看

SIGINT 该信号在用户键入INTR字符(通常是Ctrl-C)时发出,终端驱动程序发送此信号并送到前台进
程中的每一个进程。
SIGQUIT 该信号和SIGINT类似,但由QUIT字符(通常是Ctrl-)来控制。
SIGILL 该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误,或者试图执行数据段、
堆栈溢出时)发出。
SIGFPE 该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数为0
等其它所有的算术的错误。
SIGKILL 该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略。
SIGALRM 该信号当一个定时器到达的时候发出。
SIGSTOP 该信号用于暂停一个进程,且不能被阻塞、处理或忽略。
SIGTSTP 该信号用于交互停止进程,用户可键入SUSP字符时(通常是Ctrl-Z)发出这个信号。
SIGCHLD 子进程改变状态时,父进程会收到这个信号
SIGABRT 进程异常中止

信号处理流程

信号处理流程包含以下两个方面:
信号的发送 :可以由进程直接发送
信号投递与处理 : 由内核进行投递给具体的进程并处理

在 Linux 中对信号的处理方式如下
忽略信号,即对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
捕捉信号,定义信号处理函数,当信号发生时,执行相应的处理函数。
执行缺省操作,Linux对每种信号都规定了默认操作

信号的发送

当由进程来发送信号时,则可以调用 kill() 函数与 raise () 函数

kill

函数头文件
#include <sys/types.h>
#include <signal.h>

函数原型
int kill(pid_t pid, int sig);
函数功能
向指定的进程发送一个信号
函数参数
pid:进程的id
sig:信号的id

raise

函数头文件
#include <sys/types.h>
#include <signal.h>

函数原型
int raise(int sig);
函数功能
send a signal to the caller
函数参数
sig:信号编号

代码示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
int main()
{
        pid_t pid = fork();
        if(pid == -1)
       {
                perror("fork");
                exit(EXIT_FAILURE);
       }
        else if(pid==0)
       {
                // 执行子进程
                fprintf(stdout,"child <%d> is running.\n",getpid());
                // SIGSTOP 暂停一个进程,不能阻塞、处理或忽略,只能采用默认处理
                raise(SIGSTOP);
                fprintf(stdout,"child <%d> is exit.\n",getpid());
                // 成功退出子进程
                exit(EXIT_SUCCESS);
       }
        else
       {
                int ret;
                sleep(1);
                ret = kill(pid,SIGKILL);//给指定的进程发送kill信号,只能采用默认操
作,结束进程
                if(ret == 0)
               {
                        fprintf(stdout,"parent<%d> kill 
child<%d>.\n",getpid(),pid);
               }
                waitpid(pid,NULL,0);
                fprintf(stdout,"father<%d> exit.\n",getpid());
                exit(EXIT_SUCCESS);
       }
        return 0;
}

等待信号

在进程没有结束时,进程在任何时间点都可以接受到信号
需要阻塞等待信号时,则可以调用 pause() 函数

函数头文件
#include <unistd.h>
函数原型
int pause(void);

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
int main()
{
        pid_t pid = fork();
        if(pid == -1)
       {
                perror("fork");
                exit(EXIT_FAILURE);
       }
        else if(pid==0)
       {
                // 执行子进程
                fprintf(stdout,"child <%d> is running.\n",getpid());
                //子进程向父进程发送SIGUSR1信号,SIGUSR1信号的默认处理是终止进程
                sleep(2);
                kill(getppid(),SIGUSR1);
                fprintf(stdout,"child <%d> is exit.\n",getpid());
                // 成功退出子进程
                exit(EXIT_SUCCESS);
       }
        else
       {
                int ret;
                sleep(1);
                pause();//等待信号,让进程阻塞直到收到信号
                fprintf(stdout,"father<%d> exit.\n",getpid());
                exit(EXIT_SUCCESS);
       }
        return 0;
}

提醒:pause函数一定要在收到信号之前调用,让进程进入到睡眠状态

信号处理

信号是由操作系统内核发送给指定进程,进程收到信号后则需要进行处理
处理信号有三种方式:
忽略 : 不进行处理
默认 : 按照信号的默认方式处理
用户自定义 : 通过用户实现自定义处理函数来处理,由内核来进行调用
每种信号都有相应的默认处理方式:

进程退出
SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
进程忽略
SIGCHLD,SIGPWR,SIGURG,SIGWINCH
进程暂停
SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU

用户自定义处理

当有信号时,我们可以自由设定进程该怎样处理

1. 实现自定义处理函数
用户实现自定义处理函数,需要按照下面的格式

typedef void (*sighandler_t)(int);
typedef void (*)(int) sighandler_t

类似与这样的一个函数

2. 设置信号处理处理方式
通过 signal 函数设置信号处理方式

函数头文件 
#include <signal.h>
函数原型 
sighandler_t signal(int signum, sighandler_t handler);
函数功能 
设置信号的处理方式,如果是自定义处理方式,提供函数地址,注册到内核中

函数参数 
signum:信号编号
handler:信号处理方式
 SIG_IGN---->忽略信号
 SIG_DFL---->按照默认方式处理
  自定义处理函数的地址

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
// 自定义处理函数
void signal_handler(int signum)
{
        printf("%s\n",strsignal(signum));
}
int main()
{
        // 注册处理函数的地址到内核
        if(signal(SIGUSR1,signal_handler)==SIG_ERR)
       {
                perror("signal error.");
                exit(EXIT_FAILURE);
       }
        pid_t pid = fork();
        if(pid==-1)
       {
                perror("fork");
                 exit(EXIT_FAILURE);
       }
        else if(pid == 0)
       {
                printf("child process <%d> is running...\n",getpid());
                pause();// 等待信号唤醒
                printf("child process exit\n");
                exit(EXIT_SUCCESS);
       }
        else if(pid > 0)
       {
                sleep(1);
                int ret = kill(pid,SIGUSR1);
                if(ret == 0)
               {
                        printf("kill signum<%d> to process<%d> is 
successful.\n",SIGUSR1,pid);
               }
                waitpid(pid,NULL,0);
       }
        return 0;
}

信号定时器

有时信号间处理太快,不是我们想要的结果

这时Linux系统中提供了 alarm 函数,用于设置定时器

函数原型 
unsigned int alarm(unsigned int seconds);

seconds:定时的时间秒数

函数返回值 
返回上一次进程设置定时器剩余的秒数,如果进程上一次没有设置定时器,则返回0

定时器的定时任务由内核完成的,alarm 函数负责设置定时时间,并告诉内核启动定时器
当定时时间超时后,内核会向进程发出 SIGALRM 信号

设置定时器的定时时间为 3s ,并处理 SIGALRM 信号

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
void sigalrm_handler(int signum)
{
        printf("sigalrm:%s\n",strsignal(signum));       
}
int main()
{
        unsigned int ret;
        // 注册信号处理函数
        if(signal(SIGALRM,sigalrm_handler) == SIG_ERR)
       {
                perror("signal:");
                exit(EXIT_FAILURE);
       }
        ret = alarm(3);
        pause();
        printf("main process end....\n");
        return 0;
}

子进程退出信号

在使用 wait() 函数时,由于阻塞或者非阻塞都非常消耗资源。并且在阻塞情况下,父进程不能执行其他逻辑。
如何解决?
子进程退出是异步事件,可以利用在子进程退出时,会自动给父进程发送 SIGCHLD 信号

示例代码:使用信号处理僵死态进程

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
void sigchld_handler(int signum)
{
        printf("SIGCHLD handler:<%s>\n",strsignal(signum));
        int status=0;
        wait(&status);
}
int main()
{
        pid_t pid;
        if(signal(SIGCHLD,sigchld_handler)==SIG_ERR)
       {
                perror("signal failed.");
                exit(EXIT_FAILURE);
       }
        pid = fork();
        if(pid == -1)
       {
                perror("fork");
                exit(EXIT_FAILURE);
       }
        else if(pid == 0)
       {
                printf("child process<%d> is running.....\n",getpid());
                sleep(2);
                exit(EXIT_SUCCESS);
       }
        else
       {
                while(1)
               {
               }
       }
        return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值