linux下的进程(process)相关API函数(创建(fork vfork clone)、回收(wait\waitpid)、退出(exit\_exit))及软件开发相关面试问题

进程控制

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

1、进程的创建(三种方法)

1.1、fork()

  • fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
  • 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
1.1.1、函数原型、参数说明和返回值
pid_t fork(void)//创建一个子进程
//使用:
pid_t pid = fork();//定义了一个pid_t类型的变量pid,fork()函数返回一个进程号,这个进程号赋给了pid
  • pid_t: 类似一个类型,就像int型一样,int型定义的变量都是整型的,pid_t定义的类型都是进程号类型。
  • 返回值:
    • 1、>0 在父进程中,fork返回新创建的子进程的PID
    • 2.、0 在子进程中,fork返回0
    • 3、<0 如果出现错误,fork返回一个负值
      换句话说,返回0代表子进程,返回正整数(子进程ID)代表父进程,如果返回-1代表调用失败,errno会被设置。
1.1.2、创建特征说明
  • 1、调用一次,会有两个返回值
  • 2、先返回哪个值,不确定, 用户可以通过延时函数,决定进程的执行先后顺序。
  • 3、创建后,子进程复制父进程空间,这个空间子进程所独有的只有它的进程号、计数器和资源使用(子进程有自己的task_struct结构(复制来的)和pid
    • 这样得到的子进程独立于父进程, 具有良好的并发性,但是二者之间的通讯需要通过专门的通讯机制,如: pipe,共享内存等机制
  • 4、其他的和父进程空间一样,两个空间相互独立,互不干涉即使全局变量,在子进程中不会随父进程中变量的值的改变而改变。
    • 从父进程复制出子进程,其实是复制出一个新的虚拟内存,因为我们的程序是运行在虚内存中的,所以我们也将虚拟内存空间称为进程空间,复制出的子进程(新的虚拟内存在空间)拥 有如下特点:父进程(父亲虚拟内存)的栈,堆,bss,. data, .ro.data 会从父进程中完全的复制 出一分给子进程(孩子虚拟内存),但是.text却是父子进程所共享
1.1.3、fork 时的 资源继承说明(面试要点)
  • 1、继承资源:
  • 实际UID、GID和有效UID,GID、附加GID
  • 调用exec()时的关闭标志
  • UID设置模式比特位、GID设置模式比特位
  • 进程组号、会话ID、控制终端、环境变量
  • 当前工作目录、根目录
  • 文件创建掩码UMASK、文件长度限制ULIMIT
  • 预定值, 如优先级和任何其他的进程预定参数, 根据种类不同决定是否可以继承,一些其它属性

2、进程也有与父进程不同的属性:

  • 进程号,子进程号与任何一个活动的进程组号不同
  • 子进程继承父进程的文件描述符或流时, 具有自己的一个拷贝;并且与父进程和其它子进程共享该资源面试问过
  • 子进程的用户时间和系统时间被初始化为0
  • 子进程不继承父进程的记录锁
  • pending signals 也不会被继承
    在这里插入图片描述
1.1.4、写时复制(copy-on-write)技术简单说明

fork函数创建出子进程后,子进程和父进程几乎是一样的,但是这对我们来说是没有什么意义的,我们需要将我们新的程序代码和数据复制到子进程中(复制出来的虚拟内存中),这个过程由exec函数(该函数后面学习)实现,当需要向子进程中写入新的代码段时,子进程的.text段才被复制出新空间来并写入新的程序的代码,此时子进程的代码段会和父进程的代码段会慢慢分开,不再共享,这就是写时复制技术。

1.1.5、子进程继承父进程已经打开的文件描述符
//https://www.cnblogs.com/tshua/p/5756465.html
int main(void)
{    
        int fd = -1, ret = -1; 
    
        fd = open("file", O_CREAT|O_RDWR, 0664);
        if(fd < 0) {   
                perror("open file is fail");
                exit(-1);
        }   
  
        ret = fork();
        if(0 == ret){    //返回0是子进程   
                printf("in parent fd = %d\n", fd);    
                write(fd, "hello ", 6); 
        }   
        else if(ret > 0){  //返回正整数时父进程
                printf("in child fd = %d\n", fd);    
                write(fd, "world\n", 6); 
        }   
        else if(-1 == ret) perror("fork is fail");
    
        return 0;
}

打开file文件,文件中结果如下:

hello
world 

我们发现这两个进程都向同一文件里面写数据,但是并没有出现数据相互覆盖的情况,所以我们可以猜测父子进程中的这两个文件描述符必然共享同一文件表中的文件读写指针,之所以world先写,这是由于父进程先执行导致的。
在这里插入图片描述

1.2、vfork()(面试通常和fork比较)

不同于fork(父进程的内存数据copy到子进程),用vfork创建的子进程与父进程共享地址空间,也就是说子进程完全运行在父进程的地址空间上,如果这时子进程修改了某个变量,这将影响到父进程。

1.2.1、函数原型(参数说明和返回值同fork)
pid_t vfork(void);
1.2.2、特征说明
  • 1、子进程与父进程共享地址空间,子进程修改了某个变量,这将影响到父进程。
    • 很多程序在fork一个子进程后就exec一个外部程序,于是fork需要copy父进程的数据这个动作就变得毫无意义,子进程共享的 vfork,这样成本比较低。因此,vfork本就是为了exec而生。
  • 2、vfork创建子进程后,父进程会被阻塞,直到子进程调用exit或exec函数族(exec,将可执行文件载入到地址空间并执行。),不能调用return
  • 3、用vfork()创建的子进程必须显示调用exit()来结束,否则子进程将不能结束,而fork()则不存在这个情况。
  • 4、调用vfork时,其中的代码要求尽可能少(因为容易不稳定)
    • 在调用exec或_exit之前与父进程数据是共享的,在它调用exec或_exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
    • 子进程在vfork()返回后直接运行在父进程的栈空间,并使用父进程的内存和数据。这意味着子进程可能破坏父进程的数据结构或栈,造成失败。

为什么return会挂掉,exit()不会?

如果你在子进程中return,那么基本是下面的过程:

  • 1)子进程的main() 函数 return了,于是程序的函数栈发生了变化。
  • 2)而main()函数return后,通常会调用 exit()或相似的函数(如:_exit(),exitgroup())
  • 3)这时,父进程收到子进程exit(),开始从vfork返回,但是尼玛,老子的栈都被你子进程给return干废掉了,你让我怎么执行?(注:栈会返回一个诡异一个栈地址,对于某些内核版本的实现,直接报“栈错误”就给跪了,然而,对于某些内核版本的实现,于是有可能会再次调用main(),于是进入了一个无限循环的结果,直到vfork
    调用返回 error)

