linux下system函数错误返回-1 错误原因NO child processes(转)

调用system函数执行一个shell命令,返回-1,错误提示no child processes 但system可以执行成功

原因是调用system之前有放置忽略SIGCHLD的语句

signal(SIGCHLD, SIG_IGN);

如果SIGCHLD信号行为被设置为SIG_IGN时,waitpid()函数有可能因为找不到子进程而报ECHILD错误。似乎我们找到了问题的解决方案:在调用system()函数前重新设置SIGCHLD信号为缺省值,即signal(SIGCHLD, SIG_DFL)。

解决办法 用pox_system()函数替代system(),只需要修改此处一个函数,其他调用处都不需要改。

typedef void (*sighandler_t)(int);  
int pox_system(const char *cmd_line)  
{  
   int ret = 0;  
   sighandler_t old_handler;  
  
   old_handler = signal(SIGCHLD, SIG_DFL);  
   ret = system(cmd_line);  
   signal(SIGCHLD, old_handler);  
  
   return ret;  
}  
SIG_DFL:默认信号处理程序
SIG_IGN:忽略信号的处理程序

测试过确实很奏效,感谢帖子的作者!
参考文章:http://my.oschina.net/renhc/blog/54582

原文如下:

今天,一个运行了近一年的程序突然挂掉了,问题定位到是system()函数出的问题,关于该函数的简单使用在我上篇文章做过介绍: http://my.oschina.net/renhc/blog/53580

先看一下问题

简单封装了一下system()函数:

int pox_system(const char *cmd_line)
{
    return system(cmd_line);
}
函数调用:

int ret = 0;
ret = pox_system("gzip -c /var/opt/I00005.xml > /var/opt/I00005.z");
if(0 != ret)
{
    Log("zip file failed\n");
}
问题现象:每次执行到此处,都会zip failed。而单独把该命令拿出来在shell里执行却总是对的,事实上该段代码已运行了很长时间,从没出过问题。

糟糕的日志

分析log时,我们只能看到“zip file failed”这个我们自定义的信息,至于为什么fail,毫无线索。

那好,我们先试着找出更多的线索:

int ret = 0;
ret = pox_system("gzip -c /var/opt/I00005.xml > /var/opt/I00005.z");
if(0 != ret)
{
    Log("zip file failed: %s\n", strerror(errno)); //尝试打印出系统错误信息
}
我们增加了log,通过system()函数设置的errno,我们得到一个非常有用的线索:system()函数失败是由于“ No child processes”。继续找Root Cause。

谁动了errno

我们通过上面的线索,知道system()函数设置了errno为ECHILD,然而从system()函数的man手册里我们找不到任何有关EHILD的信息。我们知道system()函数执行过程为:fork()->exec()->waitpid()。很显然waitpid()有重大嫌疑,我们去查一下man手册,看该函数有没有可能设置ECHILD:

ECHILD

