Linux进程信号
信号概念
信号:是进程之间事件异步通知的一种方式
信号是一个软件中断,通知进程发生了某件事情,中断进程
当前操作,让进程去处理信号事件
查看信号
kill -l //查看系统定义的信号列表
其中:
[1-31]信号:非可靠信号(常规信号),易丢失事件,只注册一次,多次注册时会造成信号丢失
[31-64]信号:可靠信号(实时信号),不会丢失信号
信号的生命周期
信号的产生–>在进程中注册–>在进程中注销–>信号的处理
硬件产生:ctrl+c ctrl+| ctrl+z
软件产生:kill -signum -p pid kill(pid,signum) raise(signum) abort() alarm(nsec)
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
int main()
{
//int kill(pid_t pid, int sig);
//向pid进程发送sig信号
kill(getpid(), SIGKILL);
//
//int raise(int sig);
//向自己发送sig信号
raise(SIGTERM);
//
//void abort(void);
//给自己发送SIGABRT信号(异常终止的信号)
abort();
//
//unsigned int alarm(unsigned int seconds);
//经过seconds秒之后,给自己发送一个SIGALRM信号(终止当前进程的信号)---定时器
//seconds==0时,表示取消上一个时间未到的定时器
alarm(3);
alarm(0);
while(1) {
printf("nihao~~~\n");
sleep(1);
}
char *ptr = NULL;
memcpy(ptr, "nihao", 5);
return 0;
}
信号处理动作的替换:
信号的处理动作的修改:signal 和 sigaction函数
而signal实质是去调用sigaction函数的
故我们常用sigaction函数来修改信号的处理动作
//typedef void (*sighandler_t)(int):
//它定义了一个类型sighandler_t,表示指向返回值为void型(参数为int型)的函数(的)指针。它可以用来声明一个或多个函数指针。
//在这里,typedef void (*sighandler_t)(int) 也可以写成void (*sighandler_t)(int),typedef 在语句中所起的作用不过就是把语句原先定义变量的功能变成了定义类型的功能而已。
//在这里,typedef的作用是定义变量的类型(函数指针)
//typedef有两种用法:(1)typedef int DataType; 表示的是给int重新取个名字叫 DataType
// (2)typedef long count; 表示的是定义的count变量的类型是long
// typedef void (*sighandler_t)(int);表示的是定义一个 sighandler_t 变量,其类型为函数指针
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
//信号的处理动作的修改:signal 和 sigaction函数
// 而signal实质是去调用sigaction函数的,故我们要学会用sigaction函数来修改信号的处理动作
//(1)signal:
//替换可以设置为三种处理动作: SIG_DFL(默认处理动作)
// SIG_TGN(忽略处理动作)
// User Space: void sighandler(int signo);
// signal(signo, SIG_DFL);//将自定义处理动作(函数)恢复默认处理动作
// signal(SIGINT, sigcb);//后面的参数是处理动作的替换
//(2)sigaction:
// struct sigaction结构体中的成员变量:
// struct sigaction new_act;//将new_act如下这样设置(设置其中三个成员变量)
// new_act.sa_flags = 0;
// new_act.sa_handler = signcb;//将处理方式修改为自定义的sigcb函数
// sigemptyset(&new_act.sa_mask); //清空new_act结构体中的mask集合(new_act.sa_mask成员类型是sigset_t)
// struct sigaction old_act;//这个只需要定义出来,不需要做任何处理,即保存默认的处理方式
// sigaction(2, &new_act, &old_act);用new_act替换2号信号的处理动作,将原有动作保存到old_act中
void sigcb(int signo)
{
printf("recv a signo:%d\n", signo);
signal(signo, SIG_DFL);//将自定义处理动作(函数)恢复默认处理动作
}
int main()
{
//signal(SIGINT, SIG_IGN);
//signal(SIGINT, sigcb);//后面的参数是处理动作的替换
struct sigaction new_act, old_act;//new_act和old_act的类型是struct sigaction 结构体类型,传参传的是它们的地址
new_act.sa_flags = 0;
new_act.sa_handler = sigcb;
//int sigemptyset(sigset_t *set);//传参传的是sigset_t类型的指针
//清空set信号集合
sigemptyset(&new_act.sa_mask);
//sigaction:作用是用new_act替换2号信号的处理动作,将原有动作保存到old_act中
sigaction(2, &new_act, &old_act);//old_act里面一般情况下都保存的是默认的处理动作(SIG_DFL)
//每个信号有它自带的处理动作(默认),此时放于old_act中
while(1) {
printf("xiuxiyihui~~\n");
sleep(10);
}
return 0;
}
使用 sigaction 函数:
signal 函数的使用方法简单,但并不属于 POSIX 标准,在各类 UNIX 平台上的实现不尽相同,因此其用途受到了一定的限制。而 POSIX 标准定义的信号处理接口是 sigaction 函数,其接口头文件及原型如下:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
◆ signum:要操作的信号。
◆ act:要设置的对信号的新处理方式。
◆ oldact:原来对信号的处理方式。
◆ 返回值:0 表示成功,-1 表示有错误发生。
struct sigaction 类型用来描述对信号的处理,定义如下:
struct sigaction
{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
在这个结构体中,
sa_handler 成员是一个函数指针,其含义与 signal 函数中的信号处理函数类似。
sa_sigaction 成员则是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。当 sa_flags 成员的值包含了SA_SIGINFO 标志时,系统将使用 sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理函数。在某些系统中,成员 sa_handler 与sa_sigaction 被放在联合体中,因此使用时不要同时设置。
sa_mask 成员用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。
sa_flags 成员用于指定信号处理的行为,它可以是以下值的“按位或”组合:
◆ SA_RESTART:使被信号打断的系统调用自动重新发起。
◆ SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
◆ SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。
◆ SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
◆ SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
◆ SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。
re_restorer 成员则是一个已经废弃的数据域,不要使用。
信号的阻塞:
组织信号被递达—>信号依旧可以注册,只是暂时不处理
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
//sigset_t:
//未决和阻塞标志可以用相同的数据类型sigset_t来存储,
//sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,
//在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,
//而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。
//sigset_t mask:表示默认的阻塞集合
//对于阻塞而言:(1)先定义好set信号集合 :sigset_t set; sigset_t oldset;
// (2)然后清空set集合中的信号:sigemptyset(&set); //注意是传地址
// (3)然后将(所有)信号添加到set集合中去:sigfillset(&set); //注意是传地址
// sigemptyset(sigset_t *set); //清空set信号集中的所有信号
// sigfillset(sigset_t *set); //将所有的信号赋给set信号集
// sigaddset(sigset_t *set, int signo); //将signo信号加入到set信号集中
// sigdelset(sigset_t *set, int signo); //将set信号集中的signo信号移除
// sigismember(const sigset_t *set, int signo); //判断signo信号是否在set信号集中
// (4)然后给阻塞信号集合中(默认是mask)添加set信号集合中的信号:sigpromask(SIG_BLOCK,&set,&oldset);
// getchar();输入回车,对所有信号解除阻塞
// sigprocmask(SIG_UNBLOCK,&set,NULL);//从阻塞集合中移除set集合中的信号
// sigprocmask(SIG_SETMASK,&oldset,NULL);//将oldset中保存的修改前阻塞集合中的信号 又给人家设置回去
void sigcb(int signo) {
printf("recv signo:%d\n", signo);
}
int main()
{
signal(SIGINT, sigcb);
signal(40, sigcb);
//int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);//默认阻塞集合是block=mask,然后调用sigprocmask修改默认阻塞集合
//当第一个参数是SIG_BLOCK时,向阻塞集合中加入set集合中的信号,block=mask|set oldset:保存修改前阻塞集合中的信号(mask)
//当第一个参数是SIG_UNBLOCK时,从阻塞集合中移除set集合中的信号,block=mask&(~set) oldset:传NULL即可
//当第一个参数是SIG_SETMASK时,向阻塞集合中加入set集合中的信号,block=set oldset:传NULL即可
sigset_t set, oldset;//set和oldset的类型是sigset_t类型,传参的时候传的是它们的地址
sigemptyset(&set);
//int sigfillset(sigset_t *set);//向set集合中添加所有信号
//int sigaddset(sigset_t *set, int signum);//向set集合中添加signum信号
sigfillset(&set);//向set集合中添加所有信号
sigprocmask(SIG_BLOCK, &set, &oldset);//阻塞set中的信号,oldset:保存修改前阻塞集合中的信号(mask)
getchar();//输入回车,对所有信号解除阻塞
sigprocmask(SIG_UNBLOCK, &set, NULL);//从阻塞集合中移除set集合中的信号
//sigprocmask(SIG_SETMASK, &oldset, NULL);//将oldset中保存的修改前阻塞集合中的信号(mask) 又给人家设置回去
return 0;
}
注意:在所有的信号中,9号信号SIGKILL和19号信号SIGSTOP,无法被阻塞,无法被忽略,无法被自定义 [ 即无法被修改 ]
可重入函数
函数的重入:多个执行流程同时进入某个相同的函数
- 可重入:多个执行流同时执行进入相同的函数,不会造成数据二义性以及代码混乱
- 不可重入:多个执行流同时执行进入相同的函数,有可能造成数据二义性以及代码混乱
当用户设计了一个函数或者使用一个函数的时候在多个执行流中,那么这时候就需要考虑函数是否可重入
函数是否可重入的关键点:
在于这个函数中是否对临界资源(全局数据)进行了非原子性操作
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int x = 1;
int y = 1;
int sum(int *a, int *b)//不能保证原子性
{
(*a)++;
sleep(3);
(*b)++;
return (*a + *b);
}
void sigcb(int no)//用户自定义的信号处理动作函数中,有调用了一次sum函数,即信号处理时又进行了了一次(*a)++和(*b)++,导致结果与实际想要的不符
{
printf("signal------%d\n", sum(&x, &y));
}
int main()
{
signal(SIGINT, sigcb);//将ctrl+c命令信号的处理动作修改为自定义处理动作
printf("main------%d\n", sum(&x, &y));//3s内按下ctrl+c命令后结果与不按不一样
return 0;
}
从结果可以看出,按ctrl+c命令后main中sum函数的计算结果发生了改变。