【进程的创建和回收】

目录

 1.进程基础

(1)进程概念和程序概念区别(了解)

(2)进程内容(理解)

(3)进程类型(理解)

(4)进程状态图(理解)

2.进程常用命令

(1)查看进程信息(熟练)

(2)进程相关命令(熟练)

 3.进程的创建和结束(熟练)

(1)子进程概念

(2)子进程创建 – fork

(3)子进程创建-fork-示例

(4)父子进程

 (5)一个父进程创建多个子进程

(6)进程结束-exit/_exit

Ⅰ.exit()

Ⅱ._exit()

Ⅲ._EXIT()

4.进程的回收(熟练)

(1)wait

(2)waitpid


 1.进程基础

(1)进程概念和程序概念区别(了解)

        程序:存放在磁盘上的指令数据的有序集合(文件)

        特点:静态的;

        进程:执行一个程序所分配的资源的总称;

        特点:动态的;

        进程是程序的一次执行过程 ,动态包括创建、调度、执行和消亡;

(2)进程内容(理解)

 进程包含的内容:
BSS段(Block Started by Symbol的简称):存放程序中未初始化的全局变量的一块内存区域;

数据段:存放已初始化的全局变量的一块内存区域;

代码段:存放程序执行代码的一块内存区域,这部分区域的大小在程序运行前就已经确定;

堆(heap):用于存放进程运行中被动态分配的内存段,比如使用malloc等函数分配内存;

栈(stack) :栈又称堆栈,是用户存放程序临时创建的局部变量(但不包括static声明的变量,static意味着在数据段中存放变量),在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区用来存放局部变量、函数参数、函数的返回值;

进程控制块(Process Control Block,简称PCB):用于存储和跟踪记录进程的关键信息,包括PID、进程优先级、文件描述符表等;

(3)进程类型(理解)

交互进程:交互进程是在shell(命令行界面)下启动的进程。当在shell中输入一个命令,例如启动一个应用程序或执行一个脚本时,该命令会创建一个交互进程。交互进程可以在前台运行,这意味着它会占用shell并接受用户的输入,直到任务完成或手动中止。另外,还可以将交互进程放到后台运行,这样它就会继续在后台执行,不占用shell,并且可以继续在shell中输入其他命令。在Linux和Unix系统中,通过在命令末尾加上"&"符号可以将进程放到后台运行。

批处理进程:批处理进程通常是与终端无关的,它们被提交到一个作业队列中以便顺序执行。这种进程是针对一系列事先定义好的任务或指令的处理而不需要实时用户交互。它们通常用于自动化、批量处理、定期作业等场景。在一些操作系统中,批处理脚本可以编写包含多个命令的文件,然后将该文件提交给批处理系统进行执行。


守护进程:守护进程是在后台一直运行的进程,独立于任何终端或用户会话。它们通常在系统启动时启动,并持续运行以提供服务或执行特定的任务。守护进程通常不会与用户直接交互,而是在系统级别上提供服务,如网络服务、定时任务、日志记录等。守护进程常常以超级用户(root)权限运行,以便访问系统级别的资源和功能。

  • 交互进程是在shell下启动,可以前台运行或后台运行,并接受实时用户交互
  • 批处理进程是与终端无关的,按顺序执行预定义任务,通常用于自动化处理。
  • 守护进程在后台一直运行,提供系统级别的服务,与终端无关,并通常以超级用户权限运行。

(4)进程状态图(理解)

进程状态包括:运行态、等待态、停止态、死亡态

2.进程常用命令

(1)查看进程信息(熟练)

⭕ ps     查看系统进程快照(静态查看信息)

⭕ top    查看进程动态信息(动态查看信息)

⭕ /proc  查看进程详细信息

ps  是 "Process Status" 的缩写,用于查看系统中当前运行的进程快照。它显示活动的进程列表,以及每个进程的一些关键信息,如进程ID(PID)、进程状态、占用的CPU和内存资源等ps 命令可以根据选项显示不同的进程信息,例如:

  • 显示所有用户的进程,并包括详细信息,如CPU使用情况、内存使用情况等;
  • ps aux
  • 显示所有进程,但不包括线程信息;
  • ps -e
  • 显示长格式信息,包括进程的父进程ID、进程优先级等详细信息。
  • ps -l
  • 列出进程全部属性信息状态,通常和其他选项联用;
  • ps   -f
  • 显示所有进程,并包括完整的命令行信息。
  • ps -ef

