子进程中调用system命令执行openssl后,程序退出了,原因是SIGCHLD信号的处理

本文探讨了在使用system函数调用外部命令时遇到的子进程意外退出问题。问题源于父进程对SIGCHLD信号的不当处理。文章详细解释了解决方案,并对比了不同Linux内核版本下SIGCHLD的行为差异。

源代码:

void quit_proc(int t)
{
  kill(0,15);  exit(0);
}

int main_worker(int argc, char *argv[])
{
  
  ......
  
  //自定义信号处理函数
  signal(SIGCHLD, quit_proc);
  //子进程结束时, 父进程会收到这个信号然后父进程也退出 

  for (i=0; i<childcnt; i++) {
    procid = fork();
    if ( !procid ) {
      //子进程      

      ......
      
      system("openssl ...");  //程序执行到这里就退出了,下面的跟踪没有打印出来
      printf("openssl ok!\n");
      
    } else if ( procid > 0 )  {
      //
    } else { i--; sleep( 5 ); }
  }
  while (1) sleep(1);

  return 0;
}

后来研究了一下,发现是由于父进程中(父进程A),SIGCHLD信号的处理方式被继承到子进程中(子进程B),子进程中调用system,system又会fork一个子进程(子进程C),这样当system的子进程C执行完后,子进程B收到了SIGCHLD信号,于是执行了quit_proc函数一起退出了


由于本身对信号机制不太懂吧,导致这种低级错误,上网搜索了一下,原来是要在子进程中要恢复SIGCHLD信号的默认处理方式就可以了

修改后的源代码:

void quit_proc(int t)
{
  kill(0,15);  exit(0);
}

int main_worker(int argc, char *argv[])
{
  
  ......
  
  //自定义信号处理函数
  signal(SIGCHLD, quit_proc);
  //子进程结束时, 父进程会收到这个信号然后父进程也退出 

  for (i=0; i<childcnt; i++) {
    procid = fork();
    if ( !procid ) {
      //子进程      
      signal(SIGCHLD,SIG_DFL);  //子进程以默认的方式处理该信号(system外部命令时不退出)
      
      ......
      
      system("openssl ...");  //程序执行到这里就退出了,下面的跟踪没有打印出来
      printf("openssl ok!\n");
      
    } else if ( procid > 0 )  {
      //
    } else { i--; sleep( 5 ); }
  }
  while (1) sleep(1);

  return 0;
}


1、关于在system中获取子进程的返回值与SIGCHLD

在Linux我们一般写的是Server程序,所以,一般在main函数中,首先将进程转换为后台进程,即调用deamon,deamon的一般实现。


deamon的实现中会忽略下面的信号:
signal(SIGINT, SIG_IGN);    //当在终端上按下ctrl+c后,会产生SIGINT信号。
signal(SIGHUP, SIG_IGN);    //终端退出时,会给所有的进程发送SIGHUP信号。
signal(SIGQUIT, SIG_IGN);   //终端退出时,会给所有的进程发送SIGQUIT信号。
signal(SIGPIPE, SIG_IGN);   //往没有读进程的管道中进行写操作。
signal(SIGTTOU, SIG_IGN);   //后台进程写tty
signal(SIGTTIN, SIG_IGN);   //后台进程读tty


signal(SIGCHLD, SIG_IGN);   
/*
子进程先于父进程结束时,会给父进程发送SIGCHLD信号
如果
1、父进程没有忽略SGICHLD信号;
或者
2、父进程没有调用wait或waitpid函数。
那么子进程将僵死。
(
在2.6内核,只要父进程显式忽略了SIGCHLD信号,
那么子进程将不会僵死,那么system将得不到子进程的退出状态。
也就是说system函数的返回值并不是子进程退出时的状态。
而2.4内核,只要父进程没有调用wait系列函数,子进程就将僵死。
不论是否忽略了SIGCHLD信号。
)
*/


signal(SIGTERM, SIG_IGN);   
/*
当kill pid时,向进程发送SIGTERM信号。
SIGTERM信号的默认处理是进程退出。
SIGTERM是进程在有可能的情况下退出。
注意::
killall  -9    process_name
发送的SIGKILL信号,强制进程退出。
*/

如果,我们在我们的server中需要调用system来调用外部脚本或程序来执行某写工作。

例如:
在脚本中通过wget下载文件::

#!/bin/bash
FILENAME=$0
PATHNAME=$1
wget $FILENAME $PATHNAME
if [ $? -eq 0 ] ; then
  exit 0
else
  exit -1
fi

例如::
char  command[1024];
url=http://test/test.rar;
pathname="./data";
sprintf(command,"./download.sh  %s   %s",url,pathname);
int ret = system(command);
if( ret == 0)
{
  //成功
}
else
{
  //失败
}
注意::
其中ret用来接收子进程退出是的返回值。即exit的返回值。
但是由于在deamon中忽略了SIGCHLD信号,所以主进程将不再接收子进程的返回值。所以,ret的值不能正确反映子进程的退出状态。

正确的做法是::
signal(SIGCHLD,SIG_DFL);                //默认处理方式,是接收子进程的返回值。
system(command);
signal(SIGCHLD,SIG_IGN);

