linux系统信号

信号的概念

软中断信号(signal,简称信号)用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。

信号的机制

信号的特质:由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性。但对于用户来说,这个延迟时间非常短,不易察觉。
每个进程收到的所有信号,都是由内核负责发送的,内核处理。

与信号相关的事件和状态

产生信号:

1. 按键产生,如:Ctrl+c、Ctrl+z、Ctrl+\
2. 系统调用产生,如:kill、raise、abort
3. 软件条件产生,如:定时器alarm
4. 硬件异常产生,如:非法访问内存(段错误)、除0(浮点数例外)、内存对齐出错(总线错误)
5. 命令产生,如:kill命令
	递达:递送并且到达进程。
	未决:产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态。

信号的处理方式:

1. 执行默认动作 
2. 忽略(丢弃) 
3. 捕捉(调用户处理函数)

Linux内核的进程控制块PCB是一个结构体,task_struct, 除了包含进程id,状态,工作目录,用户id,组id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。

阻塞信号集

阻塞信号集(信号屏蔽字): 将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(解除屏蔽后)

未决信号集:

1. 信号产生,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态。当信号被处理对应位翻转回为0。这一时刻往往非常短暂。 
2. 信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。    

信号的编号

可以使用kill –l命令查看当前系统可使用的信号有哪些。

1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 	5) SIGTRAP
 	6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX

不存在编号为0的信号。其中1-31号信号称之为常规信号(也叫普通信号或标准信号),34-64称之为实时信号,驱动编程与硬件相关。名字上区别不大。而前32个名字各不相同。

Linux常规信号一览表

