网站建设是不是要有营业执照,宿迁做网站需要多少钱,关于网站建设的文章,audio player wordpress 使用方法信号就是一条消息#xff0c;通知进程系统中发生了什么事#xff0c;每种信号都对应着某种系统事件。一般的底层硬件异常是由内核的异常处理程序处理的#xff0c;它对用户进程来说是透明的。而信号机制#xff0c;提供了一种方法通知用户进程发生了这些异常。例如#xf…信号就是一条消息通知进程系统中发生了什么事每种信号都对应着某种系统事件。一般的底层硬件异常是由内核的异常处理程序处理的它对用户进程来说是透明的。而信号机制提供了一种方法通知用户进程发生了这些异常。例如一个进程试图除0会引发内核向他发送SIGFPE信号执行非法指令会引发SIGILL信号非法内存访问引发SIGSEGV当你从键盘上键入Ctrl C会引发SIGINT当某个子进程结束会引发内核向其父进程发送SIGCHLD信号等等。具体请看下图1. 信号术语与原则1.1信号发送当内核检测到某种系统事件除零错误或子进程终止等等或一个进程调用了kill函数显式的要求内核发送一个信号给目的进程时内核会通过更新目的进程上下文中的某个状态而达到向它发送一个信号的目的。发送信号的方式为命令行用kill -signum PID命令向进程号为PID的进程发送signum信号键盘通过键盘发送特定信号Ctrl C 向前台进程组中的每个进程发送SIGINT终止信号Ctrl Z 向前台进程组中的每个进程发送SIGTSTP暂停信号函数alarm 使内核在一段时间secs秒后向自己发送SIGALRM信号;c #include unsigned int alarm(unsigned int secs); //返回待处理的闹钟在被发送前还剩余的秒数若之前没有待处理的闹钟则返回0 //若secs 0不会调度安排新的闹钟。 函数kill进程通过调用kill函数发送信号给其它进程包括自己。c #include #include int kill(pid_t pid, int sig); //成功返回0失败返回-1。 pid 0 :发送信号sig给进程pidpid 0 发送信号给自己所在进程组中的每个进程包括自己。pid 0 发送信号sig给进程-pid。 1.2 信号处理当进程从系统调用返回或是完成了一次上下文切换而重新取得控制权之前内核会检查该进程的待处理信号集pengding(~blocked)如果为空则完成控制权的交接如果不为空则会让进程响应该信号集合中信号值最小的那个信号。目的进程收到信号后有“忽略信号”、“终止进程”和“捕获信号“这3种方式来响应。其中SIGKILL终止和SIGSTOP暂停这2个信号不可被忽略也不能像其它信号一样可以通过signal函数改变他们的默认处理函数c #include typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler); 如果handler SIG_IGN那么就忽略类型为signum的信号 如果handler SIG_DFL那么就恢复类型为signum的信号的默认行为 否则handler就是用户自定义的信号处理函数地址。进程可以有选择的忽略某些信号通过将blocked位向量中相应的位置1即该信号虽然被内核或进程发送了过来但我可以选择视而不见。c #include int sigprocmask(int HOW, const sigset_t set, sigset_t oldset);int sigemptyset(sigset_t set); //初始化set集为空set 0 int sigfillset(sigset_t set); //将所有信号都添加进set集set 1 int sigaddset(sigset_t set, int signum); int sigdelset(sigset_t set, int signum); //以上5个函数成功返回0错误返回-1 int sigismember(const sigset_t *set, int signum); //是成员返回1不是返回0错误返回-1 关于sigprocmask函数中的HOW有以下几种可能的取值SIG_BLOCK把set集中的信号加到进程的blocked中blocked | setSIG_UNBLOCK从进程的blocked中删除set集中的信号blocked ~setSIG_SETMASK忽略set集中的信号blocked setoldset : 如果他是非空的则将进程原先blocked的值保存在其中。一下示例展示了临时忽略SIGINT信号的程序片段c sigset_t mask, oldmask;sigemptyset(mask); sigaddset(mask, SIGINT);sigprocmask(SIG_BLOCK, mask, oldmask); . . //此处的所有语句将不会响应SIGINT信号 . sigprocmask(SIG_SETMASK, oldmask, NULL); //之后的语句将会正常响应SIGINT信号 任何信号只能被记录阻塞一次即如果进程正在执行某类型信号的处理函数那么在此进程返回主程序前不管又收到了多少个该类型的信号它只会被记录一次即等到该进程从上次处理函数返回后它只会再响应一次该类型的信号。因为内核为每个进程在pending位向量中维护着待处理信号的集合而在blocked位向量中维护着被阻塞的信号集。每次收到一个信号就在blocked相应的位置1响应一个信号就在pengding中相应的清零。2. 安全的信号处理函数由于信号处理函数和主程序是并发运行的他们享有相同的全局变量他们的运行顺序是不可预测的这就导致何时接收到信号的规则往往有违人们的直觉或者说主程序和子程序间不一定会按照你预想的顺序去执行。所以为了防止竞争冒险在编写信号处理函数时有几个保守的原则需要遵守处理程序尽可能简单在处理程序中仅使用异步安全的函数也就是说该函数是可重入的只访问局部变量且不能中断下图列出了所有Linux保证安全的系统函数可以发现许多常见的库函数printf、sprintf、malloc、exit等都不是安全函数在编写信号处理函数时要尽量避免使用。为了在信号处理程序中能够打印一些简单的消息我们可以使用一些异步信号安全的系统函数来构建自己的特有包装函数。作为例子下面的程序展示了利用异步信号安全的系统函数write编写自己的SIOsafe I/O函数。c ssize_t sio_puts(char s[]) { int count 0; char *str s; if(!str) _exit(1); while(*str) count; return write(STDOUT, s, count); }保存和恢复error为了避免处理程序中某些语句的出错导致error被设置进而影响主程序中的判断在信号处理程序的第一条语句保存原error在它返回前恢复error。不管是主程序还是子程序在访问全局变量时都要阻塞所有的信号以防相互干扰。用volatile声明全局变量。 volatile要求编译器每次都是从内存中读取全局变量的值而非从缓存中。使用sig_atomic_t声明标志。 此处的标志代表在主程序和子程序间传递信号的全局变量因为sig_atomic_t要求编译器对它的操作是原子的所以即使没有阻塞所有信号它也不会被任何信号打断。使用sigaction函数重新包装signal函数使得系统自动重启被中断的系统调用。 由于一些系统函数例如read、write、accept等需要执行较长时间所以可能会被信号中断。而在许多较早以前版本的Unix系统中被中断的系统调用并不会在信号处理返回后重启而是直接返回错误并将error设置为EINTR。而sigaction函数可以设置信号处理时的语义。以下代码用sigaction函数编写了signal函数的包装函数[Signal][1]并且具有如下语义只有当前处理的该类型信号被阻塞其它信号也不会排队等待只要可能被中断的系统调用会自动重启一旦为某信号设置了信号处理程序它会一直保持到Signal重新为该信号设置SIG_IGN或SIG_DFL的信号处理程序。 handler_t Signal(int signum, handler_t handler) { struct sigaction action,oldaction;action.sa_handler handler;
sigemptyset(action.sa_mask);
action.sa_flags SA_RESTART;if(sigaction(signum, action, oldaction) 0)unix_error(Signal error);
return(oldaction.sa_handler)} 3.信号的同步当需要编写读写相同内存位置的并发进程我们不得不考虑进程间的既包括进程与进程之间也包括主进程与子进程之间竞争关系。这是一个很大的命题在此限于文章主题只讨论信号之间的竞争关系如何处理。主要分两个方面一是隐式竞争二是显式竞争。3.1 避免隐式竞争考虑一个类似shell的函数功能父进程在一个全局作业列表中记录着它的当前子进程每个作业一个条目。addjob和deletejob函数分别向这个作业列表中添加和删除作业。父进程每创建一个子进程就把它添加在作业列表中每当在SIGCHLD信号处理程序中回收一个僵死的子进程时就在job列表中删除这个子进程。void handler(int sig)
{int olderrno errno; //保存进程的原error值sigset_t mask_all,prev_all;pid_t pid;sigfillset(mask_all); //将所有信号添加到信号集mask_all中while((pid waitpid(-1, NULL, 0)) 0){ //回收僵死子进程sigprocmask(SIG_BLOCK, mask_all, prev_all); //阻塞屏蔽所有信号deletejob(pid); //从job列表中删除僵死的子进程条目sigprocmask(SIG_SETMASK, prev_all, NULL);}if(errno ! ECHILD) //如果父进程的所有子进程都已经回收则内核发送ECHILD错误Unix_error(waitpid error);errno olderrno; //恢复进程的原error值
}int main(int argc, char **argv)
{int pid;sigset_t mask_all,mask_one,prev_one;sigfillset(mask_all);sigemptyset(mask_one);sigaddset(mask_one, SIGCHLD); Signal(SIGCHLD, handler); //使用安全的Signal函数设置处理函数initjobs(); //初始化工作列表while(1){/*在产生子进程前屏蔽SIGCHLD以防止主进程还没执行到addjob就已经收到了因子进程终止而发来的SIGCHLD信号进而进入handler导致在jobs中找不到要删除的子进程条目*/sigprocmask(SIG_BLOCK, mask_one, prev_one); //频闭SIGCHLD信号if((pid fork()) 0){sigprocmask(SIG_SETMASK, prev_one, NULL) //子进程解除频闭SIGCHLDexecve(/bin/date, argv, NULL);}sigprocmask(SIG_BLOCK, mask_all, NULL) //父进程屏蔽所有信号addjob(pid); sigprocmask(SIG_SETMASK, prev_one, NULL) //父进程解除屏蔽}exit(0);
}3.2 避免显式竞争有时候主程序需要显式地等待某个信号处理运行。例如shell程序它必须等待当前的前台进程结束被SIGCHLD处理程序回收之后才能继续创建另一个进程。主进程在等待的这段时间应该干些什么才最好呢我们可以用一个无限循环语句让主进程就在那执行。但这样也太浪费CPU的资源了我们也可以用一个sleep或者nanosleep函数让主进程休眠但到底休眠多长时间不好把握间隔太小同样会造成多次循环间隔太大程序又会太慢。合适的解决办法是引入sigsuspend函数#include signal.hint sigsuspend(const sigset_t *mask); //返回-1它暂时挂起调用它的进程利用参数mask替换当前的信号阻塞集直到收到一个信号并进入处理程序如果是终止信号就直接返回处理完之后返回主进程并恢复原来的阻塞集。下面例子展示了主进程在创建完子进程后如何利用该函数显式的等待SIGCHLD的到来以达到同步的效果。#include signal.hvolatile sig_atomic_t pid;void sigchld_handler(int signum)
{int olderror errno;pid waitpid(-1, NULL, 0);int errno olderrno;
}void sigint_handler(int signum)
{}int main(int argc, char **argv)
{sigset_t mask,prev;Signal(SIGCHLD, sigchld_handler);Signal(SIGINT, sigint_handler);sigemptyset(mask);sigaddset(mask, SIGCHLD);while(1){sigprocmask(SIG_BLOCK, mask, prev); //屏蔽SIGCHLD信号if(fork() 0) //子进程exit(0);pid 0;while(!pid){ sigsuspend(prev); //挂起并等待SIGCHLD信号的到来其处理函数会使得pid大于0}sigprocmask(SIG_SETMASK, prev, NULL);printf(...);}exit(0);
}[1]: 引用Unix Network Programming: The Sockets Networking API,第三版第一卷************************************************嵌入式 Linux C ARM - 专题 - 简书www.jianshu.comCSDN-专业IT技术社区-登录blog.csdn.netLeon_Geo - 简书www.jianshu.com