好了,现在再回到 return 和 exit,return会释放局部变量,并弹栈,回到上级函数执行。exit直接退掉。如果你用c++ 你就知道,return会调用局部对象的析构函数,exit不会。(注:exit不是系统调用,是glibc对系统调用 _exit()或_exitgroup()的封装)

可见,子进程调用exit() 没有修改函数栈,所以,父进程得以顺利执行。

但是!注意!如果你调用 exit() 函数,还是会有问题的,正确的方法应该是调用 _exit() 函数,**因为 exit() 函数 会 flush 并 close 所有的 标准 I/O ,这样会导致父进程受到影响。原因如下

子进程调用的是 _exit 而不是 exit。exit 会实现冲洗标准 I/O 流,因此如果子进程调用的是 exit,那么会出 现两种情况:

  • ​ A. 如果 函数库采取的唯一操作就是冲洗 I/O 流,那么此时得到的输出结果与子进程调用 _exit 时得到的输出结果是一样的;
  • B. 如果 该实现也关闭了 I/O 流,那么表示标准输出 FILE
    对象的相关存储区将被清0.因为子进程借用了父进程的地址空间,因此当父进程恢复运行并调用 printf 的时候,printf会返回-1,即不会产生任何输出结果。
1.2.3、fork与vfork的实验区别

