信号处理

长时间运行的 linux 程序必须处理信号。

必须处理的信号有:SIGTERM, SIGINT, SIGPIPE, SIGBUS, SIGSEGV, SIGABRT

这些信号分别是什么意思?

信号 触发 阻塞/忽略/捕获 默认处理
SIGINT 输入 INTR 字符时发送,如 ctrl+C 进程终止
SIGTERM kill 发送的默认信号 可以 进程终止
SIGKILL 通过 kill -s 9 发送 不可 进程终止
SIGSTOP 不可 进程暂停
SIGHUP 会话结束时触发 可以 进程终止
SIGPIPE 向写端关闭的管道或 socket 写入数据 可以 进程终止
SIGBUS ?未对齐 终止进程,core dump
SIGSEGV 段错误:缓冲区溢出,栈溢出,非法文件访问 终止进程,core dump
SIGABRT 重复 freeassert 失败;执行 abort() 可捕获,不可阻塞

信号处理函数注意:不可在信号处理函数中调用不可重入函数。

fork 与文件描述符

fork 创建的子进程会和父进程共享文件描述符。

stdio 族函数使用了缓存机制,这可能会引发问题。缓存设置在哪里?

stdin, stdout 的缓冲区是设置在用户空间的:参考:[http://www.pixelbeat.org/programming/stdio_buffering/](http://www.pixelbeat.org/programming/stdio_buffering/)

一个例子:

  1. // 这一段代码:
  2. fork();
  3. printf(".");
  4. fflush(stdout);
  5. fork();
  6. printf(",");
  7. // 得到的输出是:..,,,,

如果将 fflush(stdout) 注释掉,输出会变成:

  1. .,.,.,.,

原因在于:stdout 使用了缓冲区,且该缓冲区位于用户进程空间。在使用 fork 得到子进程是,缓冲区也被复制了,缓冲区中还有未写入 stdout. 。所以最终得到的四个进程,每个进程都会输出 ., 到终端。

如图所示:
fork-stdout缓冲区.PNG

_exitexit

_exit 在退出之前会做三件事:

  1. 关闭进程打开的文件
  2. 将子进程托付给 init 进程
  3. 向父进程发送 SIGCHLD 信号

做完这三件事情之后,进程就会退出

exit 是在 _exit 的基础上进行了包装,它做了这些事情:

  1. 调用在 atexit() 中注册的函数,比如一些析构函数
  2. 刷写缓冲:fflush
  3. 最后调用 _exit() 函数

如果使用 fprintf 写文件,子进程继承了缓冲区,父进程继续写入,子进程此时 exit,将缓冲区中的内容刷入文件,刷入位置还是最开始的位置:

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <sys/wait.h>
  5. int main(int argc, char* argv[])
  6. {
  7. int status;
  8. char buffer[1024] = {0};
  9. FILE * fp = fopen("test.log","w");
  10. fprintf(fp,"a");
  11. pid_t pid = fork();
  12. if (pid == 0){
  13. sleep(1);
  14. exit(0);
  15. }
  16. fprintf(fp,"b");
  17. wait(&status);
  18. fprintf(fp,"c");
  19. return 0;
  20. }
  21. // 最终文件test.log的内容是:aabc
  22. // 子进程后退出,在退出时将缓冲区中的a刷入,刷入位置是fork时得到的