1、SIGHUP
本信号在用户终端连接(正常或非正常)结束退出时候发出,通常是在终端的控制进程结束时,通知同一session 内的各个作业,这时它们与控
制终端不再联系。
 登录Linux时,系统会分配给登录用户一个终端(session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个session。当用户退出Linux登录时,前台进程组和后台进程组有对终端输出的进程将会收到SIGHUP信号。这个信号默认操作为终止进程,因此前台进程组和后台进程组有终端输出的进程就会终止。不过可以捕获这个信号,比如:wget能捕获SIGHUP信号,并忽略它,这样就算退出来Linux登录,wget也能继续下载。此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。
2、SIGINT
程序终止(interrupt)信号,在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。
3、SIGQUIT
 和SIGINT类似,但由于QUIT字符(通常是Ctrl-\)来控制。进程在因收到SIGQUIT退出时会产生core文件,在这个意义上类似于一个程序错误
 信号。
4、SIGILL
执行了非法指令。通常是因为可执行文件本身出现错误,或者试图执行数据段,堆栈溢出时也可能产生这个信号。
5、SIGTRAP
有断点指令或其他指trap令产生。由debugger使用。
6、SIGABRT
调用abort函数生成的辛哈。
7、SIGBUS
非法地址,包括地址内存对齐(alignment)出错。比如访问一个四个字节长的整数,但其地址不是4的倍数,它与SIGSGV的区别在于后者是
由于对合法存储地址的非法访问触发的(如访问不属于自己的存储空间或只读存储空间)。
8、SIGFPE
在发生致命的算术运算错误时发出,不仅包括浮点运算符错误,还包括溢出及除数为0等其他所有的算术的错误。
9、SIGKILL
用来立即结束程序的运行,本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。
10、SIGUSR1
留给用户使用。
11、SIGSEGV
 试图访问未非配给自己的内存,或试图往没有写权限对内存地址写数据。
12、SIGUSR2
留给用户使用。
13、SIGPIPE
管道破裂,这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没有打开或者意外终止就往管道写,写进程会
收到SIGPIPE信号。此外用socket通信的两个进程,谢金成在写socket的时候,读进程已经终止。
14、SIGALRM
时钟定时信号,计算的时实际时间或者是时钟时间,alarm函数使用该信号。
15、SIGTERM
程序结束(terminate)信号,与SIGKILL不同的是该信号可以被阻塞和处理,通常用来要求程序正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。
17、SIGCHLD
子进程结束时,父进程会收到这个信号。如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占用表项,这时的子进程称为僵尸进程。这种情况我们应该避免(父进程或者忽略SIGCHLD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进行来接管)。
18、SIGCONT
 让一个停止(stopped)的进程继续执行,本信号不能被阻塞。可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作,例如,重新显示提示符。
19、SIGSTOP
停止(stopped)进程的执行,注意它和terminate以及interrupt的区别:该进程还未结束,本信号不能被阻塞、处理或忽略。
20、SIGTSTP
停止进程的运行,但是该信号可以被处理或者忽略,用户键入SUSP字符时(通常Ctrl-Z)发出这个信号。
21、SIGTTIN
当后台作业要求用户终端读数据时,改作业中的所有进程会收到SIGTTIN信号,缺省时这些进程会停止执行。
22、SIGTTOU
 类似于SIGTTIN,但是写终端(或修改终端模式)时收到。
23、SIGURG
有“紧急”数据或out-of-band数据到达socket时产生。
24、SIGXCPU
超过CPU时间资源限制,这个限制可以由getrlimit/setrlimit来读取/改变。
25、SIGXFSZ
当进程企图扩大文件以至于超过文件大小资源限制。
26、SIGVTALRM
虚拟时钟信号,类似于SIGALRM,但是计算的是该进程占用的CPU时间
27、SIGPROF
 类似于SIGALRM/SIGVTALRM,但包括该进程用的CPU时间以及系统调用时间。
28、SIGWINCH
窗口大小改变时发出。
29、SIGIO
文件描述符准备就绪,可以开始进行输入/输出操作。
30、SIGPWR
Power failure
31、SIGSYS
非法的系统调用

注意

在以上列出的信号中,程序不可以捕获、阻塞或忽略的信号有:SIGKILL、SIGSTOP
不能恢复至默认动作的信号有:SIGABRT、SIGBUS、SIGILL、SIGFPE、SIGIOT、SIGQUIT、SIGSEGV、SIGTRAP、SIGXCPU、SIGFSZ
 默认会导致进程退出的信号有:SIGALRM、SIGHUP、SIGINT、SIGKILL、SIGPIPE、SIGPOLL、SIGPROF、SIGSYS、SIGTERM、SIGUSR1、SIGUSR2、SIGVTALRM
 默认会导致进程停止的信号有:SIGSTOP、SIGSTP、SIGTTIN、SIGTTOU
默认进程忽略的信号有:SIGCHLD、SIGPWR、SIGURG、SIGWINCH
此外,SIGIO在SVR4是退出,在4.3BSD中忽略;SIGCONT在进程挂起时是继续、否则是忽略,不能被阻塞。

信号4要素

与变量三要素类似的,每个信号也有其必备4要素,分别是:

1. 编号 2. 名称 3. 事件 4. 默认处理动作 

可通过man 7 signal查看帮助文档获取。也可查看/usr/src/linux-headers-3.16.0-30/arch/s390/include/uapi/asm/signal.h

9) SIGKILL 和19) SIGSTOP信号,不允许忽略和捕捉,只能执行默认动作。甚至不能将其设置为阻塞。
另外需清楚,只有每个信号所对应的事件发生了,该信号才会被递送(但不一定递达),不应乱发信号!!

SIGCHLD信号

SIGCHLD的产生条件

子进程终止时
子进程接收到SIGSTOP信号停止时
子进程处在停止态,接受到SIGCONT后唤醒时
借助SIGCHLD信号回收子进程
子进程结束运行,其父进程会收到SIGCHLD信号。该信号的默认处理动作是忽略。可以捕捉该信号,在捕捉函数中完成子进程状态的回收。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

