Unix进程小结(二)关于信号的一些操作

一、基本的概念

1、中断

中止、暂停当前正在执行的进程,转而去执行其它的任务。

硬中断:来自硬件设备的中断

软中断:来自其它程序的中断

2、信号

信号是一种软中断,可以把他看作是进程与进程、内核与进程通信的一种方式,它为进程的异步执行,提供了技术支持。

3、一些常见信号

SIGINT(2)    终端中断信号Ctrl+c

SIGQUIT(3)    终端退出信号Ctrl+/

SIGABRT(6)    调用abort函数产生的信号

SIGFPE(8)    算术信号

SIGKILL(9)    死亡信号

SIGSEGV(11) 段错误信号

SIGALRM(14) 闹钟信号

SIGCHLD(17) 子进程结束信号

SIGCONT(18) 进程继续信号

SIGSTOP(19) 进程暂停信号

SIGTSTP(20) 终端停止信号

4、不可靠信号(非实时)

1、编号小于SIGRGMI(34)的信号都是不可靠的,这些信号是建立在早期的信号机制上的,一个事件发生可能会产生多次信号。

2、不可靠信号不支持排除,在接收信号的时候可能会丢失,如果一个发给一个进程多次,它可能只接收到一次,其它的可能就丢失了。

3、进程在处理这种信号的时候,哪怕设置的信号处理函数,当信号处理函数执行完毕后,会再次恢复成默认的信号处理方式。

5、可靠信号(实时)

1、位于[SIGRGMI(34),SIGRTMAX(64)]区间的都是可靠信号。

2、可靠信号支持排除,不会丢失。

3、无论是可靠信号还是不可靠信号都是通过:kill、signal、sigqueue、sigaction进行处理。

二、信号的捕获和处理

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t  signal(int  signum, sighan‐

dler_t handler);

功能:向注册一个信号处理函数

signum:信号的编号,可以直接写数字,也可以使用系统提供的宏。

handler:函数指针

SIG_IGN 忽略信号

SIG_DFL 恢复信号默认的处理方式

返回值:是之前信号处理方式

函数指针、SIG_IGN、SIG_DFL、SIG_ERR

(1)eg: signal(SIGINT ,SIG_ING );

//SIG_ING 代表忽略SIGINT信号,SIGINT信号代表由InterruptKey产生,通常是CTRL +C 或者是DELETE 。发送给所有ForeGround Group的进程。

下面我们写个死循环:

#include

#include

int main(int argc , char *argv[])

{

signal(SIGINT,SIG_IGN);

for(;;);

return 0;

}

这时我们保存执行。

按下CTRL _C程序没有反应。这就对了

如果我们想结束该程序可以按下CTRL +\来结束

其实当我们按下CTRL +\组合键时,是产生了SIGQUIT信号

(2)eg: signal(SIGINT ,SIG_DFL );

//SIGINT信号代表由InterruptKey产生,通常是CTRL +C或者是DELETE。发送给所有ForeGroundGroup的进程。 SIG_DFL代表执行系统默认操作,其实对于大多数信号的系统默认动作时终止该进程。这与不写此处理函数是一样的。

我们将上面的程序改成:

#include

#include

int main(int argc , char *argv[])

{

//signal(SIGINT,SIG_IGN);

signal(SIGINT,SIG_DFL)

for(;;);

return 0;

}

这时就可以按下CTRL +C 来终止该进程了。把signal(SIGINT,SIG_DFL);这句去掉,效果是一样的。

(3) void ( *signal( int sig, void (* handler)( int )))( int );

int (*p)();

这是一个函数指针, p所指向的函数是一个不带任何参数, 并且返回值为int的一个函数.

int (*fun())();

这个式子与上面式子的区别在于用fun()代替了p,而fun()是一个函数,所以说就可以看成是fun()这个函数执行之后,它的返回值是一个函数指针,这个函数指针(其实就是上面的p)所指向的函数是一个不带任何参数,并且返回值为int的一个函数.

void (*signal(int signo, void
(*handler)(int)))(int);就可以看成是signal()函数(它自己是带两个参数,一个为整型,一个为函数指针的函数),而这个signal()函数的返回值也为一个函数指针,这个函数指针指向一个带一个整型参数,并且返回值为void的一个函数.

在写信号处理函数时对于信号处理的函数也是void sig_fun(int signo);这种类型,恰好与上面signal()函数所返回的函数指针所指向的函数是一样的.void ( *signal() )( int );

