求助,为什么子进程signal函数用法接收

fork()函数与Linux中的多线程编程 - 文章 - 伯乐在线
& fork()函数与Linux中的多线程编程
一、fork()函数
在操作系统的基本概念中进程是程序的一次执行,且是拥有资源的最小单位和调度单位(在引入线程的操作系统中,线程是最小的调度单位)。在Linux系统中创建进程有两种方式:一是由操作系统创建,二是由父进程创建进程(通常为子进程)。
系统调用函数fork()是创建一个新进程的唯一方式,当然vfork()也可以创建进程,但是实际上其还是调用了fork()函数。fork()函数是Linux系统中一个比较特殊的函数,其一次调用会有两个返回值,下面是fork()函数的声明:
#include &unistd.h&
// On success, The PID of the process is returned in the parent, and 0 is returned in the child. On failure,
// -1 is returned in the parent, no child process is created, and errno is set appropriately.
pid_t fork (void);
#include &unistd.h&&// On success, The PID of the process is returned in the parent, and 0 is returned in the child. On failure,// -1 is returned in the parent, no child process is created, and errno is set appropriately.&pid_t fork (void);
当程序调用fork()函数并返回成功之后,程序就将变成两个进程,调用fork()者为父进程,后来生成者为子进程。这两个进程将执行相同的程序文本,但却各自拥有不同的栈段、数据段以及堆栈拷贝。子进程的栈、数据以及栈段开始时是父进程内存相应各部分的完全拷贝,因此它们互不影响。从性能方面考虑,父进程到子进程的数据拷贝并不是创建时就拷贝了的,而是采用了写时拷贝(copy-on -write)技术来处理。调用fork()之后,父进程与子进程的执行顺序是我们无法确定的(即调度进程使用CPU),意识到这一点极为重要,因为在一些设计不好的程序中会导致资源竞争,从而出现不可预知的问题。下图为写时拷贝技术处理前后的示意图:
在Linux系统中,常常存在许多对文件的操作,fork()的执行将会对文件操作带来一些小麻烦。由于子进程会将父进程的大多数数据拷贝一份,这样在文件操作中就意味着子进程会获得父进程所有文件描述符的副本,这些副本的创建方式类似于dup()函数调用,因此父、子进程中对应的文件描述符均指向相同的打开的文件句柄,而且打开的文件句柄包含着当前文件的偏移量以及文件状态标志,所以在父子进程中处理文件时要考虑这种情况,以避免文件内容出现混乱或者别的问题。下图为执行fork()调用后文件描述符的相关处理及其变化:
与进程类似,线程(thread)是允许应用程序并发执行多个任务的一种机制。一个进程中可以包含多个线程,同一个程序中的所有线程均会独立执行,且共享同一份全局内存区域,其中包括初始化数据段(initialized data),未初始化数据段(uninitialized data),以及堆内存段(heap segment)。在多处理器环境下,多个线程可以同时执行,如果线程数超过了CPU的个数,那么每个线程的执行顺序将是无法确定的,因此对于一些全局共享数据据需要使用同步机制来确保其的正确性。
在系统中,线程也是稀缺资源,一个进程能同时创建多少个线程这取决于地址空间的大小和内核参数,一台机器可以同时并发运行多少个线程也受限于CPU的数目。在进行程序设计时,我们应该精心规划线程的个数,特别是根据机器CPU的数目来设置工作线程的数目,并为关键任务保留足够的计算资源。如果你设计的程序在背地里启动了额外的线程来执行任务,那这也属于资源规划漏算的情况,从而影响关键任务的执行,最终导致无法达到预期的性能。很多程序中都存在全局对象,这些全局对象的初始化工作都是在进入main()函数之前进行的,为了能保证全局对象的安全初始化(按顺序的),因此在程序进入main()函数之前应该避免线程的创建,从而杜绝未知错误的发生。
三、fork()与多线程
在程序中fork()与多线程的协作性很差,这是POSIX系列操作系统的历史包袱。因为长期以来程序都是单线程的,fork()运转正常。当20世纪90年代初期引入线程之后,fork()的适用范围就大为缩小了。
在多线程执行的情况下调用fork()函数,仅会将发起调用的线程复制到子进程中。(子进程中该线程的ID与父进程中发起fork()调用的线程ID是一样的,因此,线程ID相同的情况有时我们需要做特殊的处理。)也就是说不能同时创建出于父进程一样多线程的子进程。其他线程均在子进程中立即停止并消失,并且不会为这些线程调用清理函数以及针对线程局部存储变量的析构函数。这将导致下列一些问题:
1. 虽然只将发起fork()调用的线程复制到子进程中,但全局变量的状态以及所有的pthreads对象(如互斥量、条件变量等)都会在子进程中得以保留,这就造成一个危险的局面。例如:一个线程在fork()被调用前锁定了某个互斥量,且对某个全局变量的更新也做到了一半,此时fork()被调用,所有数据及状态被拷贝到子进程中,那么子进程中对该互斥量就无法解锁(因为其并非该互斥量的属主),如果再试图锁定该互斥量就会导致死锁,这是多线程编程中最不愿意看到的情况。同时,全局变量的状态也可能处于不一致的状态,因为对其更新的操作只做到了一半对应的线程就消失了。fork()函数被调用之后,子进程就相当于处于signal handler之中,此时就不能调用线程安全的函数(用锁机制实现安全的函数),除非函数是可重入的,而只能调用异步信号安全(async-signal-safe)的函数。fork()之后,子进程不能调用:
malloc(3)。因为malloc()在访问全局状态时会加锁。
任何可能分配或释放内存的函数,包括new、map::insert()、snprintf() ……
任何pthreads函数。你不能用pthread_cond_signal()去通知父进程,只能通过读写pipe(2)来同步。
printf()系列函数,因为其他线程可能恰好持有stdout/stderr的锁。
除了man 7 signal中明确列出的“signal安全”函数之外的任何函数。
2. 因为并未执行清理函数和针对线程局部存储数据的析构函数,所以多线程情况下可能会导致子进程的内存泄露。另外,子进程中的线程可能无法访问(父进程中)由其他线程所创建的线程局部存储变量,因为(子进程)没有任何相应的引用指针。
由于这些问题,推荐在多线程程序中调用fork()的唯一情况是:其后立即调用exec()函数执行另一个程序,彻底隔断子进程与父进程的关系。由新的进程覆盖掉原有的内存,使得子进程中的所有pthreads对象消失。
对于那些必须执行fork(),而其后又无exec()紧随其后的程序来说,pthreads API提供了一种机制:fork()处理函数。利用函数pthread_atfork()来创建fork()处理函数。pthread_atfork()声明如下:
#include &pthread.h&
// Upon successful completion, pthread_atfork() shall re otherwise, an error number shall be returned to indicate the error.
// @prepare 新进程产生之前被调用
// @parent
新进程产生之后在父进程被调用
新进程产生之后,在子进程被调用
int pthread_atfork (void (*prepare) (void), void (*parent) (void), void (*child) (void));
#include &pthread.h&&// Upon successful completion, pthread_atfork() shall re otherwise, an error number shall be returned to indicate the error.// @prepare 新进程产生之前被调用// @parent
新进程产生之后在父进程被调用// @child
新进程产生之后,在子进程被调用&int pthread_atfork (void (*prepare) (void), void (*parent) (void), void (*child) (void));
该函数的作用就是往进程中注册三个函数,以便在不同的阶段调用,有了这三个参数,我们就可以在对应的函数中加入对应的处理功能。同时需要注意的是,每次调用pthread_atfork()函数会将prepare添加到一个函数列表中,创建子进程之前会(按与注册次序相反的顺序)自动执行该函数列表中函数。parent与child也会被添加到一个函数列表中,在fork()返回前,分别在父子进程中自动执行(按注册的顺序)。具体事例可参考:http://blog.chinaunix.net/uid--id-3210394.html
fork()函数的调用会导致在子进程中除调用线程外的其它线程全都终止执行并消失,因此在多线程的情况下会导致死锁和内存泄露的情况。在进行多线程编程的时候尽量避免fork()的调用,同时在程序在进入main函数之前应避免创建线程,因为这会影响到全局对象的安全初始化。线程不应该被强行终止,因为这样它就没有机会调用清理函数来做相应的操作,同时也就没有机会来释放已被锁住的锁,如果另一线程对未被解锁的锁进行加锁,那么将会立即发生死锁,从而导致程序无法正常运行。
[1] Linux/UNIX系统编程手册(上)
[2] Linux多线程服务端编程使用muduo C++网络库
[3] http://blog.chinaunix.net/uid--id-3210394.html
可能感兴趣的话题
关于伯乐在线博客
在这个信息爆炸的时代,人们已然被大量、快速并且简短的信息所包围。然而,我们相信:过多“快餐”式的阅读只会令人“虚胖”,缺乏实质的内涵。伯乐在线内容团队正试图以我们微薄的力量,把优秀的原创文章和译文分享给读者,为“快餐”添加一些“营养”元素。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2018 伯乐在线捕捉系统信号signal函数_百度文库
赠送免券下载特权
10W篇文档免费专享
部分付费文档8折起
每天抽奖多种福利
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
捕捉系统信号signal函数
&&捕捉系统信号signal函数
阅读已结束,下载本文需要
想免费下载更多文档?
定制HR最喜欢的简历
下载文档到电脑,同时保存到云知识,更方便管理
加入VIP
还剩4页未读,
定制HR最喜欢的简历
你可能喜欢向各位求助:关于signal函数
[问题点数:80分,结帖人sumless]
向各位求助:关于signal函数
[问题点数:80分,结帖人sumless]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
2005年1月 Linux/Unix社区大版内专家分月排行榜第三
2005年4月 Linux/Unix社区大版内专家分月排行榜第二
匿名用户不能发表回复!|15:31 提问
fork()父子进程间信号处理(江湖救急)
为什么在子进程中有死循环(如下图),而程序运行之后子进程中的死循环自动结束了,并且没有对收到的信号做自定义处理(输出:接收到信号2)呢?
按理来说,结果不应该是这样么?
#include&stdio.h&
#include&stdlib.h&
#include&signal.h&
#include&unistd.h&
void accept_signal(int sig){
printf("捕获了信号%d\n",sig);
int main(void)
pid_t pid = fork();
if(pid & 0){
perror("fork");
exit(EXIT_FAILURE);
else if(pid == 0){
signal(SIGINT,accept_signal);
printf("父进程给子进程%d发送信号2\n",pid);
kill(pid,SIGINT);
按赞数排序
已经解决:在父进程执行kill之前sleep(1);以确保子进程先运行,否则父进程先运行,信号发送是有问题的。
父进程只是给子进程发送了一个信号2(SIGINT),而子进程对于信号2的处理,并不会按照默认的方式进行(默认是结束进程,子进程死循环被终止),应该按照我们自定义的信号处理函数进行处理(输出:捕获了信号2)。
你在程序中不是已经把子进程kill掉了吗?
kill(pid,SIGINT)
把这句注释掉
命令行里输kill -2
另外问问题时,最好附上源码
准确详细的回答,更有利于被提问者采纳,从而获得C币。复制、灌水、广告等回答会被删除,是时候展现真正的技术了!
其他相关推荐linux编程下signal()函数
时间: 18:46:03
&&&& 阅读:287
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&
linux编程下signal()函数
当服务器close一个连接时,若client端接着发数据。根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了。根据信号的默认处理规则SIGPIPE信号的默认执行动作是 terminate(终止、退出), 所以client会退出。
若不想客户端退出可以把 SIGPIPE设为SIG_IGN
signal(SIGPIPE,SIG_IGN);
这时SIGPIPE交给了系统处理。
服务器采用了fork的话,要收集垃圾进程,防止僵死进程的产生,可以这样处理:
signal(SIGCHLD,SIG_IGN); 交给系统init去回收。
这里子进程就不会产生僵死进程了。
signal(SIGHUP, SIG_IGN);
signal信号函数,第一个参数表示需要处理的信号值(SIGHUP),第二个参数为处理函数或者是一个表示,这里,SIG_IGN表示忽略SIGHUP那个注册的信号。
SIGHUP和控制台操作有关,当控制台被关闭时系统会向拥有控制台sessionID的所有进程发送HUP信号,默认HUP信号的action是 exit,如果远程登陆启动某个服务进程并在程序运行时关闭连接的话会导致服务进程退出,所以一般服务进程都会用nohup工具启动或写成一个 daemon。
unix中进程组织结构为 session 包含一个前台进程组及一个或多个后台进程组,一个进程组包含多个进程。
一个session可能会有一个session首进程,而一个session首进程可能会有一个控制终端。
一个进程组可能会有一个进程组首进程。进程组首进程的进程ID与该进程组ID相等。
这儿是可能会有,在一定情况之下是没有的。
与终端交互的进程是前台进程,否则便是后台进程
SIGHUP会在以下3种情况下被发送给相应的进程:
1、终端关闭时,该信号被发送到session首进程以及作为job提交的进程(即用 & 符号提交的进程)
2、session首进程退出时,该信号被发送到该session中的前台进程组中的每一个进程
3、若父进程退出导致进程组成为孤儿进程组,且该进程组中有进程处于停止状态(收到SIGSTOP或SIGTSTP信号),该信号会被发送到该进程组中的每一个进程。
系统对SIGHUP信号的默认处理是终止收到该信号的进程。所以若程序中没有捕捉该信号,当收到该信号时,进程就会退出。
表头文件 #include&signal.h&   
设置某一信号的对应动作   
函数原型 :
void (*signal(int signum,void(* handler)(int)))(int);   
或者:typedef void(*sig_t) ( int );   sig_t signal(int signum,sig_t handler);   
参数说明:  
第一个参数signum指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号。  
第二个参数handler描述了与信号关联的动作,它可以取以下三种值:   
(1)一个返回值为正数的函数地址  此函数必须在signal()被调用前申明,handler中为这个函数的名字。当接收到一个类型为sig的信号时,就执行handler 所指定的函数。这个函数应有如下形式的定义:   intfunc(int sig);   sig是传递给它的唯一参数。执行了signal()调用后,进程只要接收到类型为sig的信号,不管其正在执行程序的哪一部分,就立即执行func()函数。当func()函数执行结束后,控制权返回进程被中断的那一点继续执行。  
(2)SIGIGN   这个符号表示忽略该信号,执行了相应的signal()调用后,进程会忽略类型为sig的信号。   
(3)SIGDFL   这个符号表示恢复系统对信号的默认处理。   
函数说明 :   signal()会依参数signum 指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。当一个信号的信号处理函数执行时,  如果进程又接收到了该信号,该信号会自动被储存而不会中断信号处理函数的执行,直到信号处理函数执行完毕再重新调用相应的处理函数。但是如果在信号处理函数执行时进程收到了其它类型的信号,该函数的执行就会被中断。  
返回值: 返回先前的信号处理函数指针,如果有错误则返回SIG_ERR(-1)。   
附加说明 :在信号发生跳转到自定的handler处理函数执行后,系统会自动将此处理函数换回原来系统预设的处理方式,如果要改变此操作请改用sigaction()。  
下面的情况可以产生Signal:  
1. 按下CTRL+C产生SIGINT   
2. 硬件中断,如除0,非法内存访问(SIGSEV)等等   
3. Kill函数可以对进程发送Signal   
4. Kill命令。实际上是对Kill函数的一个包装   
5. 软件中断。如当Alarm Clock超时(SIGURG),当Reader中止之后又向管道写数据(SIGPIPE),等等   
2 Signals:
Description
由调用abort函数产生,进程非正常退出
用alarm函数设置的timer超时或setitimer函数设置的interval timer超时
某种特定的硬件异常,通常由内存访问引起
由Solaris Thread Library内部使用,通常不会使用
进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略
当被stop的进程恢复运行的时候,自动发送
和实现相关的硬件异常
数学相关的异常,如被0除,浮点溢出,等等
Solaris专用,Hiberate或者Suspended时候发送
发送给具有Terminal的Controlling Process,当terminal被disconnect时候发送
非法指令异常
BSD signal。由Status Key产生,通常是CTRL+T。发送给所有Foreground Group的进程
由Interrupt Key产生,通常是CTRL+C或者DELETE。发送给所有ForeGround Group的进程
异步IO事件
实现相关的硬件异常,一般对应SIGABRT
无法处理和忽略。中止某个进程
由Solaris Thread Libray内部使用
在reader中止之后写Pipe的时候发送
当某个事件发送给Pollable Device的时候发送
Setitimer指定的Profiling Interval Timer所产生
和系统相关。和UPS相关。
输入Quit Key的时候(CTRL+\)发送给所有Foreground Group的进程
非法内存访问
Linux专用,数学协处理器的栈异常
中止进程。无法处理和忽略。
非法系统调用
请求中止进程,kill命令缺省发送
Solaris专用,从Suspend恢复时候发送
实现相关的硬件异常。一般是调试异常
Suspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程
当Background Group的进程尝试读取Terminal的时候发送
当Background Group的进程尝试写Terminal的时候发送
当out-of-band data接收的时候可能发送
用户自定义signal 1
用户自定义signal 2
setitimer函数设置的Virtual Interval Timer超时的时候
SIGWAITING
Solaris Thread Library内部实现专用
当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程
当CPU时间限制超时的时候
进程超过文件大小限制
Solaris专用,进程超过资源限制的时候发送
  1、不要使用低级的或者STDIO.H的IO函数  2、不要使用对操作  3、不要进行系统调用   4、不是浮点信号的时候不要用longjmp   5、singal函数是由ISO C定义的。因为ISO C不涉及多进程,进程组以及终端I/O等,所以他对信号的定义非常含糊,以至于对UNIX系统而言几乎毫无用处。  备注:因为singal的语义于现实有关,所以最好使用sigaction函数替代本函数。
http://blog.sina.com.cn/s/blog_4b226b.html
&标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&原文:http://www.cnblogs.com/leijiangtao/p/4048826.html
教程昨日排行
&&国之画&&&& &&&&&&
&& &&&&&&&&&&&&&&
鲁ICP备号-4
打开技术之扣,分享程序人生!

我要回帖

更多关于 c语言signal函数 的文章

 

随机推荐