top 是一个交互式的实时进程监视器,用于动态查看进程信息。它提供一个类似于任务管理器的界面,实时显示系统中运行的进程以及它们的CPU和内存使用情况top 命令默认按CPU使用率排序,并以实时更新的方式显示进程信息。可以通过按键切换不同的排序方式,如内存使用率、进程ID等。

top    查看进程动态信息

"shift" +"> "后翻页

"shift "+"<" 前翻页

top -p PID  查看某个进程

 /proc 目录是一个虚拟文件系统,它提供了有关正在运行的进程的详细信息。每个正在运行的进程在 /proc 目录下都有一个相应的目录,其名称以进程ID(PID)命名。进程的详细信息以文本文件的形式存储在相应的目录中,可以使用文本编辑器查看这些文件或者使用命令行工具(例如 cat 命令)来读取它们。在 /proc 目录下,可以查看有关进程的许多信息,如进程状态、进程命令行、进程打开的文件、进程的内存映射等。

信息页面解读:

表头

含义

F

进程标志,说明进程的权限,常见的标志有两个:

  • 1:进程可以被复制,但是不能被执行;
  • 4:进程使用超级用户权限;

S

进程状态。进程状态。常见的状态有以下几种:

  1. -D:不可被唤醒的睡眠状态,通常用于 I/O 情况。
  2. -R:该进程正在运行。
  3. -S:该进程处于睡眠状态,可被唤醒。
  4. -T:停止状态,可能是在后台暂停或进程处于除错状态。
  5. -W:内存交互状态(从 2.6 内核开始无效)。
  6. -X:死掉的进程(应该不会出现)。
  7. -Z:僵尸进程。进程已经中止,但是部分程序还在内存当中。
  8. -<:高优先级(以下状态在 BSD 格式中出现)。
  9. -N:低优先级。
  10. -L:被锁入内存。
  11. -s:包含子进程。
  12. -l:多线程(小写 L)。
  13. -+:位于后台。

UID

运行此进程的用户的 ID;

PID

进程的 ID;

PPID

父进程的 ID;

C

该进程的 CPU 使用率,单位是百分比;

PRI

进程的优先级,数值越小,该进程的优先级越高,越早被 CPU 执行;

NI

进程的优先级,数值越小,该进程越早被执行;

ADDR

该进程在内存的哪个位置;

SZ

该进程占用多大内存;

WCHAN

该进程是否运行。"-"代表正在运行;

TTY

该进程由哪个终端产生;

TIME

该进程占用 CPU 的运算时间,注意不是系统时间;

CMD

产生此进程的命令名;

(2)进程相关命令(熟练)

nice   用于改变或者设置进程的优先级

 用法:nice [-n NI值] 命令

NI 范围是 -20~19。数值越大优先级越低

普通用户调整 NI 值的范围是 0~19,而且只能调整自己的进程。

普通用户只能调高 NI 值,而不能降低。如原本 NI 值为 0,则只能调整为大于 0。

只有 root 用户才能设定进程 NI 值为负值,而且可以调整任何用户的进程。

举例:

nice -n -5 ./task1  使 task1 在运行时拥有较高的优先级,因为使用了较低的优先级值 -5。
nice -n 10 ./task2  使 task2 在运行时拥有较低的优先级,因为使用了较高的优先级值 10。

renice  用于在进程已经运行时动态地改变进程的优先级

用法:renice  [优先级]  PID

renice -5 -p 12345  把其进程ID为 12345的进程优先级设置成了-5
ctrl+z  把运行的前台进程转为后台并停止。

bg     将挂起的进程在后台运行

jobs   查看后台进程

fg      把后台运行的进程放到前台运行

./test &    把test程序后台运行
  1. Ctrl+Z:在终端运行时,当按下 Ctrl+Z 组合键时,它会将当前正在前台运行的进程暂停(挂起)。被挂起的进程可以通过 bg 命令继续在后台运行,或者通过 fg 命令将其放回前台运行。

  2. bgbg 命令用于将一个挂起的(暂停的)进程在后台继续运行。当使用 Ctrl+Z 将一个进程挂起后,可以使用 bg 命令使该进程继续在后台运行。

  3. jobsjobs 命令用于查看当前shell会话中的后台作业(后台进程)。它将显示在当前shell会话中运行的所有作业,并给出每个作业的作业号及其状态。

  4. fgfg 命令用于将后台运行的作业调回前台运行。如果有一个在后台运行的进程,可以使用 fg 命令将其放到前台执行,这样它将成为当前活动进程。

  5. ./test &:这是在后台运行名为 test 的程序的命令。通过在命令末尾加上 & 符号,该命令会使 test 程序在后台运行,这时可以继续在终端执行其他命令。

