Linux系统编程——信号

一、信号概念

  1. 信号机制

    	信号是软件层面上的“中断”。一旦产生。无论程序执行到什么位置,必须立即停止运行,处理信号,处理结束,再继续执行后续指令。
    	
    	所有信号的产生和处理都由【内核】完成。
    
  2. 与信号相关的事件和状态

    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)阻塞信号集(信号屏蔽字):
    			将某些信号加入集合,当屏蔽某信号后,再收到该信号,相应的处理在解除屏蔽后。
    
  3. 信号编号

    kill -l  #查看当前系统可使用信号
    
    没有编号为0的信号。
    
    1至31称为常规信号,每个信号都有对应的默认事件或处理动作。Linux系统编程主要学这部分即可。
    
    34至64称为实时信号,常用于驱动编程或硬件相关,每个信号没有对应的默认事件或处理动作
    
  4. 信号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.
    
  5. 常规信号一览表
    在这里插入图片描述

二、信号的产生

  1. 终端按键产生信号
    不赘述

  2. 硬件异常产生信号
    不赘述

  3. 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
    
  4. 软件条件产生信号

    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
    

三、信号集操作函数

  1. 设置阻塞信号集的方法

在这里插入图片描述

PCB自带阻塞信号集,
利用自定义信号集和PCB自带信号集进行【位或】操作,或者利用自定义信号集【替换】PCB自带信号集,即可设置新的阻塞信号集。(详见下面sigprocmask函数)
这一步利用【位或】操作进行。

阻塞信号集生命周期就是进程PCB的声明周期。
  1. 信号集操作函数

    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. 
    
    
    
    
  2. 利用信号集设置屏蔽字和解除屏蔽

    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.
    
    
        
    
  3. 查看未决信号集函数

    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.
    
    
  4. demo

    使用函数为234中函数。
    实现阻塞信号。
    
    https://github.com/Panor520/LinuxCode/tree/master/signal/sigset.c
    

四、信号捕捉函数(重点)

  1. 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
    
  2. 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
    
  3. 信号捕捉特性(总结)

    1.信号捕捉函数期间,信号屏蔽字 由mask(PCB) --> sa_mask(sigaction设置的),捕捉函数执行结束,恢复为mask(PCB).
    2.信号捕捉函数期间,本信号自动被屏蔽(sa_flags)。
    3.捕捉函数执行期间,被屏蔽信号多次发送,解除屏蔽后只处理一次。
    
  4. 内核实现信号捕捉过程(了解即可)
    在这里插入图片描述

五、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中直接阻塞)。			

七、会话

  1. 创建会话

    创建会话以下6点注意事项:
    	1.调用进程不能是进程组组长,该进程变成新会话首进程(session header)。
    	2.该进程称为一个新进程组的组长进程。
    	3.需要有root权限。(ubuntu不需要)
    	4.新会话丢弃原有的控制终端,该会话没有控制终端(不能和用户交互)。
    	5.该调用进程是组长进程,则出错返回。
    	6.建立会话时先调用fork,父进程终止,子进程调用setsid。
    
    查看系统中进程会话:
    	ps ajx 
    	#TTY列 
    		#值为? 的就是无终端,即会话
    		#tty*的是文字终端(xshell等连接服务器开启的界面)。
    		#pts/  虚拟终端(linux里的terminal工具)
    
  2. 会话相关函数

    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
    
    

八、利用会话创建守护进程

  1. 基础

    	Daemon(精灵)进程,是Linux中的后台服务进程。
    通常独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件,一般名字以d结尾。 
    如 httpd、sshd、vsftpd。 也有例外如 telent。
    
    	Linux后台的一些系统服务进程,没有控制终端,不能和用户交互。
    不受用户登录、注销的影响,一直在运行着,它们都是守护进程。
    如 ftp服务器、nfs服务器等。
    
    
    
    
  2. 创建守护进程

    创建守护进程,最关键的一步就是调用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 查看守护进程
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值