(for waitpid() or waitid()) The process specified by pid (waitpid()) or idtype and id (waitid()) does not exist or is not a child of the calling process. (This can happen for one's own child if the action for SIGCHLD is set to SIG_IGN. See also the Linux Notes section about threads.)

果然有料,如果SIGCHLD信号行为被设置为SIG_IGN时,waitpid()函数有可能因为找不到子进程而报ECHILD错误。似乎我们找到了问题的解决方案:在调用system()函数前重新设置SIGCHLD信号为缺省值,即signal(SIGCHLD, SIG_DFL)。我们很兴奋,暂时顾不上看Linux Notes部分,直接加上代码测试!乖乖,问题解决了!

如此处理问题是你的风格吗

正当我们急于check in 代码时,一个疑问出现了:“这个错误为什么以前没发生”?是啊,运行良好的程序怎么突然就挂了呢?首先我们代码没有改动,那么肯定是外部因素了。一想到外部因素,我们开始抱怨:“肯定是其他组的程序影响我们了!”但抱怨这是没用的,如果你这么认为,那么请拿出证据!但静下来分析一下不难发现,这不可能是其他程序的影响,其他进程不可能影响我们进程对信号的处理方式。

system()函数之前没出错,是因为systeme()函数依赖了系统的一个特性,那就是内核初始化进程时对SIGCHLD信号的处理方式为SIG_DFL,这是什么什么意思呢?即内核发现进程的子进程终止后给进程发送一个SIGCHLD信号,进程收到该信号后采用SIG_DFL方式处理,那么SIG_DFL又是什么方式呢?SIG_DFL是一个宏,定义了一个信号处理函数指针,事实上该信号处理函数什么也没做。这个特性正是system()函数需要的,system()函数首先fork()一个子进程执行command命令,执行完后system()函数会使用waitpid()函数对子进程进行收尸。

通过上面的分析,我们可以清醒的得知,system()执行前,SIGCHLD信号的处理方式肯定变了,不再是SIG_DFL了,至于变成什么暂时不知道,事实上,我们也不需要知道,我们只需要记得使用system()函数前把SIGCHLD信号处理方式显式修改为SIG_DFL方式,同时记录原来的处理方式,使用完system()后再设为原来的处理方式。这样我们可以屏蔽因系统升级或信号处理方式改变带来的影响。

验证猜想 

我们公司采用的是持续集成+敏捷开发模式,每天都会由专门的team负责自动化case的测试,每次称为一个build,我们分析了本次build与上次build使用的系统版本,发现版本确实升级了。于是我们找到了相关team进行验证,我们把问题详细的描述了一下,很快对方给了反馈,下面是邮件回复原文:

LIBGEN 里新增加了SIGCHLD的处理。将其ignore。为了避免僵尸进程的产生。
看来我们的猜想没错!问题分析到这里,解决方法也清晰了,于是我们修改了我们的pox_system()函数:

typedef void (*sighandler_t)(int);
int pox_system(const char *cmd_line)
{
    int ret = 0;
    sighandler_t old_handler;    
    old_handler = signal(SIGCHLD, SIG_DFL);
    ret = system(cmd_line);
    signal(SIGCHLD, old_handler);
    return ret;
}
我想这是调用system()比较完美的解决方案了,同时使用pox_system()函数封装带来了非常棒的易维护性,我们只需要修改此处一个函数,其他调用处都不需要改。

后来,查看了对方修改的代码,果然从代码上找到了答案:

 
/* Ignore SIGCHLD to avoid zombie process */
if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {
    return -1;
} else {
    return 0;
}
其他思考

我们公司的代码使用SVN进程管理的,到目前为止有很多branch,逐渐的,几乎每个branch都出现了上面的问题,于是我逐个在各个branchc上fix这个问题,几乎忙了一天,因为有的branch已被锁定,再想merge代码必须找相关负责人说明问题的严重性,还要在不同的环境上测试,我边做这些边想,系统这样升级合适吗?

首先,由于系统的升级导致我们的代码在测试时发现问题,这时再急忙去fix,造成了我们的被动,我想这是他们的一个失误。你做的升级必须要考虑到对其他team的影响吧?何况你做的是系统升级。升级前需要做个风险评估,对可能造成的影响通知大家,这样才职业嘛。

再者,据他们的说法,修改信号处理方式是为了避免僵尸进程,当然初衷是好的,但这样的升级影响了一些函数的使用方式,比如system()函数、wait()函数、waipid()、fork()函数,这些函数都与子进程有关,如果你希望使用wait()或waitpid()对子进程收尸,那么你必须使用上面介绍的方式:在调用前(事实上是fork()前)将SIGCHLD信号置为SIG_DFL处理方式,调用后(事实上wait()/waitpid()后)再将信号处理方式设置为从前的值。你的系统升级,强制大家完善代码,确实提高了代码质量,但是对于这种升级我不是很认同,试想一下,你见过多少fork()->waitpid()前后都设置SIGCHLD信号的代码?

使用system()函数的建议

上在给出了调用system()函数的比较安全的用法,但使用system()函数还是容易出错,错在哪?那就是system()函数的返回值,关于其返回值的介绍请见上篇文章。system()函数有时很方便,但不可滥用!

1、建议system()函数只用来执行shell命令,因为一般来讲,system()返回值不是0就说明出错了;

2、建议监控一下system()函数的执行完毕后的errno值,争取出错时给出更多有用信息;

3、建议考虑一下system()函数的替代函数popen();其用法在我的另一篇文章有介绍。

 本文截取自网络。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.youkuaiyun.com/zpzkitt/article/details/13297251

进程观察实验(一):多进程环境1、查看系统中的task_struct结构,了解PCB所包含信息。 2、列出当前终端上启动的所有进程。试观察屏幕上的显示结果。 3、显示系统中的进程状态。试观察记录屏幕上的显示结果。 4、显示linux系统中的进程树。试观察分析屏幕上的显示结果。 5、编写程序,使用fork( )创建两个子进程。观察在程序运行过程中的进程状态变化,分析原因(1)编写一段程序,使用系统调用fork( )创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符串,其中,每个进程显示其pid值,及其父进程的pid值。类似如下显示:父进程显示'"I am parent.pid:...ppid:.... ",子进程分别显示'"I am child1.pid:..., ppid:.... "和'"I am child2.pid:..., ppid:...."。试观察并分析屏幕上的显示结果。 (2)改写以上程序,使得父进程在创建所有子进程之前执行一次system(“ps -af”),创建子进程之后,每个进程都执行一次system(“ps -af”)。通过在多进程执行过程中执行命令“ps -af”,显示当前进程状态。试观察并分析屏幕上的显示结果。 从进程并发执行来看,上面的三个进程没有同步措施,只要进程就绪就可能执行,因此各种执行顺序都有可能,所以三个进程的输出次序带有随机性。并且,每当一个进程执行了一段时间,其它就绪进程可能抢占处理机,因此,多个进程可能交错执行。不过,操作系统实现函数printf( )时,保证了进程每次调用该函数输出一个字符串时不会被中断。
最新发布
04-02
### Linux 多进程环境下任务结构、进程状态及 `fork()` 使用 #### 查看 `task_struct` 结构 在 Linux 中,`task_struct` 是用于表示进程的核心数据结构。该结构体定义了进程的各种属性和行为,包括但不限于进程状态、调度信息、文件描述符表、内存信息等[^2]。 可以通过阅读内核源码中的头文件 `/include/linux/sched.h` 来详细了解 `task_struct` 的具体实现。虽然无法直接打印整个 `task_struct` 数据结构的内容,但可以借助调试工具(如 GDB 或者 kdump)或者编写内核模块来访问其部分字段。 --- #### 列出当前终端的所有进程及其状态 为了列出当前终端上的所有进程并显示其状态,可使用命令: ```bash ps -af ``` 此命令会输出详细的进程列表,其中包括: - 用户名 (`USER`) - 进程 ID (`PID`) 和父进程 ID (`PPID`) - CPU 时间 (`C`) - 启动时间 (`STIME`) - 命令名称 (`CMD`) - 当前状态 (`STAT`),例如 `R` 表示运行态,`S` 表示睡眠态,`T` 表示停止态等[^5] 对于更复杂的过滤条件,还可以结合管道操作筛选特定进程的信息。例如查找名为 `myprocess` 的进程详情: ```bash ps ajx | head -1 && ps ajx | grep myprocess | grep -v grep ``` 上述命令先打印列标题再匹配目标进程条目[^3]。 --- #### 显示进程树 要查看系统的进程层次关系即进程树,可以利用如下命令: ```bash pstree ``` 这将以树状图形式展现各进程间的父子关联。如果只想聚焦某个具体的程序,则加上参数指定根节点即可,比如: ```bash pstree $$ ``` 这里 `$` 符号代表当前 shell 自身的 PID。 --- #### 创建两个子进程并通过 `fork()` 观察状态变化 以下是基于 C 语言的一个简单例子演示如何通过调用一次 `fork()` 函数创建两个子进程,并分别打印各自的 PID 及 PPID,最后调用外部命令展示完整的执行状况。 ```c #include <stdio.h> #include <unistd.h> #include <sys/types.h> int main(void){ pid_t child_pid; printf("Parent process (PID=%d)\n", getpid()); // First fork call. if ((child_pid = fork()) == 0) { // Child Process A printf("Child A created, PID=%d, Parent's PID=%d\n", getpid(), getppid()); // Second fork inside first child to create another sibling. if(fork()==0){ // Grandchild or Sibling B depending on perspective. printf("Child B from A, PID=%d, Parent's PID=%d\n", getpid(), getppid()); } }else{ sleep(1); // Give children time to execute before parent finishes. system("ps -af"); // Display all processes and their hierarchy at the end of execution. } return 0; } ``` 在这个脚本里,第一次 `fork()` 调用会产生第一个子进程(称为A)。接着,在这个新进程中再次发起第二次 `fork()` ,从而又派生出了第二个子进程(称为B)。最终三者的相互间关系可通过标准库函数 `getpid()` 获取自身的唯一标识符以及 `getppid()` 查询到所属上级实例编号[^1]^。 注意每次实际编译运行时得到的具体数值可能不同,取决于操作系统当时的资源分配策略。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值