kill  -9 PID 杀掉进程

 3.进程的创建和结束(熟练)

(1)子进程概念

        子进程为由另外一个进程(对应称之为父进程)所创建的进程;

(2)子进程创建 – fork

 #include  <unistd.h>  

pid_t  fork(void);

创建新的进程,失败时返回-1

成功时父进程返回子进程的进程号子进程返回0;

通过fork的返回值区分父进程和子进程;

(3)子进程创建-fork-示例

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

int main(int argc, const char *argv[])
{
	pid_t pid;
	pid =  fork();
	if(pid>0)
	{
		printf("this is father process,pid=%d\n",getpid());
	}
	else if(pid == 0)
	{
		printf("this is child process,pid=%d\n",getpid());
	}
	else 
	{
		perror("pid:");
	}


	return 0;
}

运行结果:

分析要点总结:

⭕当进程调用 fork() 时,操作系统会为当前进程(父进程)创建一个新的进程(子进程)。这个新的进程是当前进程的副本,拥有与父进程相同的代码、数据和堆栈。

⭕在创建子进程时,父进程的内存空间会被复制到子进程中。这包括程序的代码段(text segment)、数据段(data segment)和堆栈段(stack segment)。由于采用了写时复制(Copy-on-Write)机制,父进程和子进程最初共享相同的物理内存页。只有在父进程或子进程试图修改这些页时,才会真正复制这些页,从而使它们在独立的内存空间中进行修改。

⭕创建子进程后,父进程和子进程都从 fork() 调用返回。在父进程中,fork() 返回子进程的进程号(PID),而在子进程中,fork() 返回0。

父进程和子进程继续执行 fork() 之后的代码。由于它们共享代码段,它们之后的代码是相同的,但是由于它们是不同的进程,它们各自拥有独立的数据和堆栈,父子进程有独立的地址空间,互不影响。

父进程和子进程之间的执行顺序是不确定的,这取决于操作系统的调度策略。通常情况下,父进程和子进程会并发执行。

(4)父子进程

子进程继承了父进程的内容

父子进程有独立的地址空间,互不影响

若父进程先结束  子进程成为孤儿进程,被init进程收养 ,子进程变成后台进程,孤儿进程对系统基本没有危害;

若子进程先结束 ,父进程如果没有及时回收子进程,子进程就会变成僵尸进程,僵尸进程就是进程结束了,但是PCB的相关进程的信息资源还没有回收,僵尸进程多了会使操作系统资源被浪费占用,可以通过在父进程中调用Wait和Waitpid对子进程进行回收;

 (5)一个父进程创建多个子进程

在父进程中创建多个子进程的过程通常使用循环和 fork() 系统调用。通过在循环中多次调用 fork(),父进程可以创建多个子进程。每次 fork() 调用都会生成一个新的子进程。

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

int main(int argc, const char *argv[])
{
	pid_t pid;
	int i;
	for(i = 0;i <5 ;i++)
 {
	pid =  fork();
	if(pid>0)
	{
		printf("this is father process,pid=%d\n",getpid());
	}
	else if(pid == 0)
	{
		printf("this is child process,pid=%d\n",getpid());
         break;
	}
	else 
	{
		perror("pid:");
	}
 }

}

分析:

在使用循环创建多个子线程的时候,要在子线程的执行程序里面加上break

因为子进程的程序代码和父进程相同,而当程序里面有循环时,子进程也要执行循环,所以会导致子进程创建多个子孙进程,所以在子进程的程序加上break,使得子进程只执行一次自己的程序就退出,不会再创建孙进程。

(6)进程结束-exit/_exit

#include <stdlib.h> 
#include  <unistd.h>
 void  exit(int  status);
 void  _exit(int  status);
 void  _Exit(int  status);

Ⅰ.exit()

void  exit(int  status);

 结束当前的进程并将status返回,satatus为返回给系统此时流的状态值;

exit结束进程时会刷新(流)缓冲区;main程序执行结束后默认调用exit()和return 0,所以正常程序执行结束也会刷新流缓冲区,如果是异常或者中途退出是不会刷新的

