2.父子进程

1.fork函数

  (1)pid_t fork(void); 创建一个子进程,子进程所有的数据,代码都是从父进程开呗过来的。失败返回-1,成功则返回:父进程返回子进程的ID,子进程返回0。pid_t类型表示进程ID,但是为了表示-1,它是有符号整型(0不是有效的进程ID,init的进程ID是最小的,1)。系统中能创建的进程的数量有系统资源决定,如果进程退出之后没有回收进程资源,那么这部分资源就会被一直占用着。

  进程ID相关的函数:

  getpid()函数;//获取当前进程ID  pid_t getpid(void);

        //获取当前进程的父进程的ID  pid_t getppid(void);

  getgid()函数;//获取当前进程使用用户组ID  gid_t getgid(void);

        //获取当前进程有效用户组ID  gid_t getegid(void);

  getuid()函数;//获取当前进程实际用户ID  uid_t getuid(void);

        //获取当前进程有效用户ID  uid_t geteuid(void);

  进程的创建:

#include <sdtio.h>
#include <unistd.h>

int main()
{
    pid_t id = fork();
    if(id == -1){
        perror("create fail:");
        return -1;
    }

    if(id == 0){
        printf("本进程是子进程:%d-----父进程ID:%d",getpid(),getppid());
    }
    if(id>0){
        printf("本进程是父进程:%d-----子进程ID:%d",getpid(),id);
    }
    while(1);
    return 0;
}

  区分一个函数是系统函数还是库函数的依据:

    1.能否访问内核数据结构

    2.能否访问外部硬件资源

  二者有任意一个就是系统函数,都没有则是库函数。

  常用的查看当前系统进程的命令:ps aux

   查看系统能创建多少进程:

int main()
{
    int number = 0;
    while(1)
    {
        pid_t pid = fork();
        if(pid<0)
        {
            perror("创建失败");
            sleep(1);
        }
        if(pid == 0){
            exit(-1);//子进程退出
        }
        if(pid>0){
            //父进程回收子进程资源
            int status;
            wait(&status);//阻塞直到子进程退出
            printf("number = %d
",number++);
        }
    }
}

  僵尸进程:子进程退出之后,父进程没有对子进程的资源进行回收,这个时候子进程就是僵尸进程,一定要避免僵尸进程的产生,僵尸进程的数量是有限的,有系统的最大资源决定。

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

int main()
{
    pid_t pid = fork();
    if(pid ==0)exit(-1);
    if(pid>0)
    {
        while(1);
    }
}

  孤儿进程:子进程还在运行,但是父进程退出了,那么这时候子进程就是孤儿进程,孤儿进程对系统不会有影响,因为孤儿进程会被自动回收。

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

int main()
{
    pid_t pid = fork();
    if(pid == 0){
        while(1);
    }
    if(pid>0)
    {
        //父进程退出
        sleep(10);
        exit(-1);
    }
    return 0;
}

  进程退出:exit(-1);

  进程退出的方式:1.进程运行完之后自动退出

          2.通过exit(-1)退出  进程关闭文件描述符,清空缓冲区

          3._exit()退出  只是关闭进程,不会处理文件描述符,也不会清空缓冲区。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    printf("hello world");
    _exit(0);


    return 0;
}

  

(2)父子进程在fork()之后,有哪些异同?

  查看父子进程共享的数据:

#include <sdio.h>
#include <unistd.h>

int main()
{
    pid_t id = fork();
    if(id == -1){
        perror("create fail:");
        return -1;
    }

    if(id == 0){
        printf("本进程是子进程:%d-----父进程ID:%d",getpid(),getppid());
    }
    if(id>0){
        printf("本进程是父进程:%d-----子进程ID:%d",getpid(),id);
    }
    while(1);
    return 0;
}

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

int gdata = 123;//全局数据---数据段
int main()
{
    int mydata = 123;//局部数据--栈空间
    
    pid_t pid = fork();
    if(pid == 0)
    {
        while(1)
        {
            //子进程
            printf("子进程---gdata=%d mydata=%d",gdata,mydata);
            sleep(1);
        }
    }

    if(pid>0)
    {
        while(1)
        {
            printf("父进程---gdata=%d mydata=%d",gdata,mydata);
            sleep(1);
            mydata++;
            gdata++;
        }
    }

    return 0;
}

    相同的部分:全局变量、.data、.text、堆、栈、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式.....

    不同的部分:进程ID、fork的返回值、两者的父进程ID、程序运行时间、闹钟(定时器)、未决信号集。

    父子进程共享的区域:

      1.文件描述符

      2.mmap简历的映射区,fork之后,父子进程的先后执行顺序是不确定的,取决于内核所使用的的调度算法。

2.vfork()函数

  fork()和vfork()都是用来创建子进程的,vfork()-----父进程要等子进程退出后才运行(写时复制),而fork()的父子进程的执行顺序是由系统调度决定的。

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

int main()
{
    pid_t pid = vfork();
    if(pid == 0){
        printf("子进程
");
        while(1);
    }

    if(pid>0){
        printf("父进程
");
    }
}

3.exec函数族

  (1)fork()创建子进程后执行的是和父进程相同的程序(但有可能是执行不同的代码分支),子进程往往要调动一种exec函数来执行另一个程序。当进程调用一种exec函数时,该进程用户空间代码和数据完全被程序替换,从新程序的启动例程开始执行。调用exec函数并不创建新进程,所以调用exec函数前后的进程ID并没有改变。

    将当前进程的.text、.data替换为所要加载程序的.text、.data,然后让进程从新的.text的第一条指令开始执行,但是进程ID并没有改变,只是换核不换壳。

    exec函数如下:  

int execl(const char *path,const char *arg,....);
int execlp(const char *file,const char *arg....);
int execle(const char *path,const char *arg,..,char *const envp[]);
int execv(const char *path,char *const argv[]);
int execvp(const char *file,char *const argv[]);
int execve(const char *path,char *const argv[],char *const envp[]);

  1.execlp函数

    int execlp(const char *file,const char *arg....);//加载一个进程,借助PATH环境变量。

    成功:无返回;失败:-1;

    参数1:要加载的程序的名字,该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索之后没有参数1则返回失败。该函数通常用来调用系统程序,例如:ls、date、cp、cat等命令。

  2.execl函数

    int execl(const char *path,const char *arg,....);//加载一个进程,通过:路径+程序名来加载

    成功:无返回;失败:-1;

    参数1:要加载的程序的绝对路径。

  3.execvp函数

    int execvp(const char *file,char *const argv[]);//加载一个进程,使用自定义环境变量env

    成功:无返回;失败:-1;

    参数1:要加载的程序名,execvp和execlp原理上一样,只是参数不同。

  4.exec函数族的一般规律

    exec函数一旦调用成功就立刻执行新的程序,无返回值,错误则返回-1,所以我们通常直接在exec()函数调用后直接调用perror()和exit(),无需if判断。

l(list) 命令行参数列表
p(path) 搜索file时使用path变量
v(vector) 使用命令行参数数组
e(environment) 使用环境变量数组,不适用进程原有的环境变量,设置新加载程序运行的环境变量

  事实上只有execve才是真正的系统调用,其他五个函数最终都是调用execve,这些函数之间的关系如下。

  

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

int main()
{
    pid_t pid = fork();
    if(pid=0){
        char *const argv[]={"ls","-1","-a",NULL};
        execv("/bin/ls",argv);
        printf("********
");
    }
    if(pid>0){
        printf("#######
");
        while(1);
    }
    return 0;
}

PS:哪里写错了请指正,互相学习。

原文地址:https://www.cnblogs.com/smallqizhang/p/12451982.html