文章目录

  • 1. 进程创建
  • 1.1 fork函数
  • 1.2 写时拷贝
  • 1.3 vfork函数
  • 2. 进程终止
  • 2.1 场景
  • 2.2 终止的方法
  • 2.2.1 exit函数和_exit函数区别
  • 2.3 执行用户定义的清理函数
  • 2.4 刷新缓冲区的方式
  • 3. 进程等待
  • 3.1 作用
  • 3.2 wait函数
  • 3.3 查看进程调用堆栈的命令
  • 3.4 阻塞
  • 3.5 waitpid函数


1. 进程创建

1.1 fork函数

  子进程拷贝父进程的PCB,子进程的大部分数据是来源于父进程,例如:内存指针(数据段和代码段)
  父进程创建子进程成功之后,父进程是独立的两个进程,父子进程的调度取决于操作系统内核,进程是抢占的方式执行的,父子进程谁先运行是不确定的,子进程拥有自己独立的进程。

1.2 写时拷贝

centos kill进度_父进程


  一个进程就会有一个PCB,task_struct,父进程内存指针指向了父进程的进程虚拟地址空间,但是这个进程虚拟地址空间并不能真正的存储数据,真正的数据是存储在物理内存上的,因此,我们需要把进程虚拟地址空间和物理内存上的数据对应起来,我们就引入了页表。

  当父进程执行fork函数创建了一个子进程,子进程会复制父进程的内存指针,进程虚拟地址空间中的内容还有页表中的对应关系,并且页表中所对应的数据的也是一样的,但是当在子进程中如果修改了数据的值,这时就会存在写时拷贝会在物理内存中重新开辟一个空间

1.3 vfork函数

  使用vfork函数创建的出来的子进程和父进程指向同一个进程虚拟地址空间。

问题
  在进行函数调用压栈的时候,如果子进程调用不出栈,那么父进程也将无法出栈。

解决方案
  先把子进程处理完,再处理父进程。

补充: 执行ulimit -a,里面中有一个max user processes,这个是最大进程限制。

centos kill进度_centos kill进度_02

2. 进程终止

2.1 场景

  1. 从main函数的return返回
  2. 代码执行完毕获得目标结果
  3. 代码执行完毕未获得目标结果
  4. 程序崩溃,异常中止

2.2 终止的方法

  1. main函数return返回
  2. exit函数
  3. _exit函数

2.2.1 exit函数和_exit函数区别

  在linux的标准库函数中,有一套称作高级I/O的函数,我们熟知的printf 、fopen 、fread 、fwrite都在此列,他们也被称作缓冲I/O。其特征是对应每一个打开的文件,都存在一个缓冲区, 在内存中都有一片缓冲区,每次读文件会多读若干条记录,这样下次读文件时就可以直接从内存的缓存中取出,每次写文件时也仅仅是写入到内存的缓冲区,等待满足一定的条件(达到一定的数量,或者遇到特定字符,如换行和文件结束符EOF),再将缓冲区的内容一次性的写入文件,这样就大大增加了文件读写的速度,但也为我们编程带来了一点点麻烦,如果有些数据,我们认为已经写入了文件,实际上因为没有满足特定的条件,他们还只是保存在缓冲区内,这时我们用_exit函数直接将程序关闭,缓冲区中的数据就会丢失,反之,如果想保证数据的完整性,就一定要使用exit函数。

  1. exit是一个库函数,_exit是一个系统调用函数。
  2. exit函数定义在stdlib.h中,而_exit定义在unistd.h中。
  3. exit和_exit都是用于终止的函数,但是exit会执行用户定义的清理函数和刷新缓冲区和关闭流等。

  exit是一个库函数,_exit是一个系统调用函数,而在C库中会冲刷缓冲区关闭流。

centos kill进度_进程退出_03


centos kill进度_子进程_04


  void exit (int status)

  void _exit (int status)

  status会被进程等待接口获得,使用echo $?查看进程退出码

echo $?

centos kill进度_进程退出_05


  因为我在这里的程序是正常运行然后退出的,所以进程退出码是0。

2.3 执行用户定义的清理函数

int atexit (void(*function)(void))
{
	atexit(func);
	printf("hello\n");
}

  这里面的参数是一个指针函数,func是一个回调函数,当etexit退出时执行回调函数

2.4 刷新缓冲区的方式

  1. \n
  2. fflush函数
  3. exit()
  4. main函数的return

3. 进程等待

3.1 作用

  父进程进行进程等待,子进程先于父进程退出,由于父进程在等待子进程,所以父进程会回收子进程退出资源,从而防止子进程变成僵尸进程。

3.2 wait函数

头文件
  #include <sys/wait.h>

pid_t wait (int *status);

centos kill进度_进程退出_06


返回值

  成功  返回子进程pid

  失败  -1

接收的参数

  整型指针  int *  4个字节,但只是用最右边的两个字节。

centos kill进度_进程退出_07


  进程退出码是return ,exit(),exit()返回过来中间的数字。

  coredump标志位,如果为1,表示进程有coredump产生,核心转储文件,如果为0,表示当前进程没有coredump产生。

  终止信号,当前的程序是由什么信号导致终止的,比如有6号信号(double free)和11号信号(解引用空指针)。

正常情况
  只看进程退出码。
异常情况下
  进程退出码毫无意义,这时需要看终止信号和coredump标志位

补充
  参数int* status是一个出参,调用者准备一个int类型变量,将地址传递给wait函数,wait函数在自己内部进行实现赋值,当wait函数返回值之后,调用者就可以通过int变量,获取进程退出的信息。

3.3 查看进程调用堆栈的命令

命令: pstack [pid]

centos kill进度_centos kill进度_08

3.4 阻塞

  当前执行流调用某个函数的时候,一直没有返回,此时称为这个现象是阻塞。子进程退出会给父进程发送SIGCHLD信号,而父进程会忽略处理,wait内部就是判断是否接收到一个SIGCHLD信号,如果有,回收资源。

如何获取进程退出码?
  (status>>8)& 0xFF
如何获取终止信号?
  status & 0x7F
如何获取coredump?
  (status>>7) & 0x1

注意

  如果想要获取coredump,需要执行ulimit -a

centos kill进度_centos kill进度_09


命令: unlimit -c unlimited

功能: 接收coredump

命令: unlimit -c 0

功能: 不接收coredump

3.5 waitpid函数

pid_t waitpid(pid_t pid,int * status,int options)
返回值一共有四类,重点有这两个
  -1  等待任一子进程,一旦等待到了,则返回,比如多个子进程一个子进程返回则返回。
  >0  等待特定的子进程,大于0的值就是子进程PID号

options

  WNOHANG    非阻塞等待方式(非阻塞等待需要搭配循环,直到完成函数功能)
  0         阻塞等待