Linux系统编程之----》信号

  1 "===信号========================================================================================================================"
  2 一.信号的概念:
  3     1.本质:
  4         软中端;信号通过内核发送,内核处理的。
  5     2.特性:
  6         1)简单,2)不能携带大量信息; 3)满足某一条件
  7     3.信号机制:
  8         软中端;信号通过内核发送,内核处理的;通过软件的方法实现的,有较强的延迟性。
  9         
 10     3.四要素:
 11         1.信号编号, 2.信号名称, 3。默认处理动作,4.对应的事件。
 12         
 13         2.信号的默认处理动作(5种):
 14             1.终止进程(term);2.忽略(Ign:信号被处理丢弃);3.终止进程并产生core文件(core);
 15             4.Stop(暂停);  5.Cont (继续);
 16 
 17     4.名词称呼
 18         1.信号的状态:
 19             1.产生;
 20             2.未决:处于产生和递达中间,由于阻塞不能递达、处理。
 21             3.递达;信号递达后内核会立即处理。(也就是说,他俩经常绑定在一起,信号递达就代表这信号被处理。)
 22             4.处理:(信号必须递达)
 23                 1.忽略处理(信号已经处理,丢弃操作); 2.执行默认操作,  3.捕捉(不执行默认操作,来指定操作让其执行;)
 24             
 25         2.未决信号集:(pending)
 26             在PCB中,以位图的方式存在。
 27             记录信号是否被产生,并且被处理。 内核处理信号的依据。用户不能直接修改未决信号集。
 28 
 29         3.阻塞信号集/信号屏蔽字:(mask)
 30             在PCB中,以位图的方式存在。    mask影响pending;用户只可以通过mask来影响pending。
 31             记录信号是否被设置屏蔽。用户影响信号的依据。
 32 
 33         4. 1-31 号信号,常规信号(不支持排队); 34-64 号信号,实时信号(支持排队)
 34 
 35         5.特殊信号:(两个);
 36             9 号、 19 号信号;不允许捕捉,忽略,甚至不能设置屏蔽。
 37 
 38         6.产生断错误的方式(三种);
 39             1.访问非访问区域。0x1000   printf();            mmu---没有映射该虚拟内存
 40             2.对只读区域进行写操作。 char* p = hello; p[1] = 'H';      mmu---映射的虚拟内存对应的物理内存权限不足。 0 内核权限; 3 用户权限
 41             3.内存溢出;  char buf[10];  buf[10] = '10'; buf[100] = '28';  mmu---没有映射该虚拟内存。
 42 
 43 二.产生信号:
 44     1.常见的产生信号方法:
 45         1.按键产生:ctrl+c;
 46         2.系统调用:如kill
 47             kill(pid_t pid, int sig); 向指定进程或进程组发送指定信号;
 48                 第一个参数: pid > 0; 向指定进程发送指定信号;    pid == 0; 向调用kill函数的进程组的所有进程发送该信号
 49                             pid == -1; 发送信号给系统中有权限的所有进程;  pid < -1; 发送信号给指定的进程组|pid|;
 50                 第二个参数: 信号。(不同的平台环境下,信号的编号不同;但是信号的宏定义相同,所以一般使用宏名)。
 51 
 52             raise() 给当前进程发送指定信号;
 53             abort() 向当前进程发送SIGABRT信号;
 54         3.软件条件产生:如,定时器alarm;
 55             alarm:每个进程有且只有唯一一个定时器。
 56             返回值特殊:上次定时剩余的时间。定时(采用自然定时),与进程状态无关!!!,无论处于何种状态,都会计时。;
 57             取消闹钟: alarm(0);    实际执行时间 = 系统时间+用户时间+等待时间。
 58              定时的单位是:秒。
 59             setitimer(); 定时单位单位:微妙;
 60                 三个参数:
 61 
 62 
 63         4.硬件异常产生:如,非法内存访问(段错误);内存对齐出错(总线错误);除0(浮点数除外)。
 64         5.命令产生:如kill命令
 65 
 66 三。信号操作函数;
 67     1.信号集 set 68         sigset_t   set;
 69 
 70         sigemptyset(&set)  清空集合  
 71 
 72         sigfillset(&set)  置1集合  
 73 
 74         sigaddset(&set, 待添加的信号名称) 添加信号到集合中
 75 
 76         sigdelset(&set, 待删除的信号名称) 删除信号到集合中
 77 
 78         sigismember(&set, 待判断的信号名称)判断信号是否在集合中  -- 》 1:在 0:不在; -1;错
 79 
 80     2.mask(信号屏蔽字/阻塞信号集)操作
 81         sigprocmask();用来屏蔽信号、解除信号;其本质是读取或修改进程的信号屏蔽字(PCB中);
 82         "注意:屏蔽信号只是将信号处理延后执行(延至解除屏蔽), 而忽略表示将信号丢弃。"
 83         参数:第一个参数 how 的取值:假设当前的信号屏蔽字为mask;
 84                 1.SIG_BLOCK:当how设置为此时,set表示要屏蔽的信号。相当于mask= mask|set;
 85                 2.SIG_UNBLOCK:当how 设置为此时,set 表示要解除的信号。相当于:mask = mask & ~set;
 86                 3.SIG_SETMASK; 当how 设置为此时,set = mask;
 87             第二个参数: set 传入参数:用来操作mask的set集合。
 88             第三个参数: oldset 传出参数。记录旧有的mask状态。
 89 
 90     3.pending操作
 91         int sigpending(sigset_t *set);传出参数
 92         参数:获取未决信号集
 93         返回值:存在返回 1; 不存在返回 0 94 
 95 四。信号捕捉
 96     1.signal:(注册信号的捕捉处理函数)
 97         参数:1.信号编号 2.捕捉后调用的执行函数(是一个函数指针。即回调函数);
 98         返回值:捕捉的函数句柄。
 99 