例如:

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

  int main(void) 
{
     printf(“this process will exit”);
     exit(0);
     printf(“never  be  displayed”);
}
 

执行后结果为:首先打印 this process will exit,然后调用 exit(0) 中途结束程序,后面的 printf 语句不会被执行。

 this process will be exit

Ⅱ._exit()

void _exit(int status)
  • _exit 函数会立即终止进程,不进行任何清理工作,包括不刷新标准 I/O 缓冲区,不执行注册的退出处理函数。
  • 它直接将状态码 status 返回给操作系统,表示进程终止的状态。由于没有执行清理工作,使用 _exit 可能会导致资源泄漏等问题。

Ⅲ._EXIT()

void _Exit(int status)
  • 类似于 _exit_Exit 函数也会立即终止进程,不执行清理工作。
  • 它将状态码 status 返回给操作系统,表示进程终止的状态。

4.进程的回收(熟练)

子进程结束时由父进程回收,但是如果子进程比父进程提前结束,就无法被回收,若没有及时回收会出现僵尸进程,所以一般要调用wait或者waitpid对子进程进行回收。使用wait,如果子进程还没结束,父进程就会阻塞等待子进程结束对它进行回收,如果子进程结束了,父进程紧随其后结束,父进程因为调用了wait也会对子进程的资源进行回收

没有调用wait或者waitpid回收,如果父进程先结束,子进程结束会变成孤儿进程,由init进程回收

wait是waitpid的特殊情况,wait内部还是调用的waitpid,只是加入了一些参数;

常用的是waitpid使用不阻塞回收的参数,然后用轮询,这样可以不浪费资源; 

(1)wait

#include <sys/wait.h>
pid_t wait(int *status); 

成功时返回回收的子进程的进程号;失败时返回EOF  ;

若子进程没有结束,父进程一直阻塞,父进程阻塞等待子进程结束回收;

 若有多个子进程,哪个先结束就先回收 ;

status 指定保存子进程返回值和结束方式的地址 ,status保存了子进程终止的一些信息,需要通过下面表格中的宏定义来获得特定的信息

status为NULL表示直接释放子进程PCB,不接收返回值;

解析得到子进程终止状态
 WIFEXITED(status)判断子进程是否正常结束
 WEXITSTATUS(status)获取子进程返回值
 WIFSIGNALED(status)判断子进程是否被信号结束
 WTERMSIG(status)获取结束子进程的信号类型

进程回收 – wait – 示例:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
	int status;
	pid_t pid;

	if ((pid = fork()) < 0) 
	{
		perror("fork");
		exit(-1);
	}
	else if (pid == 0)
	{
		sleep(1); 
		exit(2);
	}
	else if(pid>0)
	{
		wait(&status);  
		printf("%x\n",WEXITSTATUS(status));
	}  

}

运行结果为:2

(2)waitpid

#include <unistd.h>
pid_t waitpid(pid_t pid, int *status, int option);

成功时返回回收的子进程的pid或0;失败时返回EOF  ;

pid :可用于指定回收哪个子进程或任意子进程;  

status:指定用于保存子进程返回值和结束方式的地址  ;

option:指定回收方式,一般0(表现为阻塞等待回收) 或 WNOHANG(如果pid指定的子进程没有结束,则立即返回)(但是建议使用轮询地检查子进程状态,这种轮询的方式允许父进程继续执行其他任务,同时又能够实时地监视子进程的终止状态,这种方式允许父进程继续执行其他任务,同时又能够实时地监视子进程的终止状态。)

参数:

pid

pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去

pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。

pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。

pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

options

options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用

WNOHANG :若由pid指定的子进程未发生状态改变(没有结束),则waitpid()不阻塞,立即返回0,一般用于非阻塞地检查子进程是否已经终止。当在调用 waitpid 函数时使用了 WNOHANG 选项,函数会立即返回而不会阻塞等待子进程终止。例如我们希望继续执行其他任务,但同时又需要监视子进程的状态。通过使用 WNOHANG,可以在不阻塞的情况下定期检查子进程的终止状态,以便适时采取必要的操作。

WUNTRACED: 返回终止子进程信息和因信号停止的子进程信息,允许等待被停止的子进程的状态改变,以便在子进程被暂停时采取适当的行动。

所以 综上所述

wait(&stat) 等价于waitpid(-1,&stat,0) ;

评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

放牛的守护神_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值