1、fork()

#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
 
int main()
{
    pid_t pid;
    int cnt = 0;
    pid = fork();
    if(pid<0)
        printf("error in fork!\n");
    else if(pid == 0)
    {
        cnt++;
	printf("I am the child process.\n");
        printf("cnt= %d,cnt_address= %p,child_ID = %d\n",cnt,&cnt,getpid());
    }
    else
    {
        printf("I am the parent process.\n");
        printf("cnt= %d,cnt_address= %p,parent_ID = %d\n",cnt,&cnt,getpid());
    }
    return 0;
}

在这里插入图片描述

  • 子进程cnt的值改变了,父进程的没有改变,说明占用不同的内存空间。
  • 子进程与父进程cnt的地址(虚拟地址)是相同的(注意他们在内核中被映射的物理地址不同)

2、vfork()

#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
 
int main()
{
    pid_t pid;
    int cnt = 0;
    pid = vfork();
    if(pid<0)
        printf("error in fork!\n");
    else if(pid == 0)
    {
	printf("I am the child process.\n");
        printf("cnt= %d,cnt_address= %p,child_ID = %d\n",++cnt,&cnt,getpid());
	_exit(0); 
    }
    else
    {
        printf("I am the parent process.\n");
        printf("cnt= %d,cnt_address= %p,parent_ID = %d\n",++cnt,&cnt,getpid());
    }
    return 0;
}

在这里插入图片描述

  • 子进程cnt的值改变了,父进程的跟着改变。
  • 虚拟地址和物理地址是相同的
  • 并且子进程先执行,父进程等待
    注意:当不使用使用_exit(0); 而使用return来退出,实验结果如下:
    在这里插入图片描述
    异常退出。
    vfork()与fork()区别:
  • 1、fork() 子进程拷贝父进程的数据段,代码段.
    vfork() 子进程与父进程共享数据段.|
  • 2、fork() 父子进程的执行次序不确定.
    vfork():保证子进程先运行。

1.3、clone()

 #include <sched.h>//头文件

fork()是全部复制,vfork()是共享内存,而clone() 是则可以将父进程资源有选择地复制给子进程,而没有复制的数据结构则通过指针的复制让子进程共享,具体要复制哪些资源给子进程,由参数列表中的 clone_flags来决定。

1.3.1、函数原型、参数说明和返回值

实际上, clone系统调用是fork和pthread_create的通用形式,它允许调用者指定在调用进程和新创建的进程之间共享哪些资源。clone()的主要用途是实现线程:在共享内存空间中并发运行的程序中的多个控制线程。