void sys_err(char *str)
{
    perror(str);
    exit(1);
}
void do_sig_child(int signo)
{
    int status;    pid_t pid;
while ((pid = waitpid(0, &status, WNOHANG)) > 0) {
	//函数可能只进入一次,但可以多次回收,避免几个子进程一起死的情况(几乎同时发送信号,未决信号集只能记录一次。避免漏掉。)
        if (WIFEXITED(status))
            printf("child %d exit %d\n", pid, WEXITSTATUS(status));
        else if (WIFSIGNALED(status))
            printf("child %d cancel signal %d\n", pid, WTERMSIG(status));
    }
}
int main(void)
{
    pid_t pid;    int i;
    for (i = 0; i < 10; i++) {
        if ((pid = fork()) == 0)
            break;
        else if (pid < 0)
            sys_err("fork");
    }
    if (pid == 0) {    
        int n = 1;
        while (n--) {
            printf("child ID %d\n", getpid());
            sleep(1);
        }
        return i+1;
    } else if (pid > 0) {
        struct sigaction act;
        act.sa_handler = do_sig_child;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        sigaction(SIGCHLD, &act, NULL);
        
        while (1) {
            printf("Parent ID %d\n", getpid());
            sleep(1);
        }
    }
    return 0;
}

子进程结束status处理方式

pid_t waitpid(pid_t pid, int *status, int options)
options
WNOHANG
没有子进程结束,立即返回
WUNTRACED
如果子进程由于被停止产生的SIGCHLD,waitpid则立即返回
WCONTINUED
如果子进程由于被SIGCONT唤醒而产生的SIGCHLD,waitpid则立即返回
获取status
WIFEXITED(status)
子进程正常exit终止,返回真
WEXITSTATUS(status)返回子进程正常退出值
WIFSIGNALED(status)
子进程被信号终止,返回真
WTERMSIG(status)返回终止子进程的信号值
WIFSTOPPED(status)
子进程被停止,返回真
WSTOPSIG(status)返回停止子进程的信号值
WIFCONTINUED(status)

SIGCHLD信号注意问题

  1. 子进程继承了父进程的信号屏蔽字和信号处理动作,但子进程没有继承未决信号集spending。
  2. 注意注册信号捕捉函数的位置。
  3. 应该在fork之前,阻塞SIGCHLD信号。注册完捕捉函数后解除阻塞。
    信号传参
    发送信号传参
    sigqueue函数对应kill函数,但可在向指定进程发送信号的同时携带参数
int sigqueue(pid_t pid, int sig, const union sigval value);成功:0;失败:-1,设置errno
           union sigval {
               int   sival_int;
               void *sival_ptr;
           };
向指定进程发送指定信号的同时,携带数据。但,如传地址,需注意,不同进程之间虚拟地址空间各自独立,将当前进程地址传递给另一进程没有实际意义。

捕捉函数传参

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
           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而应该使用sa_sigaction。但此时的sa_flags必须指定为SA_SIGINFO。siginfo_t是一个成员十分丰富的结构体类型,可以携带各种与信号相关的数据。

信号的产生

终端按键产生信号

 Ctrl + c  → 2) SIGINT(终止/中断)	 "INT" ----Interrupt
    Ctrl + z  → 20) SIGTSTP(暂停/停止)  "T" ----Terminal 终端。
    Ctrl + \  → 3) SIGQUIT(退出)	

硬件异常产生信号
除0操作 → 8) SIGFPE (浮点数例外) “F” -----float 浮点数。
非法访问内存 → 11) SIGSEGV (段错误)
总线错误 → 7) SIGBUS

捕获信号

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void sigact(int  num)
{
    printf("\n %d号 信号 被我吃了. \n", num);
}

int main()
{
    printf("catch start ... \n");
    signal(SIGINT, sigact); // 捕捉 SIGINT 信号,提供自定义动作
    while(1)
    {
        sleep(1);
        printf("你杀不掉我 hhh \n");
    }
    return 0;
}

Kill函数操作

kill命令产生信号:kill -SIGKILL pid
kill函数:给指定进程发送指定信号(不一定杀死)

 int kill(pid_t pid, int sig);	 成功:0;失败:-1 (ID非法,信号非法,普通用户杀init进程等权级问题),设置errno
	sig:不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。
    pid > 0:  发送信号给指定的进程。
	pid = 0:  发送信号给 与调用kill函数进程属于同一进程组的所有进程。
	pid < 0:|pid|发给对应进程组。
	pid = -1:发送给进程有权限发送的系统中所有进程

