玩命加载中 . . .

2.1-linux下的进程相关


exec函数族

exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,即在调用进程内部执行一个可执行文件

exec执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈都已经被新的内容取代,调用失败返回-1

一般会先fork创建一个子进程,再调用exec

fork仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值

  1. 在父进程中,fork返回新创建子进程的进程ID
  2. 在子进程中,fork返回0
  3. 如果出现错误,fork返回一个负值

fork只拷贝fork要执行的代码到新的进程

#include <unistd.h>
int execl(const char *path, const char *arg, /* (char *) NULL */);
- path: 可执行文件的路径
- arg: 执行文件的参数列表,必须以NULL结尾

int execlp(const char *file, const char *arg, /* (char *) NULL */);
// 会到环境变量中找可执行文件
- file: 给出文件名就可以了,会自己去找

int execv(const char *path, char *const argv[]);
- argv: 参数的数组

int execvpe(const char *file, char *const argv[],
                       char *const envp[]);
- envp: 指定环境变量

示例

#include <unistd.h>
#include <stdio.h>

int main() {

    pid_t pid = fork();

    if (pid > 0) {  // 父进程
        printf("I am parent: %d\n", getpid());
        sleep(1);
    }
    else if (pid == 0) {    // 子进程
        execl("hello", "hello", NULL);
        perror("execlp");
        printf("I am child: %d\n", getpid());   // 子进程的代码不再执行
    }

    for (int i = 0; i < 3; i++) {
        printf("i = %d, pid = %d\n", i, getpid());
    }
    return 0;
}
I am parent: 16936
hello world     // 执行指定的文件
i = 0, pid = 16936
i = 1, pid = 16936
i = 2, pid = 16936  // 继续执行父进程的代码

进程退出、孤儿进程、僵尸进程

exit函数和_exit函数

#include <stdlib.h>
void exit(int status);  // 标准C库函数

#include <unistd.h>
void _exit(int status); // Linux内核函数
- status: 进程退出时的状态信息,父进程回收子进程资源时用到
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main() {

    printf("hello\n");  // 换行会刷新缓冲区
    printf("word");

    exit(0);    // exit也会刷新缓冲区,再调用_exit()结束程序
    
    return 0;
}
hello
world
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main() {

    printf("hello\n");
    printf("word"); // 先放在缓冲区中,没有刷新输出,程序就结束了

    _exit(0);   // 没有刷新缓冲区
    
    return 0;
}
hello

孤儿进程

父进程运行结束,但子进程还在运行,称为孤儿进程(Orphan Process)

出现孤儿进程时,内核会将其父进程设置为initinit进程会循环wait(),这样当孤儿进程结束后,由init进程进行内核区的资源回收

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>

int main() {

    pid_t pid = fork(); // 写时复制:copy on write

    if (pid > 0) {
        printf("I am parent, pid: %d, ppid: %d\n", getpid(), getppid());
    }
    else if (pid == 0) {
        sleep(1);   // 等父进程先结束
        printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
    }

    for (int i = 0; i < 3; i++) {
        printf("i: %d, pid: %d\n", i, getpid());
    }

    return 0;
}

$./orphan
I am parent, pid: 25283, ppid: 20601
i: 0, pid: 25283
i: 1, pid: 25283
i: 2, pid: 25283

$ I am child, pid: 25284, ppid: 1880    // 父进程变了
i: 0, pid: 25284
i: 1, pid: 25284
i: 2, pid: 25284

僵尸进程

进程终止时,父进程尚未回收,子进程残留资源存放于内核中,变成僵尸进程(Zombie)

僵尸进程不能被kill -9杀死

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>

int main() {

    pid_t pid = fork(); // 写时复制:copy on write

    if (pid > 0) {  // 父进程没有回收子进程资源
        while (1) {
            printf("I am parent, pid: %d, ppid: %d\n", getpid(), getppid());
            sleep(1);
        }
        
    }
    else if (pid == 0) {    // 子进程先结束
        sleep(1);
        printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
    }

    for (int i = 0; i < 3; i++) {
        printf("i: %d, pid: %d\n", i, getpid());
    }

    return 0;
}
I am parent, pid: 28586, ppid: 20601
I am parent, pid: 28586, ppid: 20601
I am child, pid: 28587, ppid: 28586
i: 0, pid: 28587
i: 1, pid: 28587
i: 2, pid: 28587
I am parent, pid: 28586, ppid: 20601

kavin    28586  0.0  0.0   4524   800 pts/1    S+   10:38   0:00 ./zombie
kavin    28587  0.0  0.0      0     0 pts/1    Z+   10:38   0:00 [zombie] <defunct> 
// defunct不存在,Z表示僵尸进程

