一、信号概念
-
信号机制
信号是软件层面上的“中断”。一旦产生。无论程序执行到什么位置,必须立即停止运行,处理信号,处理结束,再继续执行后续指令。 所有信号的产生和处理都由【内核】完成。
-
与信号相关的事件和状态
1.产生信号 1)按键产生,如 ctrl+c、ctrl+z、ctrl+\ 2)系统调用产生,如 kill 3)软件条件产生,如 定时器alarm(sleep机制) 4)硬件异常产生,如 非法访问内存(段错误)、除0(浮点数除外)、内存对齐出错(总线错误) 5)命令产生,如 kill 命令 2.递达 信号递送并且到达进程 3.未决 产生和递达之间的状态,主要由于阻塞(屏蔽)导致该状态。 此状态会有信号的处理方式: 1)执行默认动作 2)忽略(丢弃) 3)捕捉(调用用户处理函数) 4.Linux内核进程控制块PCB是一个结构体,其中包含信号相关的信息,主要指 【阻塞信号集】 和 【未决信号集】。 未决信号集和阻塞信号集本质是两个位图,每个位对应一个信号,1代表信号未决或阻塞,0是默认态。 1)未决信号集 1.信号产生,描述该信号的位图位反转为1,表示信号处于未决状态;当信号被处理对应位翻转为0。这一时刻非常短暂。 2.信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称为未决信号集。在屏蔽解除前,该信号一直一直处于未决状态。 2)阻塞信号集(信号屏蔽字): 将某些信号加入集合,当屏蔽某信号后,再收到该信号,相应的处理在解除屏蔽后。
-
信号编号
kill -l #查看当前系统可使用信号 没有编号为0的信号。 1至31称为常规信号,每个信号都有对应的默认事件或处理动作。Linux系统编程主要学这部分即可。 34至64称为实时信号,常用于驱动编程或硬件相关,每个信号没有对应的默认事件或处理动作
-
信号4要素
man 7 signal #查看信号相关信息 信号4要素: 1.编号 2.名称 3.事件 4.默认处理动作 1)Term Default action is to terminate the process. 2)Ign Default action is to ignore the signal. 3)Core Default action is to terminate the process and dump core (see core(5)). 4)Stop Default action is to stop the process. 5)Cont Default action is to continue the process if it is currently stopped.
-
常规信号一览表
二、信号的产生
-
终端按键产生信号
不赘述 -
硬件异常产生信号
不赘述 -
kill函数/命令产生信号
kill 函数 man 2 kill 1.NAME kill - send signal to a process 2.SYNOPSIS #include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig); 3.parameter pid: >0 :发送信号给指定进程 =0 :发送信号给跟调用kill函数的那个进程处于 同一进程组 的进程 <-1:取绝对值,发送信号给该绝对值所对应的进程组的所有组员 =-1:发送信号给 有权限发送的所有进程。 【此操作很危险,忽用】 4.RETURN VALUE On success (at least one signal was sent), zero is returned. On error, -1 is returned, and errno is set appropriately. 5.Demo https://github.com/Panor520/LinuxCode/tree/master/signal/kill.c 还有其它的发信号函数,不做详细介绍: raise() man 3 raise abort() man 3 abort
-
软件条件产生信号
1.alarm 函数 设置定时器(闹钟),自然定时,在指定seconds后,内核会给当前进程发送 14)SIGALRM信号。进程收到该信号默认动作为 终止。 每个进程都有且 只有一个定时器。 man 2 alarm 1)NAME alarm - set an alarm clock for delivery of a signal 2)SYNOPSIS #include <unistd.h> unsigned int alarm(unsigned int seconds); 3)RETURN VALUE alarm() returns the number of seconds remaining until any previously scheduled alarm was due to be delivered, or zero if there was no previously scheduled alarm. 中文简译:上次定时剩余时间。 4)Demo https://github.com/Panor520/LinuxCode/tree/master/signal/alarm.c 5)附加知识 使用time命令查看程序执行的时间。 例:time ./alarm 实际执行时间 = 系统时间 + 用户时间 + 等待时间(占绝大部分) 2.setitimer函数 设置定时器(闹钟)。可替代alarm函数。精度微妙us,可以实现周期定时。 man 2 setitimer 1)NAME getitimer, setitimer - get or set value of an interval timer 2)SYNOPSIS #include <sys/time.h> int getitimer(int which, struct itimerval *curr_value); int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value); 3)parameter (1)which:指定定时方式 1.自然定时 ITIMER_REAL 触发后发送 14)SIGLARM信号 计算自然时间(实际执行时间= 系统时间 + 用户时间 + 等待时间) 2.虚拟空间时间(用户空间) ITIMER_VIRTUAL 触发后发送 26)SIGVTALRM 只计算进程占用cpu时间 3.运行时计时(用户+内核) ITIMER_PROF 触发后发送 27)SIGPROF 计算占用cpu及执行系统调用时间 (2)new_value,old_value old_value是值结果参数,返回计时器的上一个值(即getitimer()返回的相同信息),一般设置为NULL即可。 struct itimerval { struct timeval it_interval; /* Interval for periodic timer *///用来设定两次定时任务之间间隔时间,即周期定时参数 struct timeval it_value; /* Time until next expiration *///定时时长,即计时参数 }; //it_interval、it_value两个参数都设置为0,即清0操作 struct timeval { time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ }; 赋值方式示例,详见demo: struct itimerval ni; ni.it_interval.tv_sec=0; ni.it_interval.tv_usec=0; ni.it_value.tv_sec=1; ni.it_value.tv_usec=0; setitimer(ITIMER_REAL,&ni,NULL); 4)RETURN VALUE On success, zero is returned. On error, -1 is returned, and errno is set appropriately. 5)Demo https://github.com/Panor520/LinuxCode/tree/master/signal/setitimer.c
三、信号集操作函数
- 设置阻塞信号集的方法
PCB自带阻塞信号集,
利用自定义信号集和PCB自带信号集进行【位或】操作,或者利用自定义信号集【替换】PCB自带信号集,即可设置新的阻塞信号集。(详见下面sigprocmask函数)
这一步利用【位或】操作进行。
阻塞信号集生命周期就是进程PCB的声明周期。
-
信号集操作函数
man 3 sigemptyset 1.NAME sigemptyset, sigfillset, sigaddset, sigdelset, sigismember - POSIX signal set operations 2.SYNOPSIS #include <signal.h> int sigemptyset(sigset_t *set); //清空信号集 int sigfillset(sigset_t *set); //全部信号进入自定义信号集,即全部置1 int sigaddset(sigset_t *set, int signum); //将一个信号添加到集合中 int sigdelset(sigset_t *set, int signum); //将一个信号从集合中移出 int sigismember(const sigset_t *set, int signum); //判断一个信号是否在集合中。 在返回1,不在:0. 3.RETURN VALUE sigemptyset(), sigfillset(), sigaddset(), and sigdelset() return 0 on success and -1 on error. sigismember() returns 1 if signum is a member of set, 0 if signum is not a member, and -1 on error. On error, these functions set errno to indicate the cause of the error.
-
利用信号集设置屏蔽字和解除屏蔽
man 2 sigprocmask sigset_t set;//自定义信号集 1.NAME sigprocmask, rt_sigprocmask - examine and change blocked signals 2.SYNOPSIS #include <signal.h> /* Prototype for the glibc wrapper function */ int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); 3.parameter 1)how: 1.SIG_BLOCK The set of blocked signals is the union of the current set and the set argument. //设置阻塞 2.SIG_UNBLOCK The signals in set are removed from the current set of blocked signals. It is permissible to attempt to unblock a signal which is not blocked. //取消阻塞 3.SIG_SETMASK The set of blocked signals is set to the argument set. //用自定义set替换mask(PCB中的阻塞信号集) 2)set 自定义set 3)oldset 旧有的mask。值结果参数,一般设置为NULL即可。 sigset_t set;//自定义信号集 4.RETURN VALUE sigprocmask() returns 0 on success and -1 on error. In the event of an error, errno is set to indicate the cause.
-
查看未决信号集函数
man (2) sigpending 1.NAME sigpending, rt_sigpending - examine pending signals 2.SYNOPSIS #include <signal.h> int sigpending(sigset_t *set); 3.PARAMETER set:值结果参数。传出未决信号集 4.RETURN VALUE sigpending() returns 0 on success and -1 on error. In the event of an error, errno is set to indicate the cause.
-
demo
使用函数为2、3、4中函数。 实现阻塞信号。 https://github.com/Panor520/LinuxCode/tree/master/signal/sigset.c
四、信号捕捉函数(重点)
-
signal
注册一个信号捕捉函数。 ANSI定义,不是标准的,有的系统不能用。 man 2 signal 1.NAME signal - ANSI C signal handling 2.SYNOPSIS #include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 3.PARAMETER 1)signum:指定捕捉的信号 2)handler:执行函数 固定写法:(无返回值,最多有一个int参数) void catch(int num);//num为signum的值 4.RETURN VALUE signal() returns the previous value of the signal handler, or SIG_ERR on error. In the event of an error, errno is set to indicate the cause. 5.DEMO https://github.com/Panor520/LinuxCode/tree/master/signal/signal.c
-
sigaction
注册一个信号捕捉函数 标准的。 man 2 sigaction 1.NAME sigaction, rt_sigaction - examine and change a signal action 2.SYNOPSIS #include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); 3.PARAMETER 1)SIGNUM 设置屏蔽的信号 2)act struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask;//一般为 sigemptyset(samask) int sa_flags;//一般为0 void (*sa_restorer)(void); }; (1)void (*sa_handler)(int); 设置指定的捕捉函数。 例: sigaction.sa_handler = catch(); (2)sa_mask: 设置当代码执行到信号捕捉函数时,为了避免再次接收到相同信号时从新执行信号处理函数,就使用这个替换掉PCB中的屏蔽信号集。用来解决多次接收相同信号问题,默认设置为空即可。 例: 若sigaction设置了SIGINT(ctrl+c)信号捕捉函数, 同时sa_mask中加入SIGINT信号,当给进程发送SIGINT信号时, 假设该进程正在执行捕捉函数执行期间,被屏蔽信号多次发送,解除屏蔽后才会再次接收到该信号,且只处理一次(1-32信号特性,34-64会根据触发次数依次响应)。 以此来解决多次接收相同信号问题。 (3)sa_flags 默认值为0,作用:当执行信号捕捉函数时收到相同信号会自动屏蔽。也是解决多次接收相同信号问题。 更多作用详见下第 六 节。 (4)void (*sa_sigaction)(int, siginfo_t *, void *); void (*sa_restorer)(void); 这两个参数忽略即可。 3)oldact 值结果参数,NULL即可。 4.RETURN VALUE sigaction() returns 0 on success; on error, -1 is returned, and errno is set to indicate the error. 5.DEMO https://github.com/Panor520/LinuxCode/tree/master/signal/sigaction.c
-
信号捕捉特性(总结)
1.信号捕捉函数期间,信号屏蔽字 由mask(PCB) --> sa_mask(sigaction设置的),捕捉函数执行结束,恢复为mask(PCB). 2.信号捕捉函数期间,本信号自动被屏蔽(sa_flags)。 3.捕捉函数执行期间,被屏蔽信号多次发送,解除屏蔽后只处理一次。
-
内核实现信号捕捉过程(了解即可)
五、SIGCHLD信号
1.基础
只要子进程状态 发生变化 就会产生 SIGCHLD信号。
用的最多的是子进程结束。
产生条件:
子进程终止时;(最常用)
子进程接收到SIGSTOP信号停止时;
子进程处在停止态,接受到SIGCONT后唤醒时。
DEMO:
https://github.com/Panor520/LinuxCode/tree/master/signal/sigchld.c
六、慢速系统调用中断重启(sigaction函数sa_flags参数的延伸)
系统调用分为两类:
1.慢速系统调用:可能会使进程永远阻塞的一类。
如果在阻塞期间收到一个信号,该信号调用就被中断,不再继续执行;但可以设定执行完该信号后系统调用是否重启。如 read、write、pause、wait
2.其他系统调用:如 getpid、fork
慢速系统调用被中断的相关行为,实际上就是pause行为:
如read:
1.想中断pause,信号不能被屏蔽
2.信号的处理方式必须是捕捉(默认、忽略都不可以)
3.中断后返回-1,设置errno为 EINTR(表“信号被中断”)
可以修改sa_flags参数来设置被信号中断后系统调用是否重启。
sa_flags:SA_INTERRURT 不重启、SA_RESTART 重启。
扩展了解:
sa_flags 还有很多可选参数,适用于不同情况。
如 捕捉到信号后,在执行捕捉函数期间,不希望自动阻塞该信号,可将sa_flags设置为 SA_NODEFER,除非sa_mask中包含该信号(这是PCB中直接阻塞)。
七、会话
-
创建会话
创建会话以下6点注意事项: 1.调用进程不能是进程组组长,该进程变成新会话首进程(session header)。 2.该进程称为一个新进程组的组长进程。 3.需要有root权限。(ubuntu不需要) 4.新会话丢弃原有的控制终端,该会话没有控制终端(不能和用户交互)。 5.该调用进程是组长进程,则出错返回。 6.建立会话时先调用fork,父进程终止,子进程调用setsid。 查看系统中进程会话: ps ajx #TTY列 #值为? 的就是无终端,即会话 #tty*的是文字终端(xshell等连接服务器开启的界面)。 #pts/ 虚拟终端(linux里的terminal工具)
-
会话相关函数
1.gitsid man 2 getsid man 2 getpgid 获取进程所属会话id. 1)NAME getsid - get session ID 2)SYNOPSIS #include <sys/types.h> #include <unistd.h> pid_t getsid(pid_t pid); 3)PARAMETER pid: 一般填当前进程ID。getpid() If pid is 0, getsid() returns the session ID of the calling process.即 getpid()。 4)RETURN VALUE On success, a session ID is returned. On error, (pid_t) -1 will be returned, and errno is set appropriately. 2.setsid man 2 setsid 获取进程所属会话id. 1)NAME setsid - creates a session and sets the process group ID 2)SYNOPSIS #include <sys/types.h> #include <unistd.h> pid_t setsid(void); 3)PARAMETER 无参数 4)RETURN VALUE On success, the (new) session ID of the calling process is returned. On error, (pid_t) -1 is returned, and errno is set to indicate the error. 3.DEMO https://github.com/Panor520/LinuxCode/tree/master/signal/session.c
八、利用会话创建守护进程
-
基础
Daemon(精灵)进程,是Linux中的后台服务进程。 通常独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件,一般名字以d结尾。 如 httpd、sshd、vsftpd。 也有例外如 telent。 Linux后台的一些系统服务进程,没有控制终端,不能和用户交互。 不受用户登录、注销的影响,一直在运行着,它们都是守护进程。 如 ftp服务器、nfs服务器等。
-
创建守护进程
创建守护进程,最关键的一步就是调用setsid函数创建一个新的session,并成为session leader
1.创建子进程,父进程退出 所有工作在子进程中进行形式上脱离了控制终端 2.在子进程中创建会话 setsid()函数。 使子进程完全独立出来,脱离控制。 3.改变当前目录为根目录 chdir()函数 防止占用可卸载的文件系统 也可以换成其他路径 注意:程序执行时是有工作位置的,若工作在U盘路径下,U盘一拔,程序就终止了,此步就为了避免这种事情发生。 4.重设文件权限掩码 umask()函数. man 2 umask 防止继承的文件创建屏蔽字拒绝某些权限 增加守护进程灵活性. 这里设置的是PCB中的umask。并不是系统的。 5.关闭文件描述符 继承的打开文件不会用到,浪费系统资源,无法卸载 指的是 0 1 2文件描述符,守护进程用不到。但一般重定向到 /dev/null,使得下次open时依然从3开始。 6.开始执行守护进程核心工作守护进程退出处理程序模型 另:守护进程的管理 一般要另外写一个脚本进行维护。 DEMO:创建守护进程 https://github.com/Panor520/LinuxCode/tree/master/signal/myd.c 利用ps ajx |grep myd 查看守护进程