一、父子进程的关系

子进程是父进程的副本。子进程获得父进程数据段,堆,栈,正文段共享。

在fork之后,一般情况那个会先运行,是不确定的。如果非要确定那个要先运行,需要IPC机制。

区别:
        1)fork的返回值
        2)pid不同

二、进程的终止(8种情况)和退出

1..进程的终止:8种情况

主动退出

        1)main 走到return

        2)exit() //库函数

            c库函数,会执行io库的清理工作,关闭所有的流,以及所有打开的文件。已经清理函数                 (atexit)。【只要有调用该函数,则结束进程】

        3)_exit,_Exit  会关闭所有的已经打开的文件,不执行清理函数。

        4) 主线程退出

        5)主线程调用pthread_exit

异常终止

        6)abort() //系统发送重终止信号

        不允许应用层调用,若产生严重错误,系统自行调用该函数,关闭进程

        7)signal // 发信号 kill pid

        8)最后一个线程被pthread_cancle

2.进程的退出

僵尸进程和孤儿进程

僵尸进程:进程执行结束但空间未被回收变成僵尸进程,子进程消亡后pcb块未被回收,父进程还在

孤儿进程:父进程先消亡

三、相关函数

1.exit     库函数 (man 3)

退出状态,终止的进程会通知父进程,自己使如何终止的

如果是正常结束(终止),则由exit传入的参数。

如果是异常终止,则有内核通知异常终止原因的状态。

任何情况下,父进程都能使用wait,waitpid获得这个状态,以及资源的回收。

#include<stdlib.h>

void exit(int status) 

exit(1);

功能:让进程退出,并刷新IO缓存区

参数:status:进程退出的状态

返回值:缺省
EXIT_SUCCESS 0
EXIT_FAILURE 1

return  当该关键字出现在main函数中时候可以结束进程

如果在其他函数中则表示结束该函数。

exit -> 刷新缓存区 -> atexit注册的退出函数 -> _exit

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

int	main(int argc, char **argv)
{
    printf("hello");
    exit(1);//全面回收工作,文件关闭,堆释放,缓冲区清理
    printf("aaaaaa");
    //system("pause");
    return 0;
}

2._exit    系统调用 (man 2)

#include<unistd.h>

void _exit(int status);

功能:让进程退出,不刷新缓存区,进程立刻结束

参数:status:进程退出状态,值给父进程

返回值:缺省

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

int	main(int argc, char **argv)
{
    printf("hello");
    _exit(1);//只做文件关闭工作
    printf("aaaaaa");
    //system("pause");
    return 0;
}

3.atexit (man 3)

#include<stdlib.h>

int atexit(void (*function)(void));

功能:注册进程退出前清理的函数

参数:function:函数指针;指向void返回值void参数的函数指针

返回值:成功返回0;失败返回非0

当程序调用exit或者由main函数执行return时,所有用atexit;注册的退出函数,将会由注册时顺序倒

序被调用

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
char *p;
void clean(void)
{
    printf("this clean fun ,%s\n",p);
}
int	main(int argc, char **argv)
{
    p=malloc(50);
    atexit(clean);
    strcpy(p,"hello");
    printf("befor exit\n");
    exit(1);//如果有注册有清理函数,只能调用exit
    //system("pause");
    return 0;
}

上述三个函数的综合使用: 

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

void cleanup1 (void)
{
    printf("----------cleanup1-------\n");
}

void cleanup2 (void)
{
    printf("----------cleanup2-------\n");
}

void cleanup3 (void)
{
    printf("----------cleanup3-------\n");
}

int	main(int argc, char **argv)
{
    atexit(cleanup1);

    int i = 0;
    for(i = 0;i < 5;++i)
    {
        printf("n = %d\n",i);
    }

    //_exit(0);
    exit(0);
    atexit(cleanup2);    
    atexit(cleanup3);


    return 0;
}

4.进程空间的回收

exit(20);
wait/waitpid
pid_t wait(int *status);

功能:该函数可以阻塞等待任意子进程退出并回收该进程的状态,一般用于父进程回收子进程状态

参数:status 进程退出时候的状态

         如果不关心其退出状态一般用NULL表示;

         如果要回收进程退出状态,则用WEXITSTATUS回收。

注:阻塞:进程在运行过程中由于某种条件未到达,导致程序被迫进入阻塞状态

宏:

 正常结束:

      WEXTSTATUS(wstatus)//判断子进程是否正常结束,正常快结束则为真

        WEXTSTATUS(wstatus)//获得exit(status) _exit(ststus) return status

                                                    //status&3077



返回值:成功 回收的子进程pid; 失败 -1;

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

int	main(int argc, char **argv)
{
    pid_t pid=fork();
    if(pid>0)
    {
        printf("father %d\n",getpid());
        pid_t ret =wait(NULL);//阻塞(block),父进程需等子进程结束
        printf("pid :%d ret %d\n",pid,ret);
    }
    else if(0==pid)
    {
        int i = 3;
        while(i--)
        {
            printf("child %d\n",getpid());
            sleep(1);
        }
    }
    else
    {
        perror("fork");
        return 1;
    }
    return 0;


    system("pause");
    return 0;
}

(1)pid_t wait(int *status);

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

功能:等待子进程状态发生改变;结束、暂停、恢复

参数:status 子进程退出时候的状态信息,如果不关注退出状态用NULL;//被调修改主调
注:
        1)如果所有的子进程都在运行,在阻塞
        2)如果一个子进程终止,正在等待的父进程则获得终止状态,获得子进程的状态后,立刻返                回。
        3)如果没有子进程,则立即出错退出。