signal是一个函数, 它返回一个函数指针, 后者所指向的函数接受一个整型参数 且没有返回值, 仔细看, 是不是siganal( int
signo, void (*handler)(int) )的第2个参数了,对了,其实他所返回的就是
signal的第2个信号处理函数,指向信号处理函数,就可以执行函数了( signal内部时,
signal把信号做为参数传递给handler信号处理函数,接着 signal函数返回指针, 并且又指向信号处理函数, 就开始执行它)

三、子进程的信号处理

1、通过fork创建的子进程会继承父进程的信号处理方式。

2、通过vfork+exec创建的子进程不会继承父进程的信号处理方式,会恢复成默认的。

练习:测试通过vfork+exec创建的子进程是否会继承父进程的信号处理方式。

四、信号的发送与kill命令以及kill函数的一些操作

kill命令是使跟在它后面的进程结束,学习了信号我们知道,它应该是给进程发送了让进程结束的信号才能完成这一功能。

如何给进程发送信号呢。这里就要用到kill函数;

它的原型如下;

int  kill(pid_ pid,int signum);   返回值含义:成功0 失败-1

接受者  信号类型

kill命令发送的是什么信号呢?我们知道让进程结束的信号有SIGINT。

可是kill其实还有kill和kill -9之分。kill不一定能杀死进程,kill -9却能100%杀死进程。这是为什么呢?

难道还有其他信号能使进程结束?

答案是肯定的。

下面用一个简单的程序来实现简单的kill命令

  1. #include<stdio.h>
  2. #include<signal.h>
  3. #include<unistd.h>
  4. #include<stdlib.h>
  5.  
  6.  
  7.  
  8. int main(int argc,char* argv[])
  9. {
  10.  
  11. if(argc==1)
  12. {
  13. printf("error cmd!\n");
  14. return -1;
  15. }
  16. int pid=atoi(argv[2]);
  17. int sig=atoi(argv[1]);
  18. kill(pid,sig);
  19. }

atoi函数将命令行中的字符串参数转换成整型,这样才能传递给kill函数!

这个程序最简单的操作是通过进程号杀死一个进程!

四、pause/sleep/alarm函数的一些操作

#include <unistd.h>

int pause(void);

功能:休眠

1、进程调用了pause函数后会进程睡眠状态,直到有信号把它叫醒(不被忽略的信号)。

2、当信号来临后,先执行信号处理函数,信号处理函数结束后pause再返回。

3、pause函数要么不返回(一直睡眠),要么返回-1,并且修改errno的值。

4、从功能上来讲它相当于没有时间限制的sleep函数。

#include <unistd.h>

unsigned int sleep(unsigned int seconds);

功能:使用调用的进程睡眠seconds秒

1、调用sleep的进程如果没有睡眠足够的秒数,除非收到信号后才会返回。

2、sleep的返回值是0,或剩余的睡眠秒数。

3、相当于有时间限制的pause

int usleep(useconds_t usec);v

功能:睡眠usec微秒

1秒=1000毫秒=1000000微秒。

它是一种更精确的睡眠函数。

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

功能:定时一个闹钟信号

1、让内核向调用它的进程,在seconds秒后发送一个SIGALRM信号。

2、SIGALRM信号的默认处理方式是直接退出。

下面通过一个简单的定时操作来让大家感受一下这几给函数

  1. #include<stdlib.h>
  2. #include<signal.h>
  3.  
  4. void sigalrm(int signum)
  5. {
  6. printf("我接受到了%d\n",signum);
  7. }
  8.  
  9. int main(int argc,char* argv[])
  10. {
  11. signal(SIGALRM,sigalrm);
  12. if(argc<1)
  13. {
  14. printf("error cmd!\n");
  15. return -1;
  16. }
  17. int sec=atoi(argv[1]);
  18. alarm(sec);
  19. pause();
  20. printf("定时%d秒!\n",sec);
  21.  
  22. }

此程序通过alarm和pause的配合简单实现了一个定时功能!先让程序休眠,在规定时间后alarm发送一个信号,pause也就结束了

达到了定时的目的!

五、信号集和信号屏蔽

1、信号集:

多个信号的集合,sigset_t

由128个二进制位组成,每个二进制位表示一个信号

int sigemptyset(sigset_t *set);

功能:清空信号集

int sigfillset(sigset_t *set);

功能:填满信号信

int sigaddset(sigset_t *set, int signum);

功能:向信号集中添加信号

int sigdelset(sigset_t *set, int signum);

