门户网站建设方案ppt 百度文库,网站每年需要续费吗,上海公司沪牌价格,免费vi模板网站信号介绍 信号的概念 信号是信息的载体#xff0c;Linux/UNIX 环境下#xff0c;古老、经典的通信方式#xff0c; 现下依然是主要的通信手段。 信号在我们的生活中随处可见#xff0c;例如#xff1a; 古代战争中摔杯为号#xff1b; 现代战争中的信号弹#x…信号介绍 信号的概念 信号是信息的载体Linux/UNIX 环境下古老、经典的通信方式 现下依然是主要的通信手段。 信号在我们的生活中随处可见例如 古代战争中摔杯为号 现代战争中的信号弹 体育比赛中使用的信号枪… 信号的特点 简单 不能携带大量信息 满足某个特点条件才会产生 2 信号的机制 进程A给进程B发送信号进程B收到信号之前执行自己的代码收到信号后不管执行到程序的什么位置都要暂停运行去处理信号处理完毕后再继续执行。与硬件中断类似——异步模式。但信号是软件层面上实现的中断早期常被称为“软中断”。 每个进程收到的所有信号都是由内核负责发送的。 进程A给进程B发送信号示意图
2.1 信号的状态 信号有三种状态产生、未决和递达。 信号的产生 按键产生如Ctrlc、Ctrlz、Ctrl 系统调用产生如kill、raise、abort 软件条件产生如定时器alarm 硬件异常产生如非法访问内存(段错误)、除0(浮点数例外)、内存对齐出错(总线错误) 命令产生如kill命令 未决产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态。 递达递送并且到达进程。 2.2 信号的处理方式 执行默认动作 忽略信号(丢弃不处理) 捕捉信号(调用用户的自定义的处理函数) 2.3 信号的特质 信号的实现手段导致信号有很强的延时性但对于用户来说时间非常短不易察觉。 Linux内核的进程控制块PCB是一个结构体task_struct, 除了包含进程id状态工作目录用户id组id文件描述符表还包含了信号相关的信息主要指阻塞信号集和未决信号集。 注:表示PCB的task_struct结构体定义在 /usr/src/linux-headers-4.4.0-97/include/linux/sched.h:1390 2.4 阻塞信号集和未决信号集 Linux内核的进程控制块PCB是一个结构体这个结构体里面包含了信号相关的信息主要有阻塞信号集和未决信号集。 阻塞信号集中保存的都是被当前进程阻塞的信号。若当前进程收到的是阻塞信号集中的某些信号这些信号需要暂时被阻塞不予处理。 信号产生后由于某些原因(主要是阻塞)不能抵达这类信号的集合称之为未决信号集。在屏蔽解除前信号一直处于未决状态若是信号从阻塞信号集中解除阻塞则该信号会被处理并从未决信号集中去除。 2.5 信号的四要素 通过man 7 signal可以查看信号相关信息 1 信号的编号 使用kill -l命令可以查看当前系统有哪些信号不存在编号为0的信号。其中1-31号信号称之为常规信号也叫普通信号或标准信号34-64称之为实时信号驱动编程与硬件相关。 2 信号的名称 3 产生信号的事件 4信号的默认处理动作 Term终止进程 Ign忽略信号 (默认即时对该种信号忽略操作) Core终止进程生成Core文件。(查验死亡原因用于gdb调试) Stop停止暂停进程 Cont继续运行进程 特别需要注意的是The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored. 几个常用到的信号 SIGINT、SIGQUIT、SIGKILL、SIGSEGV、SIGUSR1、SIGUSR2、SIGPIPE、SIGALRM、SIGTERM、SIGCHLD、SIGSTOP、SIGCONT 3 信号相关函数 3.1 signal函数 函数作用注册信号捕捉函数 函数原型 typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 函数参数 signum信号编号 handler信号处理函数 3.2 kill函数/命令 描述给指定进程发送指定信号 kill命令kill -SIGKILL 进程PID kill函数原型int kill(pid_t pid, int sig); 函数返回值 成功0 失败-1设置errno 函数参数 sig信号参数不推荐直接使用数字应使用宏名因为不同操作系统信号编号可能不同但名称一致。 pid参数 pid 0: 发送信号给指定的进程。 pid 0: 发送信号给与调用kill函数进程属于同一进程组的所有进程。 pid -1: 取|pid|发给对应进程组。 pid -1发送给进程有权限发送的系统中所有进程。 进程组每个进程都属于一个进程组进程组是一个或多个进程集合他们相互关联共同完成一个实体任务每个进程组都有一个进程组长默认进程组ID与进程组长ID相同。 3.3 abort函数raise函数 raise函数 函说描述给当前进程发送指定信号(自己给自己发) 函数原型int raise(int sig); 函数返回值成功0失败非0值 函数拓展raise(signo) kill(getpid(), signo); abort函数 函数描述给自己发送异常终止信号 6) SIGABRT并产生core文件 函数原型void abort(void); 函数拓展abort() kill(getpid(), SIGABRT); 3.4 alarm函数 函数原型unsigned int alarm(unsigned int seconds); 函数描述设置定时器(闹钟)。在指定seconds后内核会给当前进程发送14SIGALRM信号。进程收到该信号默认动作终止。每个进程都有且只有唯一的一个定时器。 函数返回值返回0或剩余的秒数无失败。例如
常用操作取消定时器alarm(0)返回旧闹钟余下秒数。 alarm使用的是自然定时法与进程状态无关就绪、运行、挂起(阻塞、暂停)、终止、僵尸…无论进程处于何种状态alarm都计时。
练习题1编写一个程序测试alarm函数 练习题2编写程序测试你的电脑1秒种能数多个数字。 使用time命令查看程序执行的时间。程序运行的瓶颈在于IO优化程序首选优化IO。 实际执行时间 系统时间 用户时间 损耗时间 损耗的时间主要来来自文件IO操作IO操作会有用户区到内核区的切换切换的次数越多越耗时。 3.5 setitimer函数 函数原型 int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value); 函数描述 设置定时器(闹钟)可代替alarm函数精度微秒us可以实现周期定时。 函数返回值 成功0 失败-1设置errno值 函数参数 which指定定时方式 自然定时ITIMER_REAL → 14SIGALRM计算自然时间 虚拟空间计时(用户空间)ITIMER_VIRTUAL → 26SIGVTALRM 只计算进程占用cpu的时间 运行时计时(用户内核)ITIMER_PROF → 27SIGPROF计算占用cpu及执行系统调用的时间 new_valuestruct itimerval, 负责设定timeout时间。 itimerval.it_value: 设定第一次执行function所延迟的秒数 itimerval.it_interval: 设定以后每几秒执行function struct itimerval { struct timerval it_interval; // 闹钟触发周期 struct timerval it_value; // 闹钟触发时间 }; struct timeval { long tv_sec; // 秒 long tv_usec; // 微秒 } old_value 存放旧的timeout值一般指定为NULL 练习: 使用setitimer实现每隔一秒打印一次hello, world。
4 信号集相关 4.1 未决信号集和阻塞信号集的关系 阻塞信号集是当前进程要阻塞的信号的集合未决信号集是当前进程中还处于未决状态的信号的集合这两个集合存储在内核的PCB中。 下面以SIGINT为例说明信号未决信号集和阻塞信号集的关系 当进程收到一个SIGINT信号信号编号为2首先这个信号会保存在未决信号集合中此时对应的2号编号的这个位置上置为1表示处于未决状态在这个信号需要被处理之前首先要在阻塞信号集中的编号为2的位置上去检查该值是否为1 如果为1表示SIGNIT信号被当前进程阻塞了这个信号暂时不被处理所以未决信号集上该位置上的值保持为1表示该信号处于未决状态 如果为0表示SIGINT信号没有被当前进程阻塞这个信号需要被处理内核会对SIGINT信号进行处理执行默认动作忽略或者执行用户自定义的信号处理函数并将未决信号集中编号为2的位置上将1变为0表示该信号已经处理了这个时间非常短暂用户感知不到。 当SIGINT信号从阻塞信号集中解除阻塞之后该信号就会被处理。
4.2 信号集相关函数 由于信号集属于内核的一块区域用户不能直接操作内核空间为此内核提供了一些信号集相关的接口函数使用这些函数用户就可以完成对信号集的相关操作。 信号集是一个能表示多个信号的数据类型sigset_t setset即一个信号集。既然是一个集合就需要对集进行添加、删除等操作。 sigset_t类型的定义在signal.h文件中的第49行处: typedef __sigset_t sigset_t; __sigset_t的定义在sigset.h文件中的2627行处:
define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct { unsigned long int __val[_SIGSET_NWORDS]; } __sigset_t;
上述变量类型的定义的查找有个小窍门 可以执行gcc的预处理命令 gcc -E test.c -o test.i 这样头文件就会展开可以直接到test.i文件中看到相关变量类型的定义。
信号集相关函数 int sigemptyset(sigset_t *set); 函数说明将某个信号集清0 函数返回值成功0失败-1设置errno int sigfillset(sigset_t *set); 函数说明将某个信号集置1 函数返回值成功0失败-1设置errno int sigaddset(sigset_t *set, int signum); 函数说明将某个信号加入信号集合中 函数返回值成功0失败-1设置errno int sigdelset(sigset_t *set, int signum); 函数说明将某信号从信号清出信号集 函数返回值成功0失败-1设置errno int sigismember(const sigset_t *set, int signum); 函数说明判断某个信号是否在信号集中 函数返回值在1不在0出错-1设置errno sigprocmask函数 函数说明用来屏蔽信号、解除屏蔽也使用该函数。其本质读 取或修改进程控制块中的信号屏蔽字阻塞信号集。 特别注意屏蔽信号只是将信号处理延后执行(延至解除屏蔽)而忽略表示将信号丢弃处理。 函数原型int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); 函数返回值成功0失败-1设置errno 函数参数 how参数取值假设当前的信号屏蔽字为mask SIG_BLOCK: 当how设置为此值set表示需要屏蔽的信号。相当于 mask mask|set SIG_UNBLOCK: 当how设置为此set表示需要解除屏蔽的信号。相当于 mask mask ~set SIG_SETMASK: 当how设置为此set表示用于替代原始屏蔽及的新屏蔽集。相当于mask set若调用sigprocmask解除了对当前若干个信号的阻塞则在sigprocmask返回前至少将其中一个信号递达。 set传入参数是一个自定义信号集合。由参数how来指示如何修改当前信号屏蔽字。 oldset传出参数保存旧的信号屏蔽字。 sigpending函数 函数原型int sigpending(sigset_t *set); 函数说明读取当前进程的未决信号集 函数参数set传出参数 函数返回值成功0失败-1设置errno 练习编写程序设置阻塞信号集并把所有常规信号的未决状态打印至屏幕。 5 信号捕捉函数 signal函数 sigaction函数 函数说明 注册一个信号处理函数 函数原型 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); 函数参数 signum捕捉的信号 act 传入参数新的处理方式。 oldact 传出参数旧的处理方式
struct sigaction { void (*sa_handler)(int); // 信号处理函数 void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理函数 sigset_t sa_mask; //信号处理函数执行期间需要阻塞的信号 int sa_flags; //通常为0表示使用默认标识 void (*sa_restorer)(void); };
总结 sa_handler指定信号捕捉后的处理函数名(即注册函数)。也可赋值为SIG_IGN表忽略 或 SIG_DFL表执行默认动作 sa_mask: 用来指定在信号处理函数执行期间需要被屏蔽的信号特别是当某个信号被处理时它自身会被自动放入进程的信号掩码因此在信号处理函数执行期间这个信号不会再度发生。注意仅在处理函数被调用期间屏蔽生效是临时性设置。 sa_flags通常设置为0使用默认属性。 sa_restorer已不再使用
练习编写程序使用sigaction函数注册信号捕捉函数并使用这个程序验证信号是否支持排队。 知识点: 信号处理不支持排队: 在XXX信号处理函数执行期间, XXX信号是被阻塞的, 如果该信号产生了多次, 在XXX信号处理函数结束之后, 该XXX信号只被处理一次. 在XXX信号处理函数执行期间,如果阻塞了YYY信号, 若YYY信号产生了多次, 当XXX信号处理函数结束后, YYY信号只会被处理一次.
内核实现信号捕捉的过程 如果信号的处理动作是用户自定义函数在信号递达时就调用这个函数这称为捕捉信号。由于信号处理函数的代码是在用户空间的处理过程比较复杂举例如下 1.用户程序注册了SIGQUIT信号的处理函数sighandler。 2.当前正在执行main函数这时发生中断或异常切换到内核态。 3.在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。 4.内核决定返回用户态后不是恢复main函数的上下文继续执行而是执行sighandler函数sighandler和main函数使用不同的堆栈空间它们之间不存在调用和被调用的关系是两个独立的控制流程。 5.sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。 6.如果没有新的信号要递达这次再返回用户态就是恢复main函数的上下文继续执行了。
6 SIGCHLD信号 6.1 产生SIGCHLD信号的条件 子进程结束的时候 子进程收到SIGSTOP信号 当子进程停止时收到SIGCONT信号 6.2 SIGCHLD信号的作用 子进程退出后内核会给它的父进程发送SIGCHLD信号父进程收到这个信号后可以对子进程进行回收。 使用SIGCHLD信号完成对子进程的回收可以避免父进程阻塞等待而不能执行其他操作只有当父进程收到SIGCHLD信号之后才去调用信号捕捉函数完成对子进程的回收未收到SIGCHLD信号之前可以处理其他操作。 6.3 使用SIGCHLD信号完成对子进程的回收 练习父进程创建三个子进程然后让父进程捕获SIGCHLD信号完成对子进程的回收。 注意点 有可能还未完成信号处理函数的注册三个子进程都退出了。 解决办法可以在fork之前先将SIGCHLD信号阻塞当完成信号处理函数的注册后在解除阻塞。 当SIGCHLD信号函数处理期间, SIGCHLD信号若再次产生是被阻塞的,而且若产生了多次, 则该信号只会被处理一次, 这样可能会产生僵尸进程。 解决办法: 可以在信号处理函数里面使用while(1)循环回收, 这样就有可能出现捕获一次SIGCHLD信号但是回收了多个子进程的情况从而可以避免产生僵尸进程。