int clone(int (*fn)(void *), void *child_stack,
         int flags, void *arg, ...
         /* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );

返回值是子进程的pid,出错的话返回-1,设置errno,并不建立子进程。

  • fn:函数指针
  • child_stack:为子进程分配系统堆栈空间(在linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块task_struct的值)
  • flags:用来描述你需要从父进程继承那些资源
    • CLONE_VM ,父子进程运行在同一段内存
    • CLONE_FS ,父子进程共享在root文件系统,当前工作目录以及umask信息
    • CLONE_FILES ,父子信息共享文件描述符
    • CLONE_SIGHAND ,父子进程共享在父进程上的信号处理器
    • CLONE_PID ,父子进程具有相同的PID
  • **arg:**传给子进程的参数
1.3.2、应用实例

下面的例子是创建一个线程(子进程共享了父进程虚存空间,没有自己独立的虚存空间不能称其为进程)。父进程被挂起当子线程释放虚存资源后再继续执行。

//https://blog.csdn.net/gatieme/article/details/51417488
#include <stdio.h>
#include <malloc.h>

#include <sched.h>
#include <signal.h>

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


#define FIBER_STACK 8192
int a;
void * stack;

int do_something()
{
    printf("This is son, the pid is:%d, the a is: %d\n", getpid(), ++a);
    free(stack); //这里我也不清楚,如果这里不释放,不知道子线程死亡后,该内存是否会释放,知情者可以告诉下,谢谢
    exit(1);
}

int main()
{
    void * stack;
    a = 1;
    stack = malloc(FIBER_STACK);//为子进程申请系统堆栈

    if(!stack)
    {
        printf("The stack failed\n");
        exit(0);
    }
    printf("creating son thread!!!\n");

    clone(&do_something, (char *)stack + FIBER_STACK, CLONE_VM|CLONE_VFORK, 0);//创建子线程

    printf("This is father, my pid is: %d, the a is: %d\n", getpid(), a);
    exit(1);
}

在这里插入图片描述
parent和son中的a都为2;所以证明他们公用了一份变量a,是指针的复制,而不是值的复制。

1.3.3、fork vfork clone 区别

区别

  • 1、系统调用fork()和vfork()是无参数的,而clone()则带有参数
  • 2、fork()是全部复制,vfork()是共享内存,而clone()是则可以将父进程资源有选择地复制给子进程,而没有复制的数据结构则通过指针的复制让子进程共享,具体要复制哪些资源给子进程,由参数列表中的clone_flags决决定。(clone和fork最大不同在于clone不再复制父进程的栈空间,而是自己创建一个新的。 (void *child_stack,)也就是第二个参数,需要分配栈指针的空间大小,所以它不再是继承或者复制,而是全新的创造。
  • 3、执行顺序不同:
    • fork不对父子进程的执行次序进行任何限制,fork返回后,子进程和父进程都从调用fork函数的下一条语句开始行,但父子进程运行顺序是不定的,它取决于内核的调度算法;
    • vfork调用中,子进程先运行,父进程挂起,直到子进程调用了exec或exit之后,父子进程的执行次序才不再有限制;
    • clone中由标志CLONE_VFORK来决定子进程在执行时父进程是阻塞还是运行,若没有设置该标志,则父子进程同时运行,设置了该标志,则父进程挂起,直到子进程结束为止

2、回收子进程的函数

#include <sys/types.h>
#include <sys/wait.h>
  • 一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。
  • wait或waitpid调用只能清理一个子进程,清理多个子进程需要用到循环

2.1、wait函数

等待任意一个子进程,这里指的是任意一个,调用该函数的进程的任意一个子进程结束,那么内核就会发送SIGCHILD信号给父进程通知它的某个子进程结束了,wait函数之前的阻塞会被SIGCHILD唤醒,并且获取到终止子进程的终止状态,将子进程的返回值填入到wait里面的status中。

2.1.1、函数原型、参数说明、返回值
pid_t wait(int *status); 
  • 返回值:成功返回终止进程的进程ID,失败返回非-1值(没有子进程),errno被设置。
  • status: 整型指针。如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中,这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的,以及正常结束时的返回值,或被哪一个信号结束的等信息。
2.1.2、函数三个功能(同waitpid)
  • 1、阻塞等待子进程退出
    • 父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出
  • 2、回收子进程残留资源
    • 当父进程忘了用wait()函数等待已终止的子进程时,子进程就会进入一种无父进程的状态,此时子进程就是僵尸进程.
    • wait()要与fork()配套出现,如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID.
    • 如果先终止父进程,子进程将继续正常进行,只是它将由init进程(PID 1)继承,当子进程终止时,init进程捕获这个状态.
  • 3、 获取子进程结束状态(退出原因)
    • 参数status用来保存被收集进程退出时的一些状态
2.1.3、返回状态(status)获取方法

请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数–指向整数的指针status,而是那个指针所指向的整数

  • 1,WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。
  • 2、 WIFSIGNALED(status) 为非0 → 进程异常终止,
    WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
  • 3, WEXITSTATUS(status)当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status)就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。
2.1.4、使用案例
/***********************************************************
    功能说明:进程等待wait()方法的应用
    author: linux.sir@qq.com
***********************************************************/
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>//pid_t的定义
#include <unistd.h>
#include <wait.h>
#include <errno.h>
#include <stdlib.h>

void waitprocess();
int main(int argc, char * argv[])
{
  waitprocess();  
}
void waitprocess()
{
  int count = 0;
  pid_t pid = fork();//创建子进程,返回两个值,父进程返回子进程ID>0,子进程返回0
  int status = -1;  
  if(pid<0)
  {
    printf("fork error for %m\n",errno );
  }
  else if(pid>0) //父进程空间
  {
    printf("this is parent ,pid = %d\n",getpid() );
    wait(&status);//父进程执行到此,马上阻塞自己,直到有子进程结束。当发现有子进程结束时,就会回收它的资源。    
  }
  else //子进程空间
  {
    printf("this is child , pid = %d , ppid = %d\n",getpid(),getppid());
    int i;    
    for (i = 0; i < 10; i++)
    {
      count++;
      sleep(1);//进入睡眠,暂时释放时间片,给其他线程
      printf("count = %d\n", count);      
    }
    exit(5); //非0表示子进程异常退出,返回至wait处,回收子进程资源
  }
  printf("child exit status is %d\n", WEXITSTATUS(status));//status是按位存储的状态信息,需要调用相应的宏来还原一下  
  printf("end of program from pid = %d\n",getpid() );  //父进程结束
}

在这里插入图片描述

2.2、waitpid函数

等待我们通过pid参数说明的某个进程的结束,当满足条件的子进程结束时,那么内核就会发送SIGCHILD信号给父进程通知它的有子进程结束了,wait函数之前的阻塞会被SIGCHILD唤醒,并且获取到终止子进程的终止状态;可以不阻塞。

2.2.1、函数原型、参数说明、返回值
pid_t waitpid(pid_t pid, int *status, int options);

1、参数:

  • pid:等待的子进程ID
    • pid > 0 时,仅仅等待进程id等于pid的子进程,无论其他已经有多少子进程运行结束退出,仅仅要指定的子进程还没有结束,waitpid就会一直等下去.
    • pid = -1 时,等待不论什么一个子进程退出,没有不论什么限制,此时 waitpid 和 wait 的作用一模一样.
    • pid = 0 时,等待统一进程组中的不论什么子进程,假设子进程已经增加了别的进程组,waitpid 不会对它做不论什么理睬.
    • pid < -1 时, 等待一个指定进程组中的不论什么子进程,这个进程组的ID等于pid的绝对值。
  • status:子进程的结束状态值(同wait)
  • options:提供了一些另外的选项来控制waitpid()函数的行为
    • WNOHANG:若由pid指定的子进程未发生状态改变(没有结束),则waitpid()不阻塞,立即返回0,若正常结束,则返回该子进程的ID
    • WUNTRACED:返回终止子进程信息和因信号停止的子进程信息
    • WCONTINUED:返回收到SIGCONT信号而恢复执行的已停止子进程状态信息

2、返回值

  • 当正常返回的时候,waitpid返回收集到的子进程的进程ID;
  • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
2.2.2、wait(),waitpid()区别
  • a)waitpid函数比wait函数功能更强大,wait函数只能等待任意进程一个进程状态的改变,而且只能等待一种进程状态的改变,那就是进程终止,但是waitpid除了可以等待进程终止外,还可以等待进程是否暂停等其它的进程状态改变。
  • b)wait函数一定是阻塞的,而waitpid是否阻塞则可被设置。
  • c)wait函数等待的是任意一进程终止,waitpid则可等待指定进程的进程状态改变。