100     2.sigaction: 1)信号捕捉函数执行期间,本信号被自动屏蔽(取决于:sa_flags) 2)信号捕捉函数执行期间,信号多次产生,只记录一次。
101 
102      函数原型:int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
103          参数解析: 1)第一个参数:信号名称或编号;要捕捉的信号。
104                     2)第二个参数:新处理动作传入,是一个结构体。
105                            1)sa_handler: 1)函数指针,捕捉信号后执行的回调函数;2) SIG_IGN 表示忽略; 3)SIG_DFL 表示执行默认操作。
106                         2)sa_mask; 信号屏蔽字/阻塞信号集;捕捉函数执行期间的屏蔽字,此中的信号在捕捉函数执行期间自动屏蔽。
107                         3)sa_flags: 1) 0; 表示:信号捕捉函数执行期间,要捕捉的信号被自动屏蔽(即第一个参数)。
108                                     2)SA_SIGINFO: 选用sa_sigaction来指定捕捉函数;
109                                     3)SA_INTERRUPT: 系统调用被信号中断后,不重启;
110                                     4)SA_REATART: 系统调用被信号中断后,自动重启。
111                                     5)SA_NODEFER: 在捕捉函数执行期间不自动屏蔽捕捉的信号;
112 
113                        4) sa_sigaction: 函数指针, 三个参数,void (*sa_sigaction)(int, siginfo_t*, void*);指定带参数的信号捕捉函数。
114                     3)第三个参数:旧处理动作传出;
115             返回值: 0 表示成功, -1 表示失败,并设置errno ;
116     3.信号捕捉特性:
117         1)捕捉函数执行期间,屏蔽字由sa_mask指定。
118         2)捕捉函数执行期间,被捕捉的信号自动屏蔽; 由sa_flags = 0 决定;
119         3)"捕捉函数执行期间,常规信号不支持排队,多次产生只记录一次。"    
120         常规信号为:1-31 信号; 实时信号为 34-64 信号。
121         详细过程:进程正常运行时,默认PCB中有一个信号屏蔽字,假定为☆,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,
122            要调用该函数。而该函数有可能执行很长时间,在这期间所屏蔽的信号不由☆来指定。而是用sa_mask来指定。调用完信号处理函数,再恢复为☆。
123     
124     4.信号内核实现捕捉函数思想:
125         信号捕捉函数是 回调函数; 由内核在信号产生、递达之后负责回调。
126         调用结束应该返回内核空间,再返回用户空间。
127 
128     5.内核实现信号捕捉的一般过程
129         1)回调捕捉函数; 2)调用结束先返回内核。
130 
131         “注意:此处加图片:”
132 五。竟态条件(时序竟态)
133     1.pause() 函数:主动造成进程挂起,等待信号唤醒。调用该系统调用的进程将处于阻塞状态(主动放弃CPU),知道有信号到达将其唤醒。
134         int pause(void); 返回值:-1,并设置errno == EINTR;
135         注意:
136                 1) 唤醒pause()函数的信号不能执行默认操作、忽略、屏蔽。原因:
137                     1)如果信号的默认动作是终止进程,则进程终止,pause函数不会有机会被调用。
138                     2)如果信号默认动作是忽略、屏蔽,则进程继续挂起,pause不会调用。
139                 2)如果信号的处理动作是捕捉,则【捕捉函数处理完后,pause被调用返回-1,并且errno == EINTR,表示被信号终端,即唤醒】;
140                 3)综上所诉:能唤醒pause()函数的信号,只能是被捕捉的信号;
141                 
142     2.时序竟态:
143         1.时序问题产生的原因: 1)系统负载严重; 2) 信号不可靠机制;
144 
145         2.解决时序问题:
146             1)使用原子操作;将解除信号屏蔽与挂起等待合并成一个步骤。sigsuspend() 函数具有这个功能。在对时序要求严格的场合下都应用
147                 sigsuspend 替换 pause。"(解除信号屏蔽+挂起等待信号)----》原子操作---》sigsuspend()函数"
148                 int sigsuspend(const sigset_t *mask); 挂起等待信号。 
149                     函数详解: 1)主动造成进程挂起,等待信号; 2)在这个函数调用期间屏蔽字由它的参数决定; 3)函数调用结束屏蔽字恢复为原值;4)原子操作
150             2)提早预见,主动规避。这种错误,没法用gdb调试出来。
151 
152     3.全局变量的异步IO:
153         存在的问题:
154             1.多个进程对 同一 个全局变量进行写操作,存在问题。(如:信号回调函数中的全局变量和主函数中的全局变量为同1个,内核进程和主进程都对
155                 全局变量进行了写操作,非常容易出问题)。
156             2.写操作时没有任何 同步 机制。
157 
158             “注意:此处有代码,父子进程数数,代码1,主进程和内核对同一个全局变量进行写操作,出问题;
159                                              代码2,全局变量只由主进程进行一次赋值操作后,主进程和内核进程对它的只读操作,没有出问题”
160 
161     4.可重入、不可重入函数:
162         注意:
163             1)不可重入函数:函数内部含有全局变量、静态变量,使用malloc、free164             "2)信号捕捉函数应设计为可重入函数;(与上面的相反的就是可重入函数)。"
165             3)信号处理程序调用的可重入的系统调用函数;可参阅 man 7 signal;
166             4)没有包含在上述列表中的系统调用函数大多数是不可重入的,其原因是:
167                 a) 使用了静态数据结构,
168                 b) 调用了malloc 或free
169                 c) 是标准I/O函数 ;
170 
171 
172 六。SIGCHLD信号 -------IGN(该信号的默认动作是忽略)
173     1.该信号的产生:
174         1)子进程终止;
175         2)子进程接收到SIGSTOP信号停止时;
176         3)子进程处在停止态,接收到SIGCONT后唤醒时;
177         综上所述:只要子进程的状态发生变化,会对父进程发出SIGCHLD信号,默认处理的动作是忽略。
178 
179     2.借助 SIGCHLD 子进程回收。
180         1)wait()--------阻塞等待子进程结束;
181         2)waitpid()--------设置不阻塞(第二个参数WNOHANG)。如果这样设置,要设置轮询,才能将子进程回收。
182         3)信号捕捉---子进程回收;
183 
184     3.注意: 
185         1)子进程继承了父进程的 信号处理动作和 信号屏蔽字(mask),但并没有继承父进程的 未决信号集(sigpending)。
186         2)注册信号捕捉函数的位置,和与其他进程发出信号的配合(必须在捕捉信号发出之前完成注册)。
187         “参考代码”
188 
189 七。信号传参:
190     1.int sigqueue(pid_t pid, int sig, const union sigval value); 成功返回0; 失败-1191         sigqueue 向指定进程发送信号的同时携带参数(即它的第三个参数);
192         "注意:携带的数据。如果携带的是 地址:需注意,不同进程之间虚拟地址空间各自独立,将当前进程地址传递给另一进程没有实际意义"
193     2.捕捉信号传参
194         1)用该函数捕捉信号传递的数据: int sigaction(int signum, const struct sigaction* act, struct sigaction* oldact);    
195         2)当注册信号捕捉函数,希望获得更多关于该信号的信息,不应使用 sa_handler, 而应使用 sa_sigaction。 但同时 sa_flags 必须指定SA_SIGINFO.
196             siginfo_t 是一个成员十分丰富的结构体,携带者该信号的各种相关信息。
197             
198 八。中断系统调用
199     1.慢速系统调用(造成当前系统永久阻塞的)
200         1)如read, write, wait, pause, sigsuspend, 都是慢速系统调用 ;
201         2)概念: 可能使进程永远阻塞的一类系统调用;如果再阻塞期间收到一个信号,该系统调用被中断,不再继续执行;也可以通过设置,使其中断后再重新启动。
202         3)特点:慢速系统调用被中断的相关行为,实际上就是pause的行为: 如read()
203             1) 信号不能被屏蔽, 执行默认动作, 忽略;
204             2)信号的处理方式必须被捕捉;    
205             3)中断后返回-1, 设置errno为 EINTR(表示:“被信号终端”);
206 
207         4)设置sa_flags 参数来决定它终端后的行为:
208                 0           表示被捕捉的信号,在捕捉函数执行期间被自动屏蔽
209                 SA_NODEFER    表示被捕捉的信号,在捕捉函数执行期间不被自动屏蔽
210                 SA_INTERRURT 表示慢速系统调用,若被信号打断,不再重启
211                 SA_RESTART  表示慢速系统调用,若被信号打断,重启 
212                 SA_SIGINFO  表示捕捉函数是3参的sigaction,需指定为这个宏名
213 
214     2.其他系统调用:
215         如Lgetpid(), fork();
216 
217 十。终端
218     1.字符终端(虚拟终端),图形终端(伪终端),网络终端(伪终端)。
219     2.线路规程(line discipline):
220         像一个过滤器,对于某些特殊字符并不是让它直接通过,而是做特殊处理,比如ctrl+c,对应的字符并不会被用户程序read读到,而是被线路规程截获,
221         解释称SIGINT信号发送给前台进程,通常会使该进程停止。线路规程应该过滤哪些字符和做哪些特殊处理是可以配置的。
222     3.在终端设备(如键盘)上输入内容进入进程的顺序:
223         终端设备--》终端设备驱动--》line discipline(线路规程过滤)---》系统调用(普通内容)---》用户进程
224                                                                  ---》内核特殊字符(解释称信号)--》内核 前台进程。;
225     4.一套伪终端由一个主设备(PTY Master)和一个从设备(PTY Slave)组成;主设备在概念上相当于键盘、显示器,只不过他不是一个真正的硬件而是一个内核模块;
226         操作它的也不是用户,而是另外一个内核模块。        网络终端或图形终端窗口的shell进程以及它启动的其他进程都认为自己的控制终端是伪终端从设备。                
227 
228 十一。进程组        
229     pid_t getpgid(pid_t pid);            返回指定进程组的ID
230     pid_t getpgrp(void);                 返回调用函数进程组的ID
231     int setpgid(pid_t pid, pid_t pgid);    设置某个进程的进程组ID; 成功返回0;失败-1232     1.当父进程创建子进程时,默认子进程与父进程属于同一进程组。其进程组ID==父进程的ID。
233     2.进程组的生命周期: 只要进程组中有一个进程存在,该进程组就存在,"与组长进程是否终止无关"234                         进程组创建到最后一个进程离开(终止或转移到另一个进程组)。
235     3.一个进程可以为自己或子进程设置进程组ID。
236     
237     4.修改一个进程的进程组ID需注意:
238         )如改变子进程为新的组,应在fork后,exec前。
239         2)权限问题。非root用户只能改变自己和它创建的子进程的进程组。
240 
241 十二。会话。
242     pid_t getsid(pid_t pid);获取该进程的会话ID(如果查看当前进程的会话ID,参数为0就行。自己测试吧)。成功:返回会话ID; 失败:返回-1,并设置errno .
243     pid_t setpid(void); 创建一个会话,以自己的ID为新会话的ID,同时也会成为一个新的进程组。 成功:返回调用进程的ID;失败,-1244     创建会话应注意:
245         1.创建会话的进程不能是进程组组长;同时创建的进程变成新会话首进程(即组长)。
246         2.创建完后该进程成为新进程组的组长进程。
247         3.需要有root权限;(ubauntu不需要)
248         4.新会话丢弃原有的控制终端,该会话没有控制终端(也就无法与用户进行交互)。
249         5.该调用进程是组长进程,则出错返回。(与1 相同)。
250         6.新建会话时,先调用fork,父进程退出;子进程创建会话(调用setsid()函数)。
251         
252     "注意:子进程不会随着成为新的进程组组长,而其父进程发生改变。"
253 
254 
255 十三:守护进程:
256     1.Daemon(精灵)进程,即守护进程。
257         特征:  1.位于Linux后台,服务进程。
258                 2.独立于控制终端(即没有控制终端),周期性的执行某任务,等待某事件
259                 3.不受用户的注销、登录而终止;(注意:不是关机)。
260                 4.通常采用以d结尾的名字。
261                 
262     2.创建守护进程的模型:
263         1.创建子进程,父进程退出
264             所有工作在子进程中进行,形式上脱离了控制终端。
265         2.在子进程中创建新会话
266             setsid()函数
267             使子进程完全独立出来,脱离控制
268 
269         3.改变当前目录为根目录;
270             chdir()函数;
271             目的:防止占用可卸载的文件系统。
272             也可换成其他目录,只要该目录稳定,不会被卸载(当时卸载的优盘目录)。
273 
274         4.重设文件掩码;
275             umask()函数:
276             目的: 防止继承的文件创建屏蔽字拒绝某些权限;
277                    增加守护进程的灵活性
278 
279         5.关闭文件描述符
280             继承的打开不会用到,浪费系统资源,无法卸载。
281 
282         6.执行守护进程核心工作
283 
284         7.设置守护进程退出。
285 
286 
287 
288 
289 
290 
291 
292 "===信号========================================================================================================================"
原文地址:https://www.cnblogs.com/yyx1-1/p/5831488.html