IPC:信号

使用场景:1、为了并发,中断处理其它事件,1、进程间通信
1、中断
中止(注意不是终止)当前正在执行的程序,
转而执行其它任务。

硬件中断:来自硬件设备的中断。
软件中断:来自其它程序的中断。

2、信号是一种软件中断
信号提供了一种以异步方式执行任务的机制。

3、常见的信号
SIGHUP(1):连接断开信号
如果终端接口检测一个连接断开,则将此信号发送给与该终端相关的控制进程。
SIGINT :Ctrl+C
SIGQUIT: Ctrl+
SIGKILL:kill -9
SIGTERM: kill
SIGCHLD:

4、不可靠信号(又叫非实时信号)
1、那些建立在早期机制伤的信号被称为“不可靠信号”。
小于SIGRTMIN(34)的信号都是不可靠信号。
2、不支持排队,可能会丢失。同一个信号产生多次,
进程可能只收到一次该信号。
3、进程每次处理完这些信号后,对相应信号的响应被自动恢复为默认动作,
除非显式地通过signal函数重新设置一次信号处理机制。

5、可靠信号(实时信号,排队信号)
2、支持排队,不会丢失
3、无论可靠信号还是不可靠信号
都可以通过sigqueue/qigaction函数发送/安装
以获得比其早期版本kill/signal函数更可靠的使用效果

6、信号的来源
1、硬件异常:除0、无效的内存访问等。
这些异常通常被硬件(驱动)检测到,并通知系统内核。
系统内核再向引发这些异常的进程递送相应的信号。
2、软件异常:通过kill/raise/alarm/setitimer/sigqueue函数产生的信号。

7、信号处理
1、忽略
2、终止进程。
3、终止进程同时产生core文件
4、捕获并处理。当信号发生时,内核会调用一个事先注册好的用户函数(信号处理函数)

二、signal
|#include
typedef void (*sighandler_t)(int);
sighandler_t signal (int signum,
sighandler_t handler);

signum - 信号码,也可以使用系统预定义的常量宏,如SIGINT等。
handler - 信号处理函数指针或以下常量;
SIG_IGN:忽略该信号;
SIG_DFL:默认处理;
成功返回原来的信号处理函数指针或SIG_IGN/SIG_DFL常量,失败返回SIG_ERR。

1、在某些unix系统上,
通过signal函数注册的信号处理函数只一次有效,
即内核每次调用该信号处理函数前,
会将对该信号的处理自动恢复为默认方式。
为了获得更持久有效的信号处理,可以在信号处理函数中再次调用signal函数,
重新注册一次。
例如:
void sigint (int signum){
...
signal(SIGINT, sigint);
}

int main(void){
...
signal(SIGINT, sigint);
}

三、子进程的信号处理
1、子进程会继承父进程的信号处理方式,只到子进程调用exec函数。
2、子进程调用exec函数后
exec函数将被父进程设置为捕获的信号恢复至默认处理,其余保持不变。

四、发送信号
1、键盘
Ctrl+C SIGINT 终端中断
Ctrl+\ SIGQUIT 终端退出
Ctrl+Z SIGTSTP 终端暂停
2、错误
除0 SIGFPE 算术异常
非法内存访问 SIGSEGV 段错误
硬件故障 SIGBUS 总线错误
3、命令
kill -信号 进程号
4、函数
|#include
int kill(pid_t pid, int sig) //不等待信号处理完就返回,异步处理
成功返回0 ,失败返回-1
pid >0 -向pid进程发送sig信号
pid=0 - 向同进程组的所有进程发送信号
pid = -1 -向所有进程发送信号,前提是有权限
pid < -1 -向绝对值等于pid的进程组发送信号
0信号为空信号。
若sig取0,则kill函数仍会执行错误检查,但并不实际发送信号。常被用来确定一个
进程是否存在。
向一个不存在的进程发送信号,会返回-1,且errno为ESRCH。

     int raise (int sig)
     向调用进程自身发送sig信号。成功返回0,失败返回-1。kill函数也可以实现此功能
     |#include 
     |#include 
     |#include 
     |#include 
     void sigint (int signum){
          printf("%u进程,永别了!\n", getpid());
          exit(0);
          //raise(signum);   这里不是递归调用,raise返回,sigint也返回,函数栈并不一致增长,会有波动,所以程序不会爆掉。
     }
     int main(void){
          if(signal(SIGINT, sigint) == SIG_ERR){
                perror("signal");
                return -1;
          }
          printf("%u进程,我要自杀。。。\n", getpid());
          if(raise(SIGINT) == -1){
               perror("raise");
               return -1;
           };
     }