2.2.3、使用案例(阻塞和非阻塞)

1、阻塞方式:option 设为 0

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

int main()
{
 	 pid_t pid;
 	 pid = fork();
	 if(pid<0)
	 {
	    perror("fork");
	    exit(1);
	 }
	 if(pid==0)
	 {
	    printf("child is run,child pid is :%d\n",getpid());//getpid()得到的是当前进程的pid
	    sleep(2);
	    exit(5);
	 }
	 if(pid > 0 )//父进程
	 {
	    int status=0;
	    pid_t ret=waitpid(-1,&status,0);//pid = -1等待不论什么一个子进程退出.阻塞式等待2秒
	    printf("parent waiting..................\n");
	    if(WIFEXITED(status)&&(ret==pid))//获取status状态
	    {
	       printf("child is 2s succes.child exit code is :%d\n",WEXITSTATUS(status));
	    }
	    else if(ret>0)
	    {
	       printf("wait child failed.\n");
	       exit(1);
	    }
	 }
	 return 0;
}

在这里插入图片描述
2、非阻塞等待

//https://blog.csdn.net/dangzhangjing97/article/details/79745880
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>

int main()
{
 	pid_t pid=fork();
	 if(pid<0)
	 {
	    perror("fork");
	    exit(1);
	 }
	 if(pid==0)
	 {
	    printf("child is run,child pid is :%d\n",getpid());//getpid()得到的是当前进程的pid
	    sleep(2);
	    printf("child run finally.\n");
	    exit(5);
	 }
	 if(pid>0)
	 {
	     int status=0;
  	     pid_t ret=0;//记得需要声明ret
	     do
		  {
		  	  ret=waitpid(-1,&status,WNOHANG);//非阻塞式等待
	          if(ret==0)
		        {
		    		printf("child is running,\n");
		        }
		      sleep(1);
		  }while(ret == 0);//ret!=0表示子进程结束
	    if(WIFEXITED(status)&&(ret==pid))
	    {
	       printf("child is 2s succes.child exit code is :%d\n",WEXITSTATUS(status));
	    }
	    else if(ret>0)
	    {
	       printf("wait child failed.\n");
	       exit(1);
	    }
	 }
	 return 0;
}