功能:从信号集中删除信号

int  sigismember(const sigset_t *set, int

signum);

功能:测试一个信号集中是否有某个信号

返回值:有返回1,没有返回0,失败返回-1

2、屏蔽信号集中的信号

每个进程都有一个信号掩码(signal mask),它就是一个信号集,里面包含了进程所屏蔽的信号。

int sigprocmask(int how, const  sigset_t

*set, sigset_t *oldset);

功能:设置进程的信号掩码(信号屏蔽码)

how:修改信号掩码的方式

SIG_BLOCK:向信号掩码中添加信号

SIG_UNBLOCK:从信号掩码中删除信号

SIG_SETMASK:用新的信号集替换旧的信号掩码

newset:新添加、删除、替换的信号集,也可以为空

oldset:获取旧的信号掩码

当newset为空时,就是在备份信号掩码

当进程执行一些敏感操作时不希望被打扰(原子操作),此需要向屏蔽信号。

屏蔽信号的目的不是为了不接收信号,而是延时接收,当处理完要做的事情后,应该把屏蔽的信号还原。

当信号屏蔽时发生的信号会记录一次,这个信号设置为末决状态,当信号屏蔽结束后,会再发送一次。

不可靠信号在信号屏蔽期间无论信号发生多少次,信号解除屏蔽后,只发送一次。

可靠信号在信号屏蔽期间发生的信号会排队记录,在信号解除屏蔽后逐个处理。

在执行处理函数时,会默认把当前处理的信号屏蔽掉,执行完成后再恢复

int sigpending(sigset_t *set);

功能:获取末决状态的信号

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <signal.h>
  4.  
  5. // 更新数据
  6. void updata(void)
  7. {
  8. for(int i=0; i<10; i++)
  9. {
  10. printf("更新第%d条数据...\n",i);
  11. sleep(1);
  12. }
  13. }
  14.  
  15. void sigint(int signum)
  16. {
  17. printf("收到信号%d,正在处理...\n",signum);
  18. }
  19.  
  20. int main()
  21. {
  22. signal(34,sigint);
  23. printf("我是进程%d\n",getpid());
  24. // 定义信号集
  25. sigset_t set,oldset;
  26. // 初始化信号集
  27. sigemptyset(&set);
  28. // 向信号集中添加信号
  29. printf("向信号集中添加%d %s\n",34,sigaddset(&set,34)?"失败":"成功");
  30. // 设置信号掩码
  31. printf("设置信号掩码%s\n",sigprocmask(SIG_SETMASK,&set,&oldset)?"失败":"成功");
  32.  
  33. updata();
  34. sigpending(&set);
  35. for(int signum=1; signum<65; signum++)
  36. {
  37. if(sigismember(&oldset,signum))
  38. {
  39. printf("默认屏蔽的的信号%d\n",signum);
  40. }
  41. }
  42. printf("还原信号掩码%s\n",sigprocmask(SIG_SETMASK,&oldset,NULL)?"失败":"成功");
  43. pause();
  44. }

这个代码就是一个简单的屏蔽信号的实例,这里就不过多赘述!

总之:信号是一种软中断,是一种处理异步事件的方法。一般来说,操作系统都支持许多信号。尤其是UNIX,比较重要应用程序一般都会处理信号。

UNIX定义了许多信号,比如SIGINT表示中断字符信号,也就是Ctrl+C的信号,SIGBUS表示硬件故障的信号;SIGCHLD表示子进程状态改变信号;SIGKILL表示终止程序运行的信号,等等。信号量编程是UNIX下非常重要的一种技术。

原文地址:https://www.cnblogs.com/dachao0426/p/9367811.html

时间: 2024-10-15 06:37:41

Unix进程小结(二)关于信号的一些操作的相关文章

【归纳总结】Unix/linux下的进程管理(二):创建进程的函数及其应用、对比

创建进程的函数fork().vfork()和execl() 本次内容主要介绍Unix/linux下2个创建进程的函数fork和vfork以及它们的差别. 1.fork函数 (1)函数的格式 #include <unistd.h> pid_t fork(void); 函数功能: 主要用于以复制正在运行进程的方式来创建新的进程,其中新进程叫做子进程,正在运行的进程叫做父进程. 返回值: 函数调用成功时,父进程返回子进程的PID,子进程返回0,函数调用出错时,父进程返回-1,子进程没有被创建. 注意

Linux下的进程与线程(二)—— 信号