五、pause 暂停函数
int pause(void);
1、使调用进程进入睡眠状态,不再占有cpu,直到有信号终止该进程或被捕获。
2、只有调用了信号处理函数并从中返回以后,该函数才会返回-1;
3、该函数要么不返回(未捕获到信号),要么返回-1(被信号中断),errno为EINTR。
4、相当于没有时间限制的sleep函数。
|#include
|#include
|#include
|#include
void sigint (int signum){
printf("%u进程,收到SIGINT信号!\n", getpid());
}
int main(void){
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if (pid == 0)
{
if (signal(SIGINT, sigint) == -1){ //没信号不阻塞
perror("signal");
return -1;
}
printf("%u进程:我是子进程 , 大睡ing....\n",getpid());
pause();
printf("%u进程:我是子进程 , 我被唤醒了......\n",getpid());
return 0 ;
}
sleep(1);
printf("%u进程:我是父进程 , 向%u发送信号......\n",getpid(), pid);
if(kill(pid, SIGINT) == -1){
perror("kill");
return -1;
}
if((pid = wait(0) == -1){ //回收僵尸,子进程退出,wait返回
perror("wait");
return -1;
}
printf("%u进程:%u进程已退出。\n", getpid(), pid);
return 0;
}


IPC:信号_第1张图片
图片发自App

六、sleep 跟pause函数功能差不多
|#include
unsigned int sleep(unsigned int seconds);
1、使调用进程睡眠seconds秒,除非有信号终止该进程或被捕获。
2、只有睡够seconds秒,或调用了信号处理函数并从中返回以后,该函数才会返回。
3、该函数要么返回0(睡够),
要么返回剩余秒数(被信号中断)。
4、相当于有时间限制的pause函数。
|#include
|#include
|#include
|#include
void sigint (int signum){
printf("%u进程,收到SIGINT信号!\n", getpid());
}
int main(void){
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if (pid == 0)
{
if (signal(SIGINT, sigint) == -1){ //没信号不阻塞
perror("signal");
return -1;
}
printf("%u进程:我是子进程 , 小睡10秒....\n",getpid());
unsigned int left = sleep(10);
printf("%u进程:我是子进程 , 还剩%u秒没睡,即将退出......\n",getpid(),left);
return 0 ;
}
sleep(3);
printf("%u进程:我是父进程 , 向%u发送信号......\n",getpid(), pid);
if(kill(pid, SIGINT) == -1){
perror("kill");
return -1;
}
if((pid = wait(0) == -1){ //回收僵尸,子进程退出,wait返回
perror("wait");
return -1;
}
printf("%u进程:%u进程已退出。\n", getpid(), pid);
return 0;
}

七、usleep 以微秒为单位
int usleep (useconds_t usec);
使调用进程睡眠usec微秒,除非有信号终止该进程或被捕获。
成功返回0,失败返回-1.

七、alarm 等于设置一个定时器
|#include
unsigned int alarm(unsigned int seconds);
1、使内核在seconds秒之后,向调用进程发送SIGALRM(14)闹钟信号。
2、SIGALRM信号的默认处理是终止进程。
3、若之前已设过定时且尚未超时,则调用该函数会重新设置定时,并返回之前定时的剩余时间。
4、seconds取0表示取消之前设过且尚未超时的定时。

|#include
|#include
|#incude
void sigalrm(int signum){
time_t t = time(NULL);
struct tm* lt = localtime(&t);
printf("\r%02d:%02d:%02d", lt->tm_hour, lt->tm_min, lt->tm_sec);
//'\r' 回车,回到当前行的行首,而不会换到下一行,如果接着输出的话,本行以前的内容会被逐一覆盖;
alarm(1);
}

int main(void){
setbuf(stdout, 0);
if(signal(SIGALRM,sigalrm) == -1){
perror("signal");
return 0;
}
sigalrm(SIGALRM);
FOR (;;){
}
return 0;
}

结果:每秒每秒更新时间

unsigned int remain = alarm(10);
sleep(2);
remain = alarm(5); //此时remain = 8
remain = alarm(0); //取消定时器

八、信号集与信号阻塞
1、信号集
1)多个信号的捷类型:
sigset_t,128个二进制位(实际预编译查看是128字节),每个位代表一个信号。
2)相关函数
|#include
//将信号集中的全部信号位置1
int sigfillset(sigset_t* set);
//将信号集中的全部信号位清0
int sigemptyset(sigset_t* set);
//将信号集set中的signum信号对应位置1
int sigaddset(sigset_t* set, int signum);
//将信号集set中的signum信号对应位清0
int sigdelset(sigset_t* set, int signum);
成功返回0,失败返回-1

         //判断信号集set中与signum对应的位是否为1,是1则返回1,否则返回0
         int sigismember(const sigset_t* set, int signum);
   |#include 
   |#include 
   int main(viod){
       sigset_t set;    //预编译后看到是一个unsigned long int的32个元素数组128个字节
       print("%u\n", sizeof(set));
       sigfillset(&set);
       sigemptyset(&set);
       sigaddset(&set, SIGINT);
       sigdelset(&set, SIGINT);
       if(sigismember(&set, SIGINT))
            printf("有\n");
        else
            printf("没有\n");
       return 0;
   }