在这里插入图片描述
非阻塞式等待,就是谁先执行不一定,

  • 1、父进程先执行的,然后让父进程睡眠1s
  • 2、子进程执行,子进程执行到sleep(2)时
  • 3、此时又去执行父进程,但是子进程还没有执行结束,故一直执行while循环
  • 4、2s过后,又再次遇到sleep(1),又去执行子进程
  • 5、这时子进程exit(5)就会结束
  • 6、父进程继续完成自己的工作

3、进程退出

关闭进程打开的文件描述符,释放它占用的内存和其他资源
在这里插入图片描述

3.1 正常退出

  • 1、main函数调用return
  • 2、在程序的任意位置调用exit函数;
  • 3、在程序的任意位置调用_exit函数; ### 3.1 正常退出
3.1.1 exit 和 _exit()

在这里插入图片描述

3.1.1.1 函数原型
#include<stdlib.h> 
void exit(int status);
/*exit()就是退出,传入的参数是程序退出时的状态码,0表示正常退出,其他表示非正常退出,一般都用-1或者1,标准C里有EXIT_SUCCESS和EXIT_FAILURE两个宏,用exit(EXIT_SUCCESS);*/

#include<unistd.h>
void  _exit(int  status)
//立即终止一个进程。任何被本进程打开的文件描述符都会被关闭;该进程的父进程会收到SIGCHLD信号。
  • status :一个整型的参数,可以利用这个参数传递进程结束时的状态。一般来说,0表示正常结束;其他的数值表示出现了错误,进程非正常结束