进程回收

每个进程退出时,内核会释放该进程的资源,包括打开的文件、占用的内存。但仍然会保留一些信息,如进程控制块PCB的信息(包括进程号、退出状态、运行时间等)

父进程可以调用waitwaitpid得到子进程的状态同时清除子进程

一次waitwaitpid只能清除一个子进程,多个子进程应使用循环

wait函数

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *wstatus);
// 等待任意一个子进程结束,回收其资源
- wstatus: int指针,进程退出时的状态信息,是传入也是传出参数,会把子进程退出的状态写到wstatus
- 成功返回子进程ID,失败返回-1

调用wait函数的进程会被挂起(阻塞),直到它的子进程退出或者收到一个不能被忽略的信号才被唤醒,如果没有子进程或者子进程都结束了,立即返回-1

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>

int main() {

    pid_t pid;
    for (int i = 0; i < 5; i++) {
        pid = fork();   // 创建5个子进程
        if (pid == 0) break;    // 不让子进程再创建子进程
    }

    if (pid > 0) {  // 父进程
        while (1) {
            printf("I am parent, pid=%d\n", getpid());
            sleep(1);
        }
        
    }
    else if (pid == 0) {    // 子进程先退出,称为僵尸进程
        printf("I am child, pid=%d\n", getpid());
    }

    return 0;
}

I am child, pid=26070
I am child, pid=26071
I am parent, pid=26069
I am child, pid=26072
I am child, pid=26073
I am child, pid=26074
I am parent, pid=26069
...
$ ps aux
kavin    26069  0.0  0.0   4524   728 pts/1    S+   14:53   0:00 ./wait
kavin    26070  0.0  0.0      0     0 pts/1    Z+   14:53   0:00 [wait] <defunct>
kavin    26071  0.0  0.0      0     0 pts/1    Z+   14:53   0:00 [wait] <defunct>
kavin    26072  0.0  0.0      0     0 pts/1    Z+   14:53   0:00 [wait] <defunct>
kavin    26073  0.0  0.0      0     0 pts/1    Z+   14:53   0:00 [wait] <defunct>
kavin    26074  0.0  0.0      0     0 pts/1    Z+   14:53   0:00 [wait] <defunct>

父进程调用wait阻塞等待子进程结束,回收其资源

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>

int main() {

    pid_t pid;
    for (int i = 0; i < 5; i++) {
        pid = fork();
        if (pid == 0) break;
    }

    if (pid > 0) {  // 父进程
        while (1) {
            printf("I am parent, pid=%d\n", getpid());

            int ret = wait(NULL);
            if (ret == -1) break;
            printf("child die, pid=%d\n", ret);

            sleep(1);
        }
        
    }
    else if (pid == 0) {    // 子进程
        while (1) {
            printf("I am child, pid=%d\n", getpid());
            sleep(1);
        }
    }

    return 0;
}

I am child, pid=26868
I am parent, pid=26867  // 父进程在wait处阻塞
I am child, pid=26869
I am child, pid=26870
I am child, pid=26871
I am child, pid=26872
kill -9 26868   // 逐一杀死子进程
kill -9 26869
kill -9 26870
kill -9 26871
kill -9 26872
child die, pid=26868    // 对应wait就执行,然后再次阻塞
child die, pid=26869
child die, pid=26870
child die, pid=26871
child die, pid=26872    // 直到子进程都退出后,父进程也退出

退出信息相关宏函数

通过下面几个函数,可以得知子进程结束的状态

含义
WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值
WEXITSTATUS(staus) WIFEXITED 返回非零值时,可以用这个宏来提取子进程的返回值
+ 如果子进程调用exit(5)退出,WEXITSTATUS(status) 就返回5
+ 如果进程不是正常退出的,也就是说, WIFEXITED 返回0,这个值就毫无意义
WIFSIGNALED(staus) 非0,异常终止
WTERMSIG(staus) 如果WIFSIGNALED返回非零值,可以获取使进程退出的信号编号

