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

时间:2022-03-07 03:51:24
  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、free
164 "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; 失败-1
191 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;失败-1
232 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;失败,-1
244 创建会话应注意:
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 "===信号========================================================================================================================"