网站怎么做成二维码,小程序制作流程微信,网站建设初步课程介绍,建设网站空间怎么预算一、信号的基本概念
1.1 信号的概念 信号#xff08;signal#xff09;#xff0c;又称为软中断信号#xff0c;用于通知进程发生了异步事件#xff0c;它是Linux系统响应某些条件而产生的一个事件#xff0c;它是在软件层次上对中断机制的一种模拟#xff0c;是一种异…一、信号的基本概念
1.1 信号的概念 信号signal又称为软中断信号用于通知进程发生了异步事件它是Linux系统响应某些条件而产生的一个事件它是在软件层次上对中断机制的一种模拟是一种异步通信方式。 一句话总结信号本质是一种异步通知机制用户或操作系统通过发送信号来通知进程某些事情已经发生进程可以进行后续处理。 1.2 信号的产生 ① 通过终端按键产生信号 用户在终端按某些键时引发终端产生信号例如在终端上按Ctrl C键通常产生中断信号(SIGINT)终止进程。 ② 通过系统函数向进程发信号 进程调用kill(2)函数可以将任意信号发送给另一个进程或进程组但接收信号和发送信号进程的所有者必须相同或发送信号进程的所有者是超级用户。在终端通过kill -SIGNO PID 也可以将指定的信号发送给指定pid的进程如kill -SIGINT 4568。 ③ 由软件条件产生信号 当检测到某种软件条件已经发生将其通知有关进程时也产生信号例如SIGURG(在网络连接上传来外面的数据)、SIGPIPE(在管道的读进程已终止后一个进程写此管道)、SIGALRM(进程所设置的闹钟已经超时)。 ④ 硬件异常产生信号 除数为0、无效的内存引用等这些条件通常由硬件检测到并通知内核然后内核为该条件发生时正在运行的进程产生适当的信号。 1.3 信号的通信机制 信号是进程间通信机制中唯一的异步通信机制一个进程不必通过任何操作来等待信号的到达事实上 进程也不知道信号到底什么时候到达。它和中断服务函数一样在中断发生的时候 就会进入中断服务函数中去处理同样的当进程接收到一个信号的时候也会相应地采取一些行动。 1.4 信号的种类 我们可以使用 kill -l 命令来查看系统中支持的信号的种类 从图中可以看出Linux系统支持信号62种信号每种信号名称都以SIG三个字符开头 注意编号为32和33的信号值是不存在的。 可以将这62种信号分为两大类信号值为1~31的信号属于非实时信号也称为不可靠信号它们是从UNIX系统中继承下来的信号信号值为34~64的信号为实时信号也称为可靠信号。 这些信号各自在什么条件下产生默认的处理动作是什么可以通过 man 7 signal 来查询。 信号值 名称 描述 默认处理 1 SIGHUP 控制终端被关闭时产生。 终止 2 SIGINT 程序终止(interrupt)信号在用户键入INTR字符通常是Ctrl C时发出用于通知前台进程组终止进程。 终止 3 SIGQUIT SIGQUIT 和SIGINT类似但由QUIT字符通常是Ctrl 来控制进程在因收到SIGQUIT退出时会产生core文件在这个意义上类似于一个程序错误信号。 终止并产生转储文件core文件 4 SIGILL CPU检测到某进程执行了非法指令时产生通常是因为可执行文件本身出现错误 或者试图执行数据段、堆栈溢出时也有可能产生这个信号。 终止并产生转储文件core文件 5 SIGTRAP 由断点指令或其它trap指令产生由debugger使用。 终止并产生转储文件core文件 6 SIGABRT 调用系统函数 abort()时产生。 终止并产生转储文件core文件 7 SIGBUS 总线错误时产生。一般是非法地址包括内存地址对齐alignment出错。比如访问一个四个字长的整数但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的如访问不属于自己存储空间或只读存储空间。 终止并产生转储文件core文件 8 SIGFPE 处理器出现致命的算术运算错误时产生不仅包括浮点运算错误还包括溢出及除数为0等其它所有的算术的错误。 终止并产生转储文件core文件 9 SIGKILL 系统杀戮信号。用来立即结束程序的运行本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了可尝试发送这个信号将进程杀死。 终止 10 SIGUSR1 用户自定义信号。 终止 11 SIGSEGV 访问非法内存时产生进程试图访问未分配给自己的内存或试图往没有写权限的内存地址写数据。 终止 12 SIGUSR2 用户自定义信号。 终止 13 SIGPIPE 这个信号通常在进程间通信产生比如采用FIFO管道通信的两个进程读管道没打开或者意外终止就往管道写写进程会收到SIGPIPE信号。此外用Socket通信的两个进程写进程在写Socket的时候读进程已经终止也会产生这个信号。 终止 14 SIGALRM 定时器到期信号计算的是实际的时间或时钟时间alarm函数使用该信号。 终止 15 SIGTERM 程序结束terminate信号与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出shell命令kill缺省产生这个信号如果进程终止不了才会尝试SIGKILL。 终止 16 SIGSTKFLT 已废弃。 终止 17 SIGCHLD 子进程暂停或终止时产生父进程将收到这个信号如果父进程没有处理这个信号也没有等待wait子进程子进程虽然终止但是还会在内核进程表中占有表项这时的子进程称为僵尸进程这种情况我们应该避免。父进程默认是忽略SIGCHILD信号的我们可以捕捉它做成异步等待它派生的子进程终止或者父进程先终止这时子进程的终止自动由init进程来接管。 忽略 18 SIGCONT 系统恢复运行信号让一个停止stopped的进程继续执行本信号不能被阻塞可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作 恢复运行 19 SIGSTOP 系统暂停信号停止进程的执行。注意它和terminate以及interrupt的区别该进程还未结束只是暂停执行本信号不能被阻塞处理或忽略。 暂停 20 SIGTSTP 由控制终端发起的暂停信号停止进程的运行但该信号可以被处理和忽略比如用户键入SUSP字符时通常是CtrlZ发出这个信号。 暂停 21 SIGTTIN 后台进程发起输入请求时控制终端产生该信号。 暂停 22 SIGTTOU 后台进程发起输出请求时控制终端产生该信号。 暂停 23 SIGURG 套接字上出现紧急数据时产生。 忽略 24 SIGXCPU 处理器占用时间超出限制值时产生。 终止并产生转储文件core文件 25 SIGXFSZ 文件尺寸超出限制值时产生。 终止并产生转储文件core文件 26 SIGVTALRM 由虚拟定时器产生的虚拟时钟信号类似于SIGALRM但是计算的是该进程占用的CPU时间。 终止 27 SIGPROF 类似于SIGALRM / SIGVTALRM但包括该进程用的CPU时间以及系统调用的时间。 终止 28 SIGWINCH 窗口大小改变时发出。 忽略 29 SIGIO 文件描述符准备就绪, 可以开始进行输入/输出操作。 终止 30 SIGPWR 启动失败时产生。 终止 31 SIGUNUSED非法的系统调用。终止并产生转储文件(core文件) 1.5 实时信号和非实时信号 Linux系统中有许多信号其中前面 31 个信号都有一个特殊的名字对应一个特殊的事件 比如信号值为1的信号SIGHUPSignal Hang UP这个信号就是通知系统关闭中断的 当系统中的一个控制终端被关闭即挂断hang up时都会产生这个信号。 信号值为1~31的信号属性非实时信号它主要是因为这类信号不支持排队 因此信号可能会丢失。比如发送多次相同的信号进程只能收到一次 也只会处理一次因此剩下的相同的信号将被丢弃。而实时信号信号值为34~64的信号则不同 它是支持排队的发送了多少个信号给进程进程就会处理多少次。 二、信号的处理 生成信号的事件一般可以归为三大类程序错误、外部事件以及显式请求。 程序错误如零作除数、非法存储访问等这种情况通常是由硬件而不是由Linux内核检测到的 但由内核向发生此错误的那个进程发送相应的信号 外部事件如当用户在终端按下某些键时产生中断生成的信号当进程超越了CPU或文件大小的限制时 内核会生成一个信号通知进程 显式请求如进程通过使用kill()函数来发送任何信号给其他进程或进程组。 信号的生成既可以是同步的也可以是异步的 同步信号大多数是程序执行过程中出现了某个错误而产生的 由进程显式请求生成的给自己的信号也是同步的。 异步信号是接收进程可控制之外的事件所生成的信号这类信号一般是进程无法控制的 只能被动接收因为进程也不知道这个信号会何时发生只能在发生的时候去处理它。 一般外部事件总是异步地生成信号异步信号可在进程运行中的任意时刻产生 进程无法预期信号到达的时刻它所能做的只是告诉Linux内核假如有信号生成时应当采取什么行动这相当于注册信号对应的处理。 无论是同步还是异步信号当信号发生时我们可以告诉Linux内核采取如下3种动作中的任意一种 忽略信号大部分信号都可以被忽略但有两个除外SIGSTOP和SIGKILL绝不会被忽略。 不能忽略这两个信号的原因是为了给超级用户提供杀掉或停止任何进程的一种手段。 此外尽管其他信号都可以被忽略但其中有一些却不宜忽略。例如若忽略硬件例外非法指令信号 则会导致进程的行为不确定。 捕获信号这种处理是要告诉Linux内核当信号出现时调用专门提供的一个函数。 这个函数称为信号处理函数它专门对产生信号的事件作出处理。 让信号默认动作起作用系统为每种信号规定了一个默认动作这个动作由Linux内核来完成 有以下几种可能的默认动作 终止进程并且生成内存转储文件即写出进程的地址空间内容和寄存器上下文至进程当前目录下名为cone的文件中 终止终止进程但不生成core文件。 忽略信号。 暂停进程。 若进程为暂停状态恢复进程否则将忽略信号。 三、信号的保存
3.1 信号其他相关概念 实际执行信号的处理动作称为信号递达(Delivery)。信号从产生到递达之间的状态称为信号未决(Pending)。进程可以选择阻塞 (Block )某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。阻塞和忽略是不同的只要信号被阻塞就不会递达而忽略是在递达之后可选的一种处理动作。 3.2 信号在内核中的表示 进程通过二个位图来管理信号的处理过程 每个信号占用两个位图某一位分别表示阻塞(block)和未决(pending)还有一个函数指针数组表示处理动作。信号产生时内核在进程控制块(task_struct)中设置该信号的未决标志直到信号递达才清除该标志。在上图的例子中SIGHUP信号未阻塞也未产生过当它递达时执行默认处理动作。SIGINT信号产生过但正在被阻塞所以暂时不能递达。虽然它的处理动作是忽略但在没有解除阻塞之前不能忽略这个信号因为进程仍有机会改变处理动作之后再解除阻塞。SIGQUIT信号未产生过一旦产生SIGQUIT信号将被阻塞且不能抵达它的处理动作是用户自定义函数sighandler。 3.3 信号集 每个信号在未决和阻塞位图中各占用一位非0即1不记录该信号产生了多少次。因此未决和阻塞标志可以用相同的数据类型sigset_t来存储sigset_t称为信号集即图中未决和阻塞的位图。这个类型可以表示每个信号的“有效”或“无效”状态在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask)这里的“屏蔽”应该理解为阻塞而不是忽略。 3.4 信号集操作函数 用户只能通过信号集操作函数来操作信号集(sigset_t)。 #include signal.h
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo); 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清0表示该信号集不包含任何有效信号。函数sigfillset初始化set所指向的信号集使其中所有信号的对应bit置1表示该信号集的有效信号包括系统支持的所有信号。sigismember是一个布尔函数用于判断一个信号集的有效信号中是否包含某种信号若包含则返回1不包含则返回0出错返回-1。注意在使用sigset_t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信这四个函数都是成功返回0出错返回-1。 3.5 sigprocmask 调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)成功返回0失败返回-1。 #include signal.h
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 参数说明 how表示如何修改信号屏蔽字。 set根据how的参数修改信号屏蔽字的值。 oset输出型参数保存信号屏蔽字修改前的值。 假设当前的信号屏蔽字为mask则how参数如下表。 SIG_BLOCKset包含了我们希望添加到当前信号屏蔽字的信号相当于mask mask | setSIG_UNBLOCKset包含了我们希望从当前信号屏蔽字中解除阻塞的信号相当于mask mask~setSIG_SETMASK 设置当前信号屏蔽字为set的值相当于mask set
3.6 sigpending 读取当前进程的未决信号集通过set参数传出成功返回0失败返回-1。 #include signal.h
int sigpending(sigset_t* set);
四、信号的捕获
4.1 signal() signal()主要是用于捕获信号可以改变进程中对信号的默认行为我们在捕获这个信号后 也可以自定义对信号的处理行为。 使用signal()时它需要提前设置一个回调函数即进程接收到信号后将要跳转执行的响应函数 或者设置忽略某个信号才能改变信号的默认行为这个过程称为“信号的捕获”。 对一个信号的“捕获”可以重复进行不过signal()函数将会返回前一次设置的信号响应函数指针。 #include signal.h
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler); 参数说明 signumsignum是指定捕获的信号名称如果指定的是一个无效的信号 或者尝试处理的信号是不可捕获或不可忽略的信号如SIGKILLerrno将被设置为EINVAL。 handlerhandler是一个函数指针它的类型是 void(*sighandler_t)(int) 类型拥有一个int类型的参数 这个参数的作用就是传递收到的信号值返回类型为void。 signal()函数会返回一个sighandler_t类型的函数指针这是因为调用signal()函数修改了信号的行为 需要返回之前的信号处理行为是哪个以便让应用层知悉 如果修改信号的默认行为识别则返回对应的错误代码SIG_ERR。 handler需要用户自定义处理信号的方式当然还可以使用以下宏定义 SIG_IGN忽略该信号。 SIG_DFL采用系统默认方式处理信号。 注意如果调用处理程序导致信号被阻塞则从处理程序返回后 信号将被解除阻塞。无法捕获或忽略信号SIGKILL和SIGSTOP。 #include unistd.h
#include stdio.h
#include stdlib.h
#include signal.h
#include sys/types.h
#include sys/wait.h/* 信号处理函数 */
void signal_handler(int sig)
{printf(\nthis signal number is %d \n,sig);if (sig SIGINT) {printf(I have get SIGINT!\n\n);/* 恢复信号为默认情况 */signal(SIGINT, SIG_DFL);printf(default processing has been restored\n); }
}int main(void)
{printf(\nthis is an singal test function\n\n);/* 设置信号处理的回调函数 */signal(SIGINT, signal_handler); while (1) {printf(waiting for the SIGINT signal , please enter \ctrl c\...\n);sleep(1); }exit(0);
} 4.2 sigaction() sigaction()函数原型如下 #include signal.hint sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); 这个函数的参数比signal()函数多了一些参数区别如下 signum指定捕获的信号值。 act是一个结构体该结构体的内容如下。 struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);}; sa_handler是一个函数指针是捕获信号后的处理函数它也有一个int类型的参数传入信号的值这个函数是标准的信号处理函数。 sa_sigaction则是扩展信号处理函数它也是一个函数指针但它比标准信号处理函数复杂的多 事实上如果选择扩展接口的话信号的接收进程不仅可以接收到int型的信号值 还会接收到一个 siginfo_t类型的结构体指针还有一个void类型的指针还有需要注意的就是 不要同时使用sa_handler和sa_sigaction因为这两个处理函数是有联合的部分联合体。 sa_mask是信号屏蔽字它指定了在执行信号处理函数期间阻塞的信号掩码被设置在该掩码中的信号 在进程响应信号期间被临时阻塞。除非使用SA_NODEFER标志否则即使是当前正在处理的响应的信号再次到来的时候也会被阻塞。 re_restorer则是一个已经废弃的成员变量不要使用。 sa_flags是指定一系列用于修改信号处理过程行为的标志由下面的0个或多个标志组合而成 SA_NOCLDSTOP如果signum是SIGCHLD则在子进程停止或恢复时不会传信号给调用sigaction()函数的进程。 即当它们接收到SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU停止中的一种时或接收到SIGCONT恢复时 父进程不会收到通知。仅当为SIGCHLD建立处理程序时此标志才有意义。 SA_NOCLDWAIT它表示父进程在它的子进程终止时不会收到SIGCHLD 信号 这时子进程终止则不会成为僵尸进程。 SA_NODEFER不要阻止从其自身的信号处理程序中接收信号使进程对信号的屏蔽无效 即在信号处理函数执行期间仍能接收这个信号仅当建立信号处理程序时此标志才有意义。 SA_RESETHAND信号处理之后重新设置为默认的处理方式。 SA_SIGINFO指示使用sa_sigaction成员而不是使用sa_handler 成员作为信号处理函数。 oldact返回原有的信号处理参数一般设置为NULL即可。 上面的成员变量绝大部分我们是几乎使用不到的因为我们如果是对信号的简单处理直接使用sa_handler处理即可 根本无需配置siginfo_t这些比较麻烦的信息。 #include unistd.h
#include stdio.h
#include stdlib.h
#include signal.h
#include sys/types.h
#include sys/wait.h/* 信号处理函数 */
void signal_handler(int sig)
{printf(\nthis signal number is %d \n,sig);if (sig SIGINT) {printf(I have get SIGINT!\n\n);printf(The signal is automatically restored to the default handler!\n\n);/* 信号自动恢复为默认处理函数 */}}int main(void)
{struct sigaction act;/* 设置信号处理的回调函数 */act.sa_handler signal_handler; /* 清空屏蔽信号集 */sigemptyset(act.sa_mask); /* 在处理完信号后恢复默认信号处理 */act.sa_flags SA_RESETHAND; sigaction(SIGINT, act, NULL); while (1){printf(waiting for the SIGINT signal , please enter \ctrl c\...\n\n);sleep(1);}exit(0);
} 五、信号的发送
5.1 kill()
#include sys/types.h
#include signal.hint kill(pid_t pid, int sig); kill()函数的参数有两个分别是pid与sig还返回一个int类型的错误码。 pid的取值如下 pid 1将信号sig发送到进程ID值为pid指定的进程。 pid 0信号被发送到所有和当前进程在同一个进程组的进程。 pid -1将sig发送到系统中所有的进程但进程1init除外。 pid -1将信号sig发送给进程组号为-pid pid绝对值的每一个进程。 sig要发送的信号值。 函数返回值 0发送成功。 -1发送失败。 进程可以通过调用kill()函数向包括它本身在内的其他进程发送一个信号。 如果程序没有发送该信号的权限对kill函数的调用就将失败失败的常见原因是目标进程由另一个用户所拥有。 因此要想发送一个信号发送进程必须拥有相应的权限这通常意味着两个进程必须拥有相同的用户ID即你只能发送信号给属于自己的进程 但超级用户可以发送信号给任何进程。 kill()函数会在失败时返回-1并设置errno变量。失败的原因可能是给定的信号无效errno设置为INVAL、 发送进程权限不够errno设置为EPERM、目标进程不存在errno设置为ESRCH等情况。 5.2 raise() raise()函数也是发送信号函数不过与 kill()函数所不同的是 raise()函数只是进程向自身发送信号的而没有向其他进程发送信号 可以说kill(getpid(),sig)等同于raise(sig)。 #include signal.hint raise(int sig); raise()函数只有一个参数sig它代表着发送的信号值如果发送成功则返回0发送失败则返回-1 发送失败的原因主要是信号无效因为它只往自身发送信号不存在权限问题也不存在目标进程不存在的情况。 5.3 alarm() alarm()也称为闹钟函数它可以在进程中设置一个定时器当定时器指定的时间seconds到时 它就向进程发送SIGALARM信号。其函数原型如下 #include unistd.hunsigned int alarm(unsigned int seconds); 如果在seconds秒内再次调用了alarm()函数设置了新的闹钟则新的设置将覆盖前面的设置 即之前设置的秒数被新的闹钟时间取代。它的返回值是之前闹钟的剩余秒数如果之前未设闹钟则返回0。 特别地如果新的seconds为0则之前设置的闹钟会被取消并将剩下的时间返回。