2、system相关问题::
system函数其实是调用fork,exec,waitpid来实现的。
1、fork一个进程;
2、在子进程中调用exec去执行新程序。
3、在父进程中调用waitpid去等待子进程结束。
如果在父进程已经signal(SIGCHLD,SIG_IGN);那么子进程结束时,子进程的返回值不能被waitpid接收。
这个是必须关注的问题。

下面我们来分析system的实现:
下面给出system函数及SIGCHLD信号处理分别在2.6及2.4内核下的区别。system函数源码的一个实现如下:
int system(const char * cmdstring)
{
  pid_t pid;
  int status;
  if(cmdstring == NULL)
  {
    return (1);
  }
  if((pid = fork())<0)
  {
    status = -1;
  }
  else if(pid == 0)
  {
    execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
    _exit(127);
  }
  else
  {
    while(waitpid(pid, &status, 0) < 0)
    {
      if(errno != EINTR)
      {
        status = -1;
        break;
      }
    }
  }
  return status;
}

2.6内核下当父进程未调用wait系列函数等待子进程结束且未显式地忽略SIGCHLD信号,则子进程将成为僵死进程;(如果显示忽略,则子进程不僵死)
而在2.4内核中只要父进程未调用wait系列函数,则子进程就会成为僵死进程,不管是否显式地忽略SIGCHLD信号。
因而在SIGCHLD信号被显式忽略的情况下,2.6内核子进程将直接退出并释放资源,等父进程调用waitpid时,发现对应的pid进程已不存在,因而返回-1错误,errno为10(No child processes);
而在2.4内核下子进程在父进程waitpid之前不会退出,因而waitpid能成功获取子进程状态。

<think> 我们之前讨论了OpenSSL配置文件的作用、重名问题的解决方案,以及编译安装时路径的处理。现在用户的问题聚焦于配置文件的调用时机和调用者。 问题:openssl.cnf是运行时调用还是编译时调用,什么程序调用它 回答思路: 1. 明确openssl.cnf是运行时加载的,不是编译时绑定的。 2. 调用它的程序主要是两个大类:OpenSSL命令行工具和使用了OpenSSL库的应用程序。 3. 具体说明调用机制(环境变量、API函数等)。 根据规则,回答后需要提出3-5个相关问题,且第一行必须是“§§相关问题§§”。 因此,回答结构: - 先回答问题(运行时调用,哪些程序调用) - 详细解释调用机制 - 给出示例 - 然后输出“§§相关问题§§”并列出3-5个相关问题 </think> ### OpenSSL配置文件调用机制详解 #### 一、调用时机与调用者分析 ```mermaid graph LR A[openssl.cnf] --> B{{运行时动态加载}} B --> C[OpenSSL命令行工具] B --> D[使用OpenSSL库的应用程序] B --> E[OpenSSL内置模块] C --> C1[openssl req] C --> C2[openssl ca] C --> C3[openssl x509] D --> D1[Apache/Nginx] D --> D2[Postfix/Dovecot] D --> D3[自定义C/Go/Python程序] E --> E1[证书颁发机构模块] E --> E2[加密引擎模块] ``` #### 二、核心调用机制 ##### 1. 运行时加载过程 - **默认加载路径搜寻**: ```bash # 典型搜索路径 /usr/lib/ssl/openssl.cnf /etc/ssl/openssl.cnf $OPENSSL_DIR/openssl.cnf ``` - **显式指定机制**: ```bash # 命令行工具指定 openssl req -config /custom/path/openssl.cnf -newkey rsa:2048 # 环境变量覆盖 export OPENSSL_CONF=/versioned/openssl_v3.cnf ``` ##### 2. 程序调用类型 | 程序类型 | 调用方式 | 加载时机 | 示例 | |----------|----------|----------|------| | **CLI工具** | 自动加载默认路径<br>或`-config`参数 | 命令执行时 | `openssl genpkey -algorithm RSA` | | **服务程序** | 通过`CONF_modules_load_file` API | 服务启动时 | Nginx初始化SSL上下文 | | **动态库** | `OPENSSL_init_crypto`初始化 | 首次调用加密函数时 | Python `ssl.create_default_context()` | | **静态链接** | 编译时无关联<br>完全运行时加载 | 应用初始化时 | 嵌入式设备程序 | ##### 3. 编译时关联性 **重要结论**:配置文件与编译过程完全无关 - 编译时`--openssldir`仅设置**默认搜索路径** - 无任何配置内容被编译进二进制文件 - 修改配置无需重新编译OpenSSL或应用程序 #### 三、典型调用场景示例 1. **证书签发流程** ```bash # 调用示例 openssl req -new \ -config /etc/pki/tls/openssl.cnf \ # 显式调用 -key server.key \ -out server.csr ``` 2. **应用程序调用(C语言)** ```c #include <openssl/conf.h> void load_openssl_conf() { // 显式加载配置文件 if (!CONF_modules_load_file("custom.cnf", "openssl_conf", 0)) { // 错误处理 } // 使用加密函数 EVP_PKEY *pkey = EVP_PKEY_new(); } ``` 3. **Web服务器配置(Nginx)** ```nginx http { ssl_protocols TLSv1.3; ssl_conf_command Options PrioritizeChaCha; # 间接调用openssl.cnf配置 } ``` > **关键事实**: > 1. 配置文件仅在**运行时**被动态加载 > 2. 所有使用OpenSSL库的程序都可能调用 > 3. 调用时机取决于程序初始化逻辑 > 4. 可通过多种方式覆盖默认配置路径
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值