• 进程管理


    可以通过execl函数把新的程序加载到内存中,把当前进程的映像替换成新的映像。path指向程序的位置,arg是新程序的第一个参数,省略号表示数目不定的参数,但是必须以NULL结束:

    int execl(const char *path, const char *arg, ...);

    下面是该函数的一个例子,该段代码加载了ls对应的程序,ls运行结束之后返回,而不再执行main中在其后面的代码:

    int main(){
    int ret = execl("/bin/ls","ls", NULL);
    if(ret == -1){
    printf("调用execl函数失败\n");
    printf("%s\n", strerror(errno));
    }
    printf("main");//不会执行
    return 0;
    }

    execl的一次成功执行不仅仅是会离开旧程序、执行新程序,还有以下影响:

    1. 任何没有处理的信号会被丢掉,而新的程序捕捉到的信号都被调用默认的处理函数。
    2. 任何对内存的锁定都会被丢掉。
    3. 多数进程重置统计数据。
    4. 多数进程属性回归默认值。
    5. 与进程的内存有关的任何东西会被丢掉。
    6. 单独存在用户空间的任何东西会被丢掉。

    fork调用可以产生一个当前进程的副本并继续执行,有点真假孙悟空的味道。子进程和父进程不同的地方在于fork调用的返回值,子进程返回0,而父进程返回子进程的PID。至于vfork与fork有什么区别相信大家都知道了,这里就不罗嗦,一个简单的例子:

    int main(){
    pid_t pid = vfork();
    if(pid > 0){
    printf("我是父进程!\n");
    }else if(pid == 0){
    printf("我是子进程!\n");
    int ret = execl("/bin/ls", "ls", NULL);
    if(ret == -1){
    printf("execl调用失败!\n");
    printf("%s\n", strerror(errno));
    }
    }else{
    printf("fork调用失败!\n");
    }
    return 0;
    }

    再看进程的结束,进程的结束并不是想象的那么简单,至少比我想的复杂:

    1. 按照与注册相反的顺序调用以atexit或on_exit注册的任何函数。
    2. 刷新所有已打开的标准I/O流。
    3. 移除任何由tmpfile所创建的临时文件。

    关于第一点可以用下面的程序看到效果:

    void func_1(){
    printf("func_1\n");
    }

    void func_2(){
    printf("func_2\n");
    }

    int main(){
    atexit(func_1);
    atexit(func_2);
    return 0;
    }

    不同的结束方式对第二点的现象是不同的,下面的两个程序的输出的差异就能说明这点:

    int main(){
    for(int i = 0; i < 10000; i++){
    printf("%d ", i);
    }
    _exit(0);
    }

    该段代码不会输出到9999,说明没有刷新标准输出流,但是在exit的时候会完整第输出。如下:

    int main(){
    for(int i = 0; i < 10000; i++){
    printf("%d ", i);
    }
    exit(0);
    }

    exit(stats)是无法返回任何值的,其中stats参数用来表示进程的结束状态,特别的是stats&0377会被返回给父进程。

    如果子进程在父进程之前死亡,则内核让子进程进入一个特殊的进程状态。进入该状态的进程为僵尸进程,此时进程只会保留最小的骨架。处于此状态的进程会等待它的父进程来打听它的状态。只有在父进程取得保留的关于子进程的信息时,子进程才会正式结束并且不再存在。
    wait的用法可以看下面的例子:

    int main(){
    pid_t pid = fork();
    if(pid == 0){
    exit(1);
    }
    sleep(2);

    int stats;
    pid = wait(&stats);
    printf("%d %d\n", stats, (int)pid);
    return 0;
    }

    这个是在等待所有的子进程,在取到一个子进程的结束状态之后就返回。可以在下面的例子中看到如何等待特定的子进程:

    int main(){
    pid_t pid = fork();

    if(pid == 0)
    exit(0);

    pid = fork();
    if(pid == 0)
    exit(2);
    printf("第二次产生进程的pid:%d\n", (int)pid);
    int stats;

    pid = waitpid(pid, &stats, WUNTRACED);
    printf("等待的子进程的pid:%d\n", (int)pid);

    return 0;
    }

    下面是一种更灵活的等待方式:

    int waitid(idtpe_t idtype, id_t id, siginfo_t *infop, int options);

    其中idtype可以的取值为下面的三种:

    1. P_PID:所要等待的是其进程ID与id相符的子进程。
    2. P_GID:所等待的是进程组ID域id相符的子进程。
    3. P_ALL:等待所有的子进程,忽略id参数。

    而options可以是下面几组的一种或者组合:

    1. WEXITED:等待已终止的子进程。
    2. WSTOPPED:等待已停止执行的子进程。
    3. WCONTINUED:等待已继续执行的子进程。
    4. WNOHANG:不会阻挡。
    5. WNOWAIT:不会把进程从僵尸进程状态移除。

    下面是waitid的一个简单的例子,虽然和上面waitpid的效果是一样的:

    int main(){
    pid_t pid = fork();

    if(pid == 0)
    exit(0);

    pid = fork();
    if(pid == 0)
    exit(2);
    printf("第二次产生进程的pid:%d\n", (int)pid);
    int stats;
    siginfo_t sig;
    waitid(P_PID, pid, &sig, WEXITED);
    printf("等待的子进程的pid:%d\n", sig.si_pid);

    return 0;
    }

    system可以用来创建新进程并等待它的终止,可以视为同步进程的创建:

    int main(){
    system("ls");

    return 0;
    }

    命令执行期间,SIGCHLD会被阻挡,而SIGINT和SIGQUIT会被忽略。忽略SIGINT和SIGQUIT会有几个问题,特别是system在一个循环中被调用的时候,这时应该确保程序会妥善检查子进程的结束状态。

  • 相关阅读:
    poj 3744 题解
    hdu 1850 题解
    New World
    CSP2019游记
    LOJ6052 DIV
    CF809E Surprise me!
    Luogu4548 歌唱王国
    Luogu4581 想法
    Note 5.26-5.28
    LOJ6519 魔力环
  • 原文地址:https://www.cnblogs.com/ggzwtj/p/2208752.html
Copyright © 2020-2023  润新知