Linux进程之间的通信: 本文主要讨论信号问题. 在Linux下的进程与线程(一)中提到,调度器可以用中断的方式调度进程. 然而,进程是怎么知道自己需要被调度了呢?是内核通过向进程发送信号,进程才得以知道的. Linux系统的进程之间是通过信号来通信的. 程序员在Shell上显式地发送信号使用的是kill命令,原型如下: kill -sigid [-]pid 其中, sigid指示的是信号的id,pid前若有-,则pid代表的为进程组id,否则pid代表的为进程id kill函数也有相同的作用

进程小结

程序与进程: 程序(program)是一个普通文件,是机器代码指令和数据的集合,这些指令和数据存储在磁盘上的一个可执行映像中.所谓可执行映像就是一个可执行文件的内容.使用6个exec函数中的一个由内核将程序读入内存,并使其执行. 进程(process)是一个动态的实体,它具有生命周期,系统中进程的生死随时发生.程序的执行实例被称为进程. 专用进程: 进程ID0是调度进程,常常被称为交换进程(swapper).该进程并不执行任何磁盘上的程序,它是内核的一部分,因此也被称为系统进程. 它是由完成内核

shell实现Unix进程间信息交换的几种方法

使用命名管道实现进程间信息交换 使用kill命令和trap语句实现进程间信息交换 使用点命令“.”实现进程间信息交换 使用export语句实现父进程对子进程的信息传递 一.使用命名管道 命名管道是一种先进先出(FIFO)的数据结构,它允许两个进程通过管道联接实现信息交换. 在Unix系统中,命名管道是一种特殊类型的文件,因此可以对命名管道进行读写操作:当然,同样 也会有读写和执行等权限的限制. 通过下面的命令可以创建一个命名管道: /etc/mknod pipe_name p 其中“pipe_n

linux strace-跟踪进程的系统调用或是信号产生情况,lstrace-跟踪己丑年调用库函数情况,进程跟踪调试命令

本工具可以用来做大多数排除,比如mount一个NFS,很慢,找不出原因,我们可以使用strace命令来跟中mount这个经常所有的调用过程. strace 命令是一种强大的工具,它能够显示所有由用户空间程序发出的系统调用. strace 显示这些调用的参数并返回符号形式的值.strace 从内核接收信息,而且不需要以任何特殊的方式来构建内核. 下面记录几个常用 option . 1 -f -F选项告诉strace同时跟踪fork和vfork出来的进程 2 -o xxx.txt 输出到某个文件.

UNIX进程

一.UNIX进程环境 在学习UNIX进程工作原理时,我们应该先了解一下UNIX进程的基本环境是怎么样的,首先从main函数开始. 1.main函数 int main(int argc, char *argv[]); 相信main函数是我们非常熟悉的一个函数,它是C程序执行的入口函数.其中,argc是命令行参数的数目,agrv是指向参数的各个指针所构成的数组,而ISO/C和POSIX.1都要求argv[argc]是一个空指针. 当内核使用一个exec函数执行C程序时,在调用main函数前先调用一个

UNIX进程的环境

在学习进程之前,先来了解下进程的执行环境. main函数 进程总是从main函数开始执行的,我们编程时,程序运行也是从main函数运行的,它的原型如下: int main(int argc, char *[]argv); argc是命令行参数的数目,argv是指镇数组,即指向指针的指针,可以写代码测试一下: #include<stdio.h> int main(int argc, char *argv[]) { int i; for(i=0; i<argc; i++) printf(&q

linux_c开发(5-3)进程间通讯_信号通讯

信号通讯 信号(signal)机制是UNIX系统中最为古老的进程间通信机制,有很多条件可以产生一个信号: 1. 当用户按某些按键时,产生信号. 2. 硬件异常产生信号:除数为零,无效的存储访问等等.这些信号通常有硬件检测得到,将其通知内核,然后内核产生适当的信号通知进程,例如: 内核对正在访问一个无效存储区的进程产生一个SIGSEGV信号. 3.进程用kill函数将信号发送给另一个进程. 4. 用户可以用kill命令将信号发送给其他进程. 信号类型 几种常见的信号 SIGHUP: 从终端发来的结

Unix环境高级编程(十)信号续

1.signal函数 Unix系统的信号机制最简单的接口是signal函数,函数原型如下: #include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); signum表示信号名称,handler取值常量SIG_IGN(忽略此信号).常量SIG_DFL(执行默认动作)或者接到此信号后要调用的函数的地址(调用信号处理程序). 写个程序练习一下