3.1.2、exit 和 _exit()对比
  • _exit 函数的作用是:直接使进程停止运行,清除其使用的内存空间,并清除其在内核的各种数据结构;exit 函数则在这些基础上做了一些小动作,在执行退出之前还加了若干道工序。
  • exit() 函数与 _exit() 函数的最大区别在于exit()函数在调用exit 系统调用前要检查文件的打开情况,把文件缓冲区中的内容写回文件。也就是图中的“清理I/O缓冲”。简单点说:exit先刷新流数据,将文件缓冲区的内容写回文件,可以保证数据的完整性,_exit会将数据直接丢失
    • printf()、fopen()、fread()、fwrite()都在此 列,它们也被称作”缓冲I/O(buffered I/O)”,其特征是对应每一个打开的文件,在内存中都有一片缓冲区,每次读文件时,会多读出若干条记录,这样下次读文件时就可以直接从内存的缓冲区中读取,每次写文件的时候,也仅仅是写入内存中的缓冲区,等满足了一定的条件(达到一定数量,或遇到特定字符,如换行符和文件结束符EOF), 再将缓冲区中的 内容一次性写入文件,这样就大大增加了文件读写的速度,但也为我们编程带来了一点点麻烦。如果有一些数据,我们认为已经写入了文件,实际上因为没有满足特 定的条件,它们还只是保存在缓冲区内,这时我们用_exit()函数直接将进程关闭,缓冲区中的数据就会丢失,反之,如果想保证数据的完整性,就一定要使用exit()函数。
3.1.3、exit 和 _exit()实验案例
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>


int main()
{

     pid_t result;
     result = fork();
     if(result<0)
     perror("fork");
     if(result == 0)
     {
             printf("This is _exit test\n");
             printf("This is the _exit content in the buffer");
             _exit(0);
     }
     else
     {
             printf("This is exit test\n");
             printf("This is the exit content in the buffer\n");
             exit(0);
     }
     return 0;
}

在这里插入图片描述
结果分析:

  • 子进程中运行_exit(0)并未将Thisis the content in the
    buffer打印出来,而父进程中运行的exit(0)将Thisis the exit content in the buffer打印出来了。
  • 说明,exit(0)会在终止进程前,将缓冲I/O内容清理掉,而_exit(0)是直接终止进程,并未将缓冲I/O内容清理掉,所以不会被打印出来。

_exit(0);改成exit(0);结果如下
在这里插入图片描述
说明,exit(0)会在终止进程前,将缓冲I/O内容清理掉,所以即使printf里面没有 \n也会被打印出来。

3.2 异常退出

  • 自杀:自己调用abort函数,自己给自己发一个信号将自己杀死,杀人凶器是信号;
  • 他杀:由别人发一个信号,将其杀死,杀人凶器也是信号,这些信号是由其它进程或内核才生的(kill信号)

参考