2、信号屏蔽(信号阻塞)
1)、当信号产生时,系统内核会在其所维护的进程表中,为特定的进程设置一个
与该信号相对应的标志位,这个过程称为递送。
2)、信号从产生到完成递送之间存在一定的时间间隔。
处于这段时间间隔中的信号状态,称为未决。
3)、每一个进程都有一个信号掩码(signal mask)
它实际上是一个信号集,
其中包括了所有需要被屏蔽的信号。 所以这些信号就变成了未决状态的信号了。
4)、可以通过sigprocmask函数,
检测和修改调用进程的信号掩码。
也可以通过sigpending函数,
获取调用进程当前处于未决状态的信号集。
5)、当进程执行诸如更新数据库等敏感任务时,
可能不希望被某些信号中断。
这时可以暂时屏蔽(注意不是忽略)这些信号,
使其停留在未决状态,
待任务完成以后,再回过头来处理这些信号。
6)、在信号处理函数的执行过程中,
这个正在被处理的信号总是处于信号掩码中。
这就避免了同样的信号同时再调用同一个函数。
|#include

int sigprocmask(int how, const sigset_t* set, sigset_t* oldset);
成功返回0 ,失败返回-1

how - 修改信号掩码的方式,可取以下值
SIG_BLOCK: 新掩码是当前掩码和set的并集(将set加入信号掩码)
SIG_UNBLOCK: 新掩码是当前掩码和set的补集的交集(将set从信号掩码移除)
SIG_SETMASK: 新掩码是set(将信号掩码设为set)

set: NULL则忽略
oldset:备份以前的信号掩码, NULL则不备份

int sigpending(sigset_t set);
set - 输出,调用进程当前处于未决状态的信号集。
成功返回0 ,失败返回-1

注意:对于不可靠信号,
通过sigprocmask函数设置信号掩码以后,
相同的被屏蔽信号只会屏蔽第一个,
并在恢复信号掩码后被递送,其余的则直接忽略掉。
而对于可靠信号,
则会在信号屏蔽时按其产生的先后顺序排队,
一旦恢复信号掩码,这些信号会依次被信号处理函数处理。

|#include
|#include
|#include
|#include
void sighand(int signum){
printf("%u进程:收到%d信号!\n", getpid(), signum);
}
void updatedb (void){
int i;
for(i=0;i<5;++i){
printf("%u进程:更新第%d记录!\n", getpid(), i+1);
sleep(1);
}
}
int main(void){
if(signal(SIGINT, sighand) == SIG_ERR){
perror("signal");
return -1;
}
if(signal(SIGINT, sighand) == SIG_ERR){
perror("signal");
return -1;
}
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){
pid_t pid = getppid();
int i ;
for(i=0; i<5; ++i){
printf("%u进程:向%u进程发送%d信号... \n", getpid(), pid, 50);
kill(pid, 50);
}
return 0;
}
updatedb();
return 0;
}

更新数据库操作频繁被打断,如下图。


IPC:信号_第2张图片
图片发自App

做一个修正。

|#include
|#include
|#include
|#include
void sighand(int signum){
printf("%u进程:收到%d信号!\n", getpid(), signum);
}
void updatedb (void){
int i;
for(i=0;i<5;++i){
printf("%u进程:更新第%d记录!\n", getpid(), i+1);
sleep(1);
}
}
int main(void){
if(signal(SIGINT, sighand) == SIG_ERR){
perror("signal");
return -1;
}
if(signal(SIGINT, sighand) == SIG_ERR){
perror("signal");
return -1;
}
printf("%u进程:屏蔽%d信号和%d信号... \n", getpid(), SIGINT, 50);
sigset_t new;
if(sigemptyset(&new) == -1){
perror("sigemptyset");
return -1;
}
if(sigaddset(&new, SIGINT) == -1){
perror("sigaddset");
return -1;
}
if(sigaddset(&new, 50) == -1){
perror("sigaddset");
return -1;
}
sigset_t old;
if(sigprocmask(SIG_SETMASK, &new, &old) == -1){
perror("sigprocmask");
return -1;
}
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){
pid_t pid = getppid();
int i ;
for(i=0; i<5; ++i){
printf("%u进程:向%u进程发送%d信号... \n", getpid(), pid, 50);
kill(pid, 50);
}
return 0;
}
updatedb();
//将信号集从掩码集中移除,恢复处理之前未决的信号
sigset_t pend;
if(sigpending(&pend) == -1){
perror("sigpending");
return -1;
}
if(sigismember(&pend, SIGINT)) //执行过程中按Ctrl+C
printf("%u进程:发现%d信号未决... \n", getpid(), SIGINT);
if(sigismember(&pend, 50))
printf("%u进程:发现%d信号未决... \n", getpid(), 50);
//取消SIGINT ,50 的屏蔽
if(sigprocmask(SIG_SETMASK, &old, NULL) == -1){
perror("sigprocmask");
return -1;
}
return 0;
}

  • 此时不会再次打断进程。上传结果图片,此时最后可以收到5个50信号,1个2(不可靠)信号:


    IPC:信号_第3张图片
    图片发自App
IPC:信号_第4张图片
图片发自App

你可能感兴趣的:(IPC:信号)