进程组:每个进程都属于一个进程组,进程组是一个或多个进程集合,他们相互关联,共同完成一个实体任务,每个进程组都有一个进程组长,默认进程组ID与进程组长ID相同。
权限保护:super用户(root)可以发送信号给任意用户,普通用户是不能向系统用户发送信号的。 kill -9 (root用户的pid) 是不可以的。同样,普通用户也不能向其他普通用户发送信号,终止其进程。 只能向自己创建的进程发送信号。普通用户基本规则是:发送者实际或有效用户ID == 接收者实际或有效用户ID

循环创建进程,杀死某一特定进程

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>

/*
 * 循环创建 5 个子进程                                                             
 */

#define N  5
int main()
{
    int i;
    pid_t pid,q;

    for(i=0;i<N;i++){
        pid = fork();
        if(pid == 0)
            break;
        if(i==2)
            q=pid;
    }
    if(i<5){
        while(1){
            printf("I am child %d,getpid= %d\n",i,getpid());
            sleep(1);
        }   
    }else{
        sleep(1);
        kill(q,SIGKILL);
        while(1);
    }   
    return 0;
}

raise和abort函数

raise 函数:给当前进程发送指定信号(自己给自己发)	raise(signo) == kill(getpid(), signo);
	int raise(int sig); 成功:0,失败非0值
	
	
abort 函数:给自己发送异常终止信号 6) SIGABRT 信号,终止并产生core文件
	void abort(void); 该函数无返回

alarm函数

每个进程都有且只有唯一个定时器。

unsigned int alarm(unsigned int seconds)
函数作用:在seconds秒之后向调用alarm()的进程发送一个SIGALRM信号。
1.如果指定seconds是0,表示取消正在等待的alarm,如果在等待时间结束之前有其它事件到来,alarm也将被取消。
2.对于一个进程而言,只有最近的依次alarm()调用是有效的。alarm()的返回值是上次alarm()调用剩余的时间。
alarm()函数经常与signal(int signum, sighandler_t handler)函数一起使用,通过signal()函数可以指定受到该信号后的动作。
signum是要处理的信号类型
handler是一个函数指针,指向接到信号后的相应动作
使用time命令查看程序执行的时间。
实际执行时间 = 系统时间 + 用户时间 + 等待时间

一秒钟计算的值

#include <stdio.h>
#include <unistd.h>

int main()
{
    int count = 0;

    alarm(1);
    while(1)
    {
        count++;
        printf("count is  %d .\n", count);
    }
    return 0;
}

setitimer函数

头文件#include<sys/time.h>

设置定时器(闹钟)。 可代替alarm函数。精度微秒us,可以实现周期定时。
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value); 成功:0;失败:-1,设置errno
参数:which:指定定时方式
① 自然定时:ITIMER_REAL → 14)SIGLARM 计算自然时间
② 虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM 只计算进程占用cpu的时间
③ 运行时计时(用户+内核):ITIMER_PROF → 27)SIGPROF 计算占用cpu及执行系统调用的时间

struct itimerval *new_value定义如下:
struct itimerval {
               struct timeval it_interval; /* next value */
               struct timeval it_value; /* current value */
           };

Struct timeval定义如下

struct timeval {
               long tv_sec; /* seconds */
               long tv_usec; /* microseconds */
          };

Setitime函数简单用法


