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 1)如改变子进程为新的组,应在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 "===信号========================================================================================================================"