WEXITSTATUS打印进程正常退出状态码

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {

    pid_t pid;
    for (int i = 0; i < 5; i++) {
        pid = fork();
        if (pid == 0) break;
    }

    if (pid > 0) {
        // 父进程
        while (1) {
            printf("I am parent, pid=%d\n", getpid());

            int st;
            int ret = wait(&st);    // 阻塞等待回收子进程
            if (WIFEXITED(st)) {    // 正常退出,打印状态码
                printf("exit state code=%d\n", WEXITSTATUS(st));
            }
            if (WIFSIGNALED(st)) {  // 异常退出,打印信号
                printf("killed by signal: %d\n", WTERMSIG(st));
            }

            if (ret == -1) break;
            printf("child die, pid=%d\n", ret);

            sleep(1);
        }
        
    }
    else if (pid == 0) {
        printf("I am child, pid=%d\n", getpid());
        sleep(1);
        exit(0);
    }

    return 0;
}
I am child, pid=29902
I am child, pid=29903
I am parent, pid=29901
I am child, pid=29904
I am child, pid=29905
I am child, pid=29906
exit state code=0   // 正常退出,打印状态码
child die, pid=29902
I am parent, pid=29901
exit state code=0
child die, pid=29903
I am parent, pid=29901
exit state code=0
child die, pid=29904
I am parent, pid=29901
exit state code=0
child die, pid=29905
I am parent, pid=29901
exit state code=0
child die, pid=29906
I am parent, pid=29901
exit state code=0

WTERMSIG打印进程异常退出状态码

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {

    pid_t pid;
    for (int i = 0; i < 5; i++) {
        pid = fork();
        if (pid == 0) break;
    }

    if (pid > 0) {  // 父进程
        while (1) {
            printf("I am parent, pid=%d\n", getpid());
            int st;
            int ret = wait(&st);    // 阻塞等待回收子进程
            if (WIFEXITED(st)) {
                printf("exit state code=%d\n", WEXITSTATUS(st));
            }
            if (WIFSIGNALED(st)) {
                printf("killed by signal: %d\n", WTERMSIG(st));
            }

            if (ret == -1) break;
            printf("child die, pid=%d\n", ret);

            sleep(1);
        }
    }
    else if (pid == 0) {    // 子进程
        while (1) {
            printf("I am child, pid=%d\n", getpid());
            sleep(1);
        }
        exit(0);
    }

    return 0;
}

$ kill -9 30023
killed by signal: 9 // 异常退出,打印信号
child die, pid=30023

waitpid函数

回收指定进程的子进程,可以设置是否阻塞

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
- pid
    pid > 0: 指定子进程的pid
    pid = 0: 回收当前进程组的所有子进程
    pid = -1: 回收所有子进程,相当于wait(最常用)
    pid < -1: 回收绝对值相等的某个进程组的子进程
- options: 设置阻塞或者非阻塞
    0: 阻塞
    WNOHANG: 非阻塞
- 返回值
    > 0: 返回子进程的id
    = 0: options=WNOHANG,还有子进程活着
    = -1: 没有子进程
阻塞的情况
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {

    pid_t pid;
    for (int i = 0; i < 5; i++) {
        pid = fork();
        if (pid == 0) break;
    }

    if (pid > 0) {  // 父进程
        while (1) {
            printf("I am parent, pid=%d\n", getpid());
            int st;
            int ret = waitpid(-1, &st, 0);  // 阻塞
            if (WIFEXITED(st)) {
                printf("exit state code=%d\n", WEXITSTATUS(st));
            }
            if (WIFSIGNALED(st)) {
                printf("killed by signal: %d\n", WTERMSIG(st));
            }

            if (ret == -1) break;
            printf("child die, pid=%d\n", ret);

            sleep(1);
        }
        
    }
    else if (pid == 0) {    // 子进程
        while (1) {
            printf("I am child, pid=%d\n", getpid());
            sleep(1);
        }
        exit(0);
    }

    return 0;
}

$ kill -9 6414
killed by signal: 9
child die, pid=6414 // 父进程执行后又阻塞
非阻塞的情况
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {

    pid_t pid;
    for (int i = 0; i < 5; i++) {
        pid = fork();
        if (pid == 0) break;
    }

    if (pid > 0) {  // 父进程
        while (1) {
            printf("I am parent, pid=%d\n", getpid());
            sleep(1);

            int st;
            int ret = waitpid(-1, &st, WNOHANG);
            

            if (ret == -1) break;   // 没有子进程了
            else if (ret == 0) continue;    // 还有子进程
            else if (ret > 0) {     // 回收了某个子进程,打印状态码
                if (WIFEXITED(st)) {
                    printf("exit state code=%d\n", WEXITSTATUS(st));
                }
                if (WIFSIGNALED(st)) {
                    printf("killed by signal: %d\n", WTERMSIG(st));
                }
                printf("child die, pid=%d\n", ret);
            }

        }
    }
    else if (pid == 0) {    // 子进程
        while (1) {
            printf("I am child, pid=%d\n", getpid());
            sleep(1);
        }
        exit(0);
    }

    return 0;
}
// 非阻塞,父进程和子进程都在打印输出
$ kill -9 8380
killed by signal: 9
child die, pid=8380 // 子进程被结束
// 子进程全部结束后,waitpid返回-1,父进程也退出

文章作者: kunpeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kunpeng !
  目录