```c
#include<stdio.h>
#include<unistd.h>
#include<sys/time.h>
#include<stdlib.h>

unsigned int my_alarm(unsigned int sec)
{
struct itimerval it, oldit;
int ret;
it.it_value.tv_sec = sec;
it.it_value.tv_usec = 0;
it.it_interval.tv_sec = 0;//两次定时之间的间隔
it.it_interval.tv_usec = 0;
ret = setitimer(ITIMER_REAL, &it, &oldit);
if(ret == -1)
{
perror("setitimer\n");
exit(1);
}
return oldit.it_value.tv_sec;
}
int main()
{
my_alarm(1);//alarm(1);
int i;
for(i=0;;i++)
{
i++;
printf("%d\n",i);
}
return 0;
}

使用setitime函数制作间隔计时器

#include <stdio.h>       
#include <unistd.h>      
#include <signal.h>       
#include <string.h>       
#include <sys/time.h>   

static int count = 0;

void printMes(int signo)
{
    printf("Get a SIGALRM, %d counts!\n", ++count);
}

int main()
{
    int res = 0;
    struct itimerval tick;
    
    signal(SIGALRM, printMes);
    memset(&tick, 0, sizeof(tick));
    tick.it_value.tv_sec = 1;
    tick.it_value.tv_usec = 0;
    tick.it_interval.tv_sec = 1;
    tick.it_interval.tv_usec = 0;

    if(setitimer(ITIMER_REAL, &tick, NULL) < 0)
            printf("Set timer failed!\n");
    while(1)
    {
        pause();
    }
    return 0;
}

sigprocmask函数

用来屏蔽信号、解除屏蔽也使用该。函数。其本质,读取或修改进程的信号屏蔽字(PCB中)
严格注意,屏蔽信号:只是将信号处理延后执行(延至解除屏蔽);而忽略表示将信号丢处理。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);	成功:0;失败:-1,设置errno
参数:
		set:传入参数,是一个位图,set中哪位置1,就表示当前进程屏蔽哪个信号。
		oldset:传出参数,保存旧的信号屏蔽集。
		how参数取值:	假设当前的信号屏蔽字为mask
1.	SIG_BLOCK: 当how设置为此值,set表示需要屏蔽的信号。相当于 mask = mask|set
2.	SIG_UNBLOCK: 当how设置为此,set表示需要解除屏蔽的信号。相当于 mask = mask & ~set
3.	SIG_SETMASK: 当how设置为此,set表示用于替代原始屏蔽及的新屏蔽集。相当于 mask = set若,调用sigprocmask解除了对当前若干个信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。

如果set是空指针,则不改变该进程的信号屏蔽字,how的值也无意义。
在调用sigprocmask后如果有任何未决的、不再阻塞的信号,则在sigprocmask返回前,至少会将其中一个信号递送给该进程。

#include <stdio.h>
#include <signal.h> 
void checkset(int i); 
 
void main()    
{
     sigset_t blockset;    
     sigemptyset(&blockset);    
     sigaddset(&blockset,SIGINT);    
     sigaddset(&blockset,SIGTSTP);    
 
     if(sigismember(&blockset,SIGINT))    
       printf("sig int in main\n");    
     checkset(0);    
 
     sigprocmask(SIG_SETMASK,&blockset,NULL);    
     if(sigismember(&blockset,SIGINT))    
       printf("sig int in main\n");    
    checkset(1);    
 
     sigaddset(&blockset,SIGTERM);    
     sigprocmask(SIG_BLOCK,&blockset,NULL);    
     checkset(2);    
 
     //sigdelset(&blockset,SIGTERM);     
     sigprocmask(SIG_UNBLOCK,&blockset,NULL);    
     if(sigismember(&blockset,SIGTERM))    
       printf("sig term in main\n");    
     if(sigismember(&blockset,SIGINT))    
       printf("sig int in main\n");    
     if(sigismember(&blockset,SIGTSTP))    
       printf("sig tstp in main\n");    
     checkset(3);
     if(sigismember(&blockset,SIGTERM))    
       printf("sig term in main\n");    
     if(sigismember(&blockset,SIGINT))    
       printf("sig int in main\n");    
     if(sigismember(&blockset,SIGTSTP))    
       printf("sig tstp in main\n");
}
 
void checkset(int i)
{
 
     sigset_t set;
     printf("check set start:%d\n", i);
 
     if(sigprocmask(SIG_BLOCK,NULL,&set)<0)
    {
       printf("check set sig procmask error!!\n");
       exit(0);
     }
 
     if(sigismember(&set,SIGINT))
       printf("sig int\n");
     if(sigismember(&set,SIGTSTP))
       printf("sig tstp\n");
     if(sigismember(&set,SIGTERM))
       printf("sig term\n");
 
     printf("check set end\n\n");
}

sigpending函数

读取当前进程的未决信号集
int sigpending(sigset_t *set);	set传出参数。   返回值:成功:0;失败:-1,设置errno

打印未决信号集

#include 
#include 
#include 
void printPending(sigset_t *set)
{
int i = 0;
for (i = 0; i < 32; i++) {
if (sigismember(set, i) == 1)
printf("1");
else
 printf("0");
}
 printf("\n");
}
int main()
{
sigset_t set, oldset, pendset;
sigemptyset(&set);
sigaddset(&set,SIGQUIT);   // ctrl + \ 将产生SIGQUIT信号
sigprocmask(SIG_BLOCK, &set, &oldset);
 while (1) {
sigpending(&pendset);
printPending(&pendset);     // 写一个函数打印未决信号集
sleep(1);
}
}

signal函数

注册一个信号捕捉函数:

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

该函数由ANSI定义,由于历史原因在不同版本的Unix和不同版本的Linux中可能有不同的行为。因此应该尽量避免使用它,取而代之使用sigaction函数。

void (*signal(int signum, void (*sighandler_t)(int))) (int);

注意多在复杂结构中使用typedef。

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<unistd.h>
typedef void (*sighandler_t) (int );
void catchsigint(int signo)
{
Printf(“…………..catch\n”);
}
Int main()
{
sighandler_t handler;
handler = signal(SIGINT,catchsigint);
if(handler == SIG_ERR)
{
perror(“signal error”);
exit(1);
}
while(1)
return 0;
}

sigaction函数

修改信号处理动作(通常在Linux用其来注册一个信号的捕捉函数)
    int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);  成功:0;失败:-1,设置errno
参数:
act:传入参数,新的处理方式。
		oldact:传出参数,旧的处理方式。													【signal.c】
struct sigaction结构体
    struct sigaction {
        void     (*sa_handler)(int);
        void     (*sa_ )(int, siginfo_t *, void *);
        sigset_t   sa_mask; 
        int       sa_flags; 
        void     (*sa_restorer)(void);
    };
	sa_restorer:该元素是过时的,不应该使用,POSIX.1标准将不指定该元素。(弃用)
	sa_sigaction:当sa_flags被指定为SA_SIGINFO标志时,使用该信号处理程序。(很少使用)  
重点掌握:
	① sa_handler:指定信号捕捉后的处理函数名(即注册函数)。也可赋值为SIG_IGN表忽略 或 SIG_DFL表执行默认动作
	② sa_mask: 调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。
	③ sa_flags:通常设置为0,表使用默认属性。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
 
int main()
{
    struct sigaction newact,oldact;
 
    /* 设置信号忽略 */
    newact.sa_handler = SIG_IGN; //这个地方也可以是函数
    sigemptyset(&newact.sa_mask);
    newact.sa_flags = 0;
    int count = 0;
    pid_t pid = 0;
 
    sigaction(SIGINT,&newact,&oldact);//原来的备份到oldact里面
 
    pid = fork();
    if(pid == 0)
    {
        while(1)
        {
            printf("I'm child gaga.......\n");
            sleep(1);
        }
        return 0;
    }
 
    while(1)
    {
        if(count++ > 3)
        {
            sigaction(SIGINT,&oldact,NULL);  //备份回来
            printf("pid = %d\n",pid);
            kill(pid,SIGKILL); //父进程发信号,来杀死子进程
        }
 
        printf("I am father .......... hahaha\n");
        sleep(1);
    }
 
    return 0;
}

信号集操作函数

内核通过读取未决信号集来判断信号是否应被处理。信号屏蔽字mask可以影响未决信号集。而我们可以在应用程序中自定义set来改变mask。已达到屏蔽指定信号的目的。

信号集设定

sigset_t  set;		// typedef unsigned long sigset_t; 
int sigemptyset(sigset_t *set);			将某个信号集清0		 		成功:0;失败:-1
    int sigfillset(sigset_t *set);				将某个信号集置1		  		成功:0;失败:-1
    int sigaddset(sigset_t *set, int signum);		将某个信号加入信号集  		成功:0;失败:-1
    int sigdelset(sigset_t *set, int signum);		将某个信号清出信号集   		成功:0;失败:-1
    int sigismember(const sigset_t *set, int signum);判断某个信号是否在信号集中	返回值:在集合:1;不在:0;出错:-1  
    sigset_t类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。
对比认知select 函数。

Select函数

头文件及函数原型
/* According to POSIX.1-2001 */
       #include <sys/select.h>  //头文件
 
       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>
 
       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);//函数原型

该函数允许进程指示等待多个事件的任何一个发生,并且只在有一个或多个事件发生或者经历一段指定的时间后才唤醒它
它只有在如下四种情况下返回

1. 集合中的任何描述符准备好读
2. 集合中的任何描述符准备好谢
3. 集合中的任何描述符有异常等待处理
4. 等待事件到达

等待事件这个参数有三种可能

1.永远等下去,仅在有一个描述符准备好才返回,为此,我们把该参数设置为空指针
2.等待一段固定时间,在有一个描述符准好好I/O才返回,但是不能超时;
3. 不等待,检查描述符后立即返回,这时定时器的时间必须设置为0

返回值:

1)正常情况下返回满足要求的文件描述符个数;
(2)经过了timeout等待后仍无文件满足要求,返回0;
(3)如果select被某个信号中断,将返回-1并设置errno为EINTR;
(4)若出错,返回-1并设置相应的errno;

select的使用方法:

1)将要监控的文件添加到文件描述符集;
(2)调用select开始监控;
(3)判断文件是否发生变化;

系统提供四个宏对描述符集进行操作:

void FD_SET(int fd, fd_set *fdset); //将文件描述符fd添加到文件描述符集fdset中;
void FD_CLR(int fd, fd_set *fdset); //从文件描述符集fdset中清除文件描述符fd;
void FD_ISSET(int fd, fd_set *fdset); //在调用select后使用FD_ISSET来检测文件描述符集中的文件fd发生了变化
void FD_ZERO(fd_set *fdset);//清空文件描述符集

注意:
每次调用完select()函数后需要将文件描述符集合清空并重新设置,也就是设置的文件描述符集合是一次性使用的。原因是调用完select()后文件描述符集合可能发生改变。

#include <sys/time.h>
#include <stdio.h>
#include <fcntl.h>

int main()
{
    int fd_key, ret;
    char value;
    fd_set readfd;
    struct timeval timeout;

    fd_key = open("/dev/tty", O_RDONLY);
    timeout.tv_sec=1;
    timeout.tv_usec=0;

    while(1){
        FD_ZERO(&readfd);                       /* 清空文件描述符集合 */
        FD_SET(fd_key, &readfd);                /* 添加文件描述符到集合 */

        ret = select(fd_key + 1, &readfd, NULL, NULL, &timeout);
        if(FD_ISSET(fd_key, &readfd)){          /* 测试fd_key是否在描述符集合中 */
            read(fd_key, &value, 1);  
            if('\n' == value){
                continue;
            }
            printf("ret = %d, fd_key = %d, value = %c\n", ret, fd_key, value);
        }
    }
}

信号捕捉特性

1.	进程正常运行时,默认PCB中有一个信号屏蔽字,假定为☆,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数有可能执行很长时间,在这期间所屏蔽的信号不由☆来指定。而是用sa_mask来指定。调用完信号处理函数,再恢复为☆。
2.	XXX信号捕捉函数执行期间,XXX信号自动被屏蔽。
阻塞的常规信号不支持排队,产生多次只记录一次。(后32个实时信号支持排队)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>

void docatch(int signo)
{
printf(%d signal is catched\n”,signo);
sleep(1);
printf(“……………….finish\n”);
}

int main()
{
		int ret ;
		struct sigaction act;
act.sa_handler = docatch;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,SIGQUIT);
act.sa_flags = 0;
ret = sigaction(SIGINT,&act,NULL);
if(ret<0)
{
perror(“sigaction error”);
exit(1);
}
while(1)
return 0;
}
发布了6 篇原创文章 · 获赞 1 · 访问量 1526

猜你喜欢

转载自blog.csdn.net/m0_46198325/article/details/104287444