目录
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
程序:存放在磁盘上的指令和数据的有序集合(文件)
特点:静态的;
进程:执行一个程序所分配的资源的总称;
特点:动态的;
进程是程序的一次执行过程 ,动态包括创建、调度、执行和消亡;
进程包含的内容:
BSS段(Block Started by Symbol的简称):存放程序中未初始化的全局变量的一块内存区域;
数据段:存放已初始化的全局变量的一块内存区域;
代码段:存放程序执行代码的一块内存区域,这部分区域的大小在程序运行前就已经确定;
堆(heap):用于存放进程运行中被动态分配的内存段,比如使用malloc等函数分配内存;
栈(stack) :栈又称堆栈,是用户存放程序临时创建的局部变量(但不包括static声明的变量,static意味着在数据段中存放变量),在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区用来存放局部变量、函数参数、函数的返回值;
进程控制块(Process Control Block,简称PCB):用于存储和跟踪记录进程的关键信息,包括PID、进程优先级、文件描述符表等;
交互进程:交互进程是在shell(命令行界面)下启动的进程。当在shell中输入一个命令,例如启动一个应用程序或执行一个脚本时,该命令会创建一个交互进程。交互进程可以在前台运行,这意味着它会占用shell并接受用户的输入,直到任务完成或手动中止。另外,还可以将交互进程放到后台运行,这样它就会继续在后台执行,不占用shell,并且可以继续在shell中输入其他命令。在Linux和Unix系统中,通过在命令末尾加上"&"符号可以将进程放到后台运行。
批处理进程:批处理进程通常是与终端无关的,它们被提交到一个作业队列中以便顺序执行。这种进程是针对一系列事先定义好的任务或指令的处理,而不需要实时用户交互。它们通常用于自动化、批量处理、定期作业等场景。在一些操作系统中,批处理脚本可以编写包含多个命令的文件,然后将该文件提交给批处理系统进行执行。
守护进程:守护进程是在后台一直运行的进程,独立于任何终端或用户会话。它们通常在系统启动时启动,并持续运行以提供服务或执行特定的任务。守护进程通常不会与用户直接交互,而是在系统级别上提供服务,如网络服务、定时任务、日志记录等。守护进程常常以超级用户(root)权限运行,以便访问系统级别的资源和功能。
进程状态包括:运行态、等待态、停止态、死亡态
⭕ 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 |
进程标志,说明进程的权限,常见的标志有两个:
|
S |
进程状态。进程状态。常见的状态有以下几种:
|
UID |
运行此进程的用户的 ID; |
PID |
进程的 ID; |
PPID |
父进程的 ID; |
C |
该进程的 CPU 使用率,单位是百分比; |
PRI |
进程的优先级,数值越小,该进程的优先级越高,越早被 CPU 执行; |
NI |
进程的优先级,数值越小,该进程越早被执行; |
ADDR |
该进程在内存的哪个位置; |
SZ |
该进程占用多大内存; |
WCHAN |
该进程是否运行。"-"代表正在运行; |
TTY |
该进程由哪个终端产生; |
TIME |
该进程占用 CPU 的运算时间,注意不是系统时间; |
CMD |
产生此进程的命令名; |
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程序后台运行
Ctrl+Z
:在终端运行时,当按下Ctrl+Z
组合键时,它会将当前正在前台运行的进程暂停(挂起)。被挂起的进程可以通过bg
命令继续在后台运行,或者通过fg
命令将其放回前台运行。
bg
:bg
命令用于将一个挂起的(暂停的)进程在后台继续运行。当使用Ctrl+Z
将一个进程挂起后,可以使用bg
命令使该进程继续在后台运行。
jobs
:jobs
命令用于查看当前shell会话中的后台作业(后台进程)。它将显示在当前shell会话中运行的所有作业,并给出每个作业的作业号及其状态。
fg
:fg
命令用于将后台运行的作业调回前台运行。如果有一个在后台运行的进程,可以使用fg
命令将其放到前台执行,这样它将成为当前活动进程。
./test &
:这是在后台运行名为test
的程序的命令。通过在命令末尾加上&
符号,该命令会使test
程序在后台运行,这时可以继续在终端执行其他命令。
kill -9 PID 杀掉进程
子进程为由另外一个进程(对应称之为父进程)所创建的进程;
#include
pid_t fork(void); 创建新的进程,失败时返回-1
成功时父进程返回子进程的进程号,子进程返回0;
通过fork的返回值区分父进程和子进程;
#include "stdio.h"
#include
#include
#include
#include
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()
之后的代码。由于它们共享代码段,它们之后的代码是相同的,但是由于它们是不同的进程,它们各自拥有独立的数据和堆栈,父子进程有独立的地址空间,互不影响。
⭕父进程和子进程之间的执行顺序是不确定的,这取决于操作系统的调度策略。通常情况下,父进程和子进程会并发执行。
子进程继承了父进程的内容
父子进程有独立的地址空间,互不影响
若父进程先结束 子进程成为孤儿进程,被init进程收养 ,子进程变成后台进程;
若子进程先结束 ,父进程如果没有及时回收,子进程变成僵尸进程
在父进程中创建多个子进程的过程通常使用循环和 fork()
系统调用。通过在循环中多次调用 fork()
,父进程可以创建多个子进程。每次 fork()
调用都会生成一个新的子进程。
#include "stdio.h"
#include
#include
#include
#include
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,使得子进程只执行一次自己的程序就退出,不会再创建孙进程。
#include
#include
void exit(int status);
void _exit(int status);
void _Exit(int status);
void exit(int status);
结束当前的进程并将status返回,satatus为返回给系统此时流的状态值;
exit结束进程时会刷新(流)缓冲区;main程序执行结束后默认调用exit()和return 0,所以正常程序执行结束也会刷新流缓冲区,如果是异常或者中途退出是不会刷新的
例如:
#include
#include
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
void _exit(int status)
_exit
函数会立即终止进程,不进行任何清理工作,包括不刷新标准 I/O 缓冲区,不执行注册的退出处理函数。status
返回给操作系统,表示进程终止的状态。由于没有执行清理工作,使用 _exit
可能会导致资源泄漏等问题。void _Exit(int status)
_exit
,_Exit
函数也会立即终止进程,不执行清理工作。status
返回给操作系统,表示进程终止的状态。子进程结束时由父进程回收
孤儿进程由init进程回收
若没有及时回收会出现僵尸进程;
#include
pid_t wait(int *status); 成功时返回回收的子进程的进程号;失败时返回EOF ;
若子进程没有结束,父进程一直阻塞,父进程阻塞等待子进程结束回收;
若有多个子进程,哪个先结束就先回收 ;
status 指定保存子进程返回值和结束方式的地址 ,status保存了子进程终止的一些信息,需要通过下面表格中的宏定义来获得特定的信息;
status为NULL表示直接释放子进程PCB,不接收返回值;
WIFEXITED(status) | 判断子进程是否正常结束 |
WEXITSTATUS(status) | 获取子进程返回值 |
WIFSIGNALED(status) | 判断子进程是否被信号结束 |
WTERMSIG(status) | 获取结束子进程的信号类型 |
进程回收 – wait – 示例:
#include
#include
#include
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
#include
pid_t waitpid(pid_t pid, int *status, int option); 成功时返回回收的子进程的pid或0;失败时返回EOF ;
pid :可用于指定回收哪个子进程或任意子进程;
status:指定用于保存子进程返回值和结束方式的地址 ;
option:指定回收方式,一般0(表现为阻塞等待回收) 或 WNOHANG(非阻塞地检查子进程状态,这种轮询的方式允许父进程继续执行其他任务,同时又能够实时地监视子进程的终止状态,这种方式允许父进程继续执行其他任务,同时又能够实时地监视子进程的终止状态。);
参数:
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) ;