1、https://www.cnblogs.com/shijiaqi1066/p/3836017.html
2、https://www.cnblogs.com/tshua/p/5756465.html
3、https://www.cnblogs.com/schips/p/12499783.html
4、https://www.cnblogs.com/suibian1/p/10859706.html
5、https://www.cnblogs.com/gdf456/p/12918327.html
6、https://blog.csdn.net/gatieme/article/details/51417488
7、https://blog.csdn.net/vict_wang/article/details/88546963

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 进程是操作系统中的一个重要概念,它是正在运行的程序的实例。在 Linux 系统中,进程管理是非常重要的一项任务,它涉及到进程创建、销毁、监控和通信等操作。 在 Linux 中,有三个常用的进程管理函数wait、exec 和 system。 wait 函数用于等待子进程的结束,并获取它的退出状态。具体来说,wait 函数会挂起当前进程,直到它的子进程结束。然后,它会把子进程退出状态保存在一个指针参数中,以便当前进程可以读取这个状态。如果子进程被信号终止,wait 函数还可以获取信号的信息。 exec 函数用于替换当前进程的映像,即把当前进程的映像替换为一个新的程序。具体来说,exec 函数会调用一个指定的程序,并运行它,而不是当前进程中的程序。执行 exec 函数后,当前进程的 PID 和其他属性仍然是不变的,但是它的映像会被新的程序替换。 system 函数用于执行一个 shell 命令,并等待它的结束。具体来说,system 函数会调用一个 shell 进程,然后执行一个指定的命令,并等待命令执行完毕后再返回。它的返回值是 shell 命令的退出状态。 这三个函数都是进程管理中非常重要的工具,开发人员可以通过它们来实现进程创建、执行和监控等操作。但是在使用这些函数时,我们需要注意安全性和系统性能,避免对系统造成不必要的影响。 ### 回答2: Linux进程管理中,wait、exec、system是非常重要的三个命令,它们具有不同的作用和用法,能够帮助用户有效地管理进程wait命令用于等待一个进程结束并返回该进程退出状态码,其语法为“wait [pid]”,其中pid表示要等待的进程ID。如果不指定pid,则wait会等待所有子进程结束并返回最后一个结束的子进程退出状态码。wait命令通常与fork和exec命令配合使用,可以方便地管理并发执行的多个进程。 exec命令用于在当前进程中执行新的命令,其语法为“exec [command]”,其中command表示要执行的命令。exec命令会将当前进程的地址空间清空,并把新的命令加载到其中,然后直接执行。因此,exec命令可以用于实现进程替换,即将当前进程替换为一个新的进程。exec命令一般不会创建新的进程,而是直接在当前进程中执行新的命令,因此它可以用于在一个shell中执行另一个shell脚本,从而避免了创建多个进程的开销。 system命令可以在新的进程中执行指定的命令,并等待命令完成后返回其退出状态码,其语法为“system [command]”,其中command表示要执行的命令。system命令会创建一个新的进程,并在其中执行指定的命令,然后等待命令完成,并返回其退出状态码。system命令一般用于在shell脚本中执行命令并获取其返回值。 总之,wait、exec、system这三个命令在Linux进程管理中非常重要,能够帮助用户高效地管理进程。通过这些命令的使用,用户可以方便地等待进程结束、实现进程替换、执行新的命令并获取结果等。其中的使用方法和技巧,需要根据实际应用场景加以理解和运用。 ### 回答3: 进程Linux操作系统的核心之一,而进程管理又是Linux系统运维和开发中非常重要的一项工作。Linux系统提供了多种进程管理命令,包括wait、exec和system等命令。下面就分别介绍一下这三个命令的使用方法及其作用。 wait命令 wait命令是一种用于进程管理的命令。它的作用是等待指定的进程结束,并返回该进程退出状态。在编写shell脚本时,我们通常需要等待子进程的结束,然后根据其退出状态来做出相应的处理。在这种情况下,wait命令可以非常方便地帮助我们实现这一功能。 wait命令的使用方法非常简单,只需要在命令行中输入wait,即可等待当前所有子进程结束,并返回状态值。 exec命令 exec命令是一个非常重要的进程管理命令,它的作用是用指定的命令替换当前的进程。通过exec命令,我们可以实现各种进程管理任务,包括重定向输入输出、执行后台任务等。exec命令还可以用于切换shell环境或者切换用户等功能。 exec命令的使用方法也非常简单,只需要在命令行中输入exec,然后输入要执行的命令即可。例如,我们可以使用exec命令将bash shell切换为zsh shell,只需要在命令行中输入exec zsh,就可以完成这一操作。 system命令 system命令是一个用于执行系统命令的命令。它的作用与在命令行中直接输入系统命令类似,可以帮助我们快速执行各种系统命令。当我们需要在脚本中执行一些特殊的系统命令时,可以使用system命令来实现。 system命令的使用方法也非常简单,只需要在命令行中输入system,然后输入要执行的系统命令即可。例如,我们可以使用system命令执行ls命令,只需要在命令行中输入system('ls'),就可以列出当前目录下所有文件和文件夹。需要注意的是,在使用system命令时,要保证输入的系统命令是可靠的,否则可能存在安全问题。 总之,Linux进程管理命令wait、exec和system是Linux系统运维和开发中非常重要的工具,它们可以帮助我们快速实现各种进程管理任务和系统命令的执行。需要注意的是,在使用这些命令时,要保证操作的正确性和安全性,避免发生不必要的问题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值