linux c进程的创建、终止

2018-06-09 18:01:16

对进程不是很了解的同学可以参考 linux 进程 这篇文章。

进程的创建

系统调用fork() 创建一新进程(子进程)。

#include <unistd.h>

pid_t fork(void);

//失败返回-1

在父进程,fork()会返回子进程的pid,在子进程会返回0。一般由此来区别父子进程。子进程的栈、数据段、堆全部从父进程中复制过来。之后,每个进程都可以修改自己的而不会影响另一进程。

pid_t childPid;

switch (childPid = fork()) {
    case -1:
        //出错控制
    case 0:
        //子进程
    default:
        //父进程
}

fork() 之后到底哪个进程先执行时不确定的。父子进程的文件描述符是复制的,所以它们指向同一打开文件表。取之前文章 linux文件io 的一张图。所以它们共享文件偏移量和状态标志等。

fork() 的内存语义

从概念上讲,可以将fork()认作对父进程程序段、数据段、堆段以及栈段的创建拷贝。不过真要是简单的将父进程的虚拟内存页拷贝到新的子进程,那就太浪费了。原因有很多,其中之一是:fork() 之后常常伴随exec(),这会用新程序替换进程的代码段,并重新初始化数据段、堆段和栈段。那之前对父进程的拷贝就多此一举。为了避免,现代的UNIX实现(包括Linux)会采用两种技术来避免这种浪费。

1.内核将每一进程的代码段标记为只读,这样子进程就无需拷贝父进程的代码段,而直接可以共用代码段。

2.内核采用 写时复制(copy-on-write)技术,调用fork()之后,内核会为一些将要修改的内存页面创建拷贝,还会对子进程的相应页表做适当的调整。这样父、子进程可以分别修改自己的拷贝页,不再相互影响。


进程的终止

通常,进程有两种方式终止。一是异常终止,比如由信号,可能产生core dump文件。另一个则是使用_exit()系统调用正常终止。

#include <unistd.h>

void _exit(int status);

status参数定义了进程的终止状态,虽然为int,但是只有低8位能被父进程所用,0表示正常终止,非0代表出错。

程序一般不会直接调用_exit(),而是调用库函数exit(),它会在调用_exit()前执行各种动作。

1.调用退出处理程序(通过atexit()和on_exit()注册的函数),其执行顺序与注册顺序相反,后面讲解。
2.刷新stdio 流缓冲区。
3.最后使用由status提供的值执行_exit()系统调用。

在main 函数里使用 return n等同于调用exit(n)。

进程终止会关闭打开文件描述符,释放文件锁等会做很多清理动作。

如果进程是管理终端,那么系统会向该终端前台进程组中的每个进程发送 SIGHUP 信号,接着终端会与会话脱离。


注册退出处理程序

#include <stdlib.h>

int atexit(void (*func)(void));

//成功返回0, 失败非0

函数atexit() 将func加到一个函数列表,进程终止时会反过来调用函数列表中的所有函数。一旦有任一退出函数无法返回,那么就不会再调用剩余的处理程序。

void func(void){
/* Perform some actions */
}


atexit() 有2个限制:
1.退出程序无法获知exit()的退出状态。
2.无法给退出处理程序制定参数。

为了摆脱这些限制,glibc提供了一个非标准的函数:on_exit()

#define _BSD_SOURCE /* Or: #define _SVID_SOURCE */

#include <stdlib.h>
int on_exit(void (*func)(int, void *), void *arg);

//Returns 0 on success, or nonzero on error
void func(int status, void *arg){
/* Perform cleanup actions */
}

这2个函数注册的函数位于同一函数列表。如果程序中同时用到了这两种方式,同样是按照相反的顺序执行相应的退出处理程序。

例子

#define _BSD_SOURCE     /* Get on_exit() declaration from <stdlib.h> */
#include <stdlib.h>
#include <string.h>     /* Commonly used string-handling functions */
#include <sys/types.h>  /* Type definitions used by many programs */
#include <stdio.h>      /* Standard I/O functions */
#include <stdlib.h>     /* Prototypes of commonly used library functions,
                           plus EXIT_SUCCESS and EXIT_FAILURE constants */
#include <unistd.h>     /* Prototypes for many system calls */
#include <errno.h>      /* Declares errno and defines error constants */

static void atexitFunc1(void){
    printf("atexit function 1 called\n");
}

static void atexitFunc2(void){
    printf("atexit function 2 called\n");
}

static void onexitFunc(int exitStatus, void *arg){
    printf("on_exit function called: status=%d, arg=%ld\n",
                exitStatus, (long) arg);
}