(2)pid_t waitpid(pid_t pid, int *status, int options);

         waitpid(-1,&status,0)=wait(&status);

  功能:等待子进程状态改变

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

int	main(int argc, char **argv)
{
    pid_t pid=fork();
    if(pid>0)
    {
        printf("father %d\n",getpid());
        int status;
        pid_t ret =wait(&status);
        if(WIFEXITED( status))//子进程正常结束
        {
            printf("child terminal normally,exit value %d\n",WEXITSTATUS(status));
        }
        else if(WIFCONTINUED(status))//异常结束
        {
            printf("child terminal by signal,signal value %d\n",WTERMSIG(status));
        }
        printf("pid :%d ret %d\n",pid,ret);
    }
    else if(0==pid)
    {
        int i = 10;
        while(i--)
        {
            printf("child %d\n",getpid());
            sleep(1);
        }
        exit(20);
    }
    else
    {
        perror("fork");
        return 1;
    }
    return 0;


    system("pause");
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char **argv)
{
  pid_t pid = fork();
  if (pid > 0)
    {
      printf("father %d\n", getpid());
      int status;
      while (1)
        {
          pid_t ret = waitpid(pid, &status, WNOHANG);
          if (pid == ret)
            {
              if (WIFEXITED(status))  //子进程正常结束
                {
                  printf("child terminal normally,exit value %d\n",
                         WEXITSTATUS(status));
                }
              else if (WIFCONTINUED(status))  //异常结束
                {
                  printf("child terminal by signal,signal value %d\n",
                         WTERMSIG(status));
                }
              printf("pid :%d ret %d\n", pid, ret);
              break;
            }
          else if (0 == ret)
            {
              printf("回收失败,稍后再试\n");
            }
          else
            {
              printf("回收错误\n");
              return 1;
            }
        }
    }
  else if (0 == pid)
    {
      int i = 3;
      while (i--)
        {
          printf("child %d\n", getpid());
          sleep(1);
        }
      exit(20);
    }
  else
    {
      perror("fork");
      return 1;
    }
  return 0;

  system("pause");
  return 0;
}

练习:

设计一个多进程程序,用waitpid函数指定回收,其中的某个进程资源并将其状态打印输出。其他的

进程都以非阻塞方式进行资源回收。

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

int main(int argc, char **argv)
{
  int i=0;
  int n=5;
  pid_t pid[5]={0};
  for(i=0;i<5;++i)
  {
    pid[i]=fork();
    if(pid[i]>0)
    {
      continue;
    }
    else if(0==pid[i])
    {
      int num =rand()%5+1;
      printf("child:%d\n",getpid());
      sleep(num);
      exit(1);
    }
    else
    {
      perror("fork\n");
      return 1;
    }
  }
  while(1)
  {
    pid_t recycle=waitpid(pid[2], NULL, WNOHANG);
    if(recycle==pid[2])
    {
      printf("recycle child pid:%d\n",recycle);
      break;
    }
    else if(0==recycle)
    {

    }
    else
    {
      perror("waitpid");
      return 1;
    }
  }
  return 0;
}

5.  execute exec族

功能:用来执行一个新的进程

          通过将新进程的各个阶段替换当前进程的各个阶段 实现的

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往

要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和

数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用

exec前后该进程的id并未改变。其实有六种以exec开头的函数,统称exec函数:

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

int	main(int argc, char **argv)
{
   // execl("/bin/ls","ls","-l","-a","--color=auto",NULL);//list
   //execl("/usr/bin/firefox","firefox","www.baidu.com",NULL);
  // execl("b.out","b.out",NULL);
    //printf("看见就错了");
    //echo $PATH
    //execlp("ls","ls","-l","-a","--color=auto",NULL);//list path
    //execlp("cat","cat","./01forck.c",NULL);
   // execlp("./b.out","b.out",NULL);
    char *args[]={"ls","-l","-a","--color=auto",NULL};
    //execv("/bin/ls",args);//vector
    execvp(args[0],args);
    printf("看见就错了");
    
    return 0;
}

char *const ps_argv[] ={"ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL};
char *const ps_envp[] ={"PATH=/bin:/usr/bin", "TERM=console", NULL};

execl("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
execv("/bin/ps", ps_argv);
execle("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL, ps_envp);
execve("/bin/ps", ps_argv, ps_envp);
execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
execvp("ps", ps_argv);

 这些函数的区别:
1)前4个使用路径名作为参数,后面两个使用文件名做参数
当filename中,含有/时视为路径名,否则就按PATH变量,在指定目录下查找可执行文件。

2)相关的参数表传递
l表示list,v表示vector
execl,execlp,execle,需要将参数一个一个列出,并以NULL结尾。
execv,execvp,execve,需要构造一个参数指针数组,然后将数组的地址传入。

3)以e结尾的函数,可以传入一个指向环境字符串的指针数组的指针。其他未指定环境变量,使用父进程继承过来的。
execve 是真正的系统调用
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。

6.system("vim 1.c");

int system(const char *command); fork+exec 
功能:使用该函数可以将shell命令直接在代码中执行。
参数:command要执行的shell命令
返回值:成功 0
失败 -1

system("vim 01fork.c");

四、总结

补充:

1.whereis xx 查看计算机上是否有装载该命令,需是系统级别的,不可查看可执行文件

2.echo 命令行打印  PATH系统级别的环境变量  代表路径

3.ps aux 查看命令路径 

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