int
main(int argc, char *argv[])
{
    if (on_exit(onexitFunc, (void *) 10) != 0)
        perror("on_exit 1");
    if (atexit(atexitFunc1) != 0)
        perror("atexit 1");
    if (atexit(atexitFunc2) != 0)
        perror("atexit 2");
    if (on_exit(onexitFunc, (void *) 20) != 0)
        perror("on_exit 2");

    exit(2);
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 
on_exit function called: status=2, arg=20
atexit function 2 called
atexit function 1 called
on_exit function called: status=2, arg=10


fork()、stdio缓冲区以及_exit()之间的交互

#define _BSD_SOURCE     /* Get on_exit() declaration from <stdlib.h> */
#include <stdlib.h>
#include <string.h>     /* Commonly used string-handling functions */
#include <sys/types.h>  /* Type definitions used by many programs */
#include <stdio.h>      /* Standard I/O functions */
#include <stdlib.h>     /* Prototypes of commonly used library functions,
                           plus EXIT_SUCCESS and EXIT_FAILURE constants */
#include <unistd.h>     /* Prototypes for many system calls */
#include <errno.h>      /* Declares errno and defines error constants */

int
main(int argc, char *argv[])
{
    //存入用户空间的缓冲区
    printf("Hello world\n");
    
    //存入内核空间的缓冲区
    write(STDOUT_FILENO, "freecls\n", 8);

    //创建子进程
    if (fork() == -1)
        perror("fork");

    exit(EXIT_SUCCESS);
}
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[]){
    //存入用户空间的缓冲区
    printf("Hello world\n");
    
    //存入内核空间的缓冲区
    write(STDOUT_FILENO, "freecls\n", 8);

    //创建子进程
    if (fork() == -1)
        perror("fork");

    return 0;
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 
Hello world
freecls

[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out > a.txt
[root@izj6cfw9yi1iqoik31tqbgz c]# cat a.txt
freecls
Hello world
Hello world

上面当程序输出到终端,会看到预期的效果。但是当重定向到文件,就不一样了。原因是:

printf()会把输出存入用户空间的缓冲区,当标准输出定位到终端时,因为默认为行缓冲,所以带换行符的字符串 "Hello world\n" 会立马刷新缓冲区输出。

而当标准输出重定向到文件时,由于默认为全缓冲(缓冲区满了才会刷新缓冲区输出),所以在fork()调用之前,"Hello world\n"还处在父进程的用户空间的内存缓冲区,随子进程创建而产生了一个副本。父子进程调用exit()时会各自刷新输出,所以会出现2次。而write 会直接进内核缓冲区,fork() 不会复制这一缓冲区。对缓冲区不了解的可以参考 linux 文件io缓冲

顺序的问题也迎刃而解了,这是因为write() 会直接进入内核缓冲区,而printf() 需要等到exit() 刷新缓冲区时才进内核缓冲区。


总结

系统编程有很多晦涩的概念,读者可以进入本文的 "linux c系统编程" 分类阅读更多文章来了解,下一篇讲解子进程的监控,如果有疑问可以给我留言。


备注
1.编译器版本gcc4.8,运行环境centos7 64位
2.原文地址http://www.freecls.com/a/2712/51

©著作权归作者所有
收藏
推荐阅读
  • linux c定时器与休眠

    定时器是进程规划自己在未来某一时刻接获通知的一种机制。休眠则能是进程(或线程)暂停执行一段时间。间隔定时器系统调用 setitimer() 创建一个间隔式定时器,一定时间后到期,这个定时器也可以每个一...

  • linux c信号(3)-高级特性

    core dump文件coredump 文件 内含进程终止时内存映像的一个文件,把它加载到调试器中,即可查明信号到达时程序代码和数据的状态。 引发core dump文件除了程序中调用 abort()外...

  • err
    linux c信号(2)-处理函数

    一般而言信号处理函数设计的越简单越好。其中的一个重要原因在于,这将降低引发竞争条件的风险。下面是信号处理函数的两种常见设计。1.信号处理器函数设置全局性标记变量并退出。...

  • err
    linux c信号(1)-基本概念

    信号是事件发生时对进程的通知机制。有时也称之为软件中断。信号与硬件中断的相似之处在于打断了程序执行的正常流程,大多数情况下,无法预测信号到达的精确时间。一个(具有合适权限...

  • err
    linux c监控文件事件

    某些应用程序需要对文件或目录进行监控,来侦测其发生特定事件。例如当文件加入或移出一目录,图形化文件管理器应能判定此目录是否在其当前显示之列,而守护进程可能也要监控自己的配...

  • nginx模块 ngx_http_headers_module

    ngx_http_headers_module 模块是用来增加 Expires 和 Cache-control,或者是任意的响应头。Syntax: add_header name value [alw...

  • nginx模块 ngx_http_gunzip_module、ngx_http_gzip_module、ngx_http_gzip_static_module

    ngx_http_gunzip_module 模块将文件解压缩后并在响应头加上 "Content-Encoding: gzip" 返回给客户端。为了解决客户端不支持gzip压缩。编译的时候带上 --w...

  • nginx模块 ngx_http_flv_module、ngx_http_mp4_module

    ngx_http_flv_module模块提供了对 flv 视频的伪流支持。编译的时候带上 --with-http_flv_module。它会根据指定的 start 参数来指定跳过多少字节,并在返回数...

  • nginx模块 ngx_http_fastcgi_module

    ngx_http_fastcgi_module 模块使得nginx可以与 fastcgi 服务器通信。比如目前要使得 nginx 支持 php 就得使用 fastcgi技术,在服务器上装上 nginx...

  • nginx模块 ngx_http_autoindex_module

    ngx_http_autoindex_module 模块可以将uri以 / 结尾时,列出里面的文件和目录。Syntax: autoindex on | off; Default: autoindex ...

简介
天降大任于斯人也,必先苦其心志。