目录
终端查询进程参数
进程状态
进程状态转换
子进程被Linux内核调入CPU执行的过程
子进程进入睡眠状态
子进程结束
进程控制
pid_t fork(void):创建子进程
exec()函数族:运行一个可执行文件。
void exit(int status):结束进程
wait():同步父进程与子进程
sleep(秒数):进程睡眠
进程组PGID:对相同类型的进程进行管理,由首进程PID决定
会话SID:管理进程组,由会话首进程决定
前台进程组
后台进程组
终端
守护进程
僵死进程
托孤进程
pstree:查看进程间的关系
终端查询进程参数
ps参数 | 描述 |
-e | 列出所有进程 |
-f | 显示进程的全部信息 |
-h | 不显示进程标题列出所有进程 |
-l | 长格式显示进程 |
-w | 宽格式显示进程 |
-a | 显示终端上的所有进程,包括其他用户的进程 |
-r | 只显示正在运行的进程 |
-x | 显示没有控制终端的进程 |
-u | 显示进程的归属用户及内存使用情况 |
-j | 显示进程归属的进程组id、会话id、父进程id |
ps auf(关注进程本身):用户名 PID 占用CPU% 占用内存% 虚拟内存大小 物理内存大小 进程关联的终端 进程当前状态 进程启动时间 进程运行时间 进程执行的程序。
ps axjf(关注进程间的关系):PPID PID PGID SID 进程关联的终端 是否是守护进程(-1是) 进程当前状态 用户id 进程运行时间 进程执行的程序。
进程状态
进程状态 | 描述 |
创建状态 | 进程正在被Linux内核创建 |
就绪 | 进程还没有开始执行,但相关数据已被创建,只要内核调度它就立即可以执行 |
内核状态 | 进程在内核状态下被运行,被调度上CPU执行 |
用户状态 | 进程在用户状态下被运行,等待被调度上CPU执行 |
睡眠 | 进程正在睡眠,等待系统资源或相关信号唤醒 |
唤醒 | 正在睡眠的进程收到Linux内核唤醒的信号 |
被抢先 | 具有更高优先级的进程强制获得进程的CPU时钟周期 |
僵死状态 | 进程通过系统调用结束,进程不再存在,但在进程表项中仍有记录,该记录可由父进程收集 |
进程状态转换
子进程被Linux内核调入CPU执行的过程
最初,父进程通过fork系统调用创建子进程,子进程被创建后,处于创建态。Linux内核为子进程配置数据结构。如果内存空间足够,子进程在内核中就绪,否则在Swap分区就绪。这时子进程处于就绪态,等待Linux内核调度。
Linux内核会为子进程分配CPU时钟周期,在合适的时间将子进程调度上CPU运行,这时子进程处于内核态,子进程开始运行。被分配的CPU时钟周期结束时,Linux内核再次调度子进程,将子进程调出CPU,子进程进入用户态。
待子进程被分配到下一个CPU时钟周期到来时,Linux内核又将子进程调度到CPU运行,使子进程进入内核态。如果有其他进程获得更高的优先级,子进程的时钟周期可能会被抢占,这时又回到用户态。
子进程进入睡眠状态
子进程在运行时,如果请求的资源得不到满足将进入睡眠态,睡眠态的子进程被从内存调换到Swap分区。被请求的资源可能是一个文件,也可能是打印机等硬件设备。如果该资源被释放,子进程将被调入内存,继续以系统态执行。
子进程结束
子进程可以通过exit系统调用结束,这时子进程将进入僵死态,声明周期结束。
子进程在内核中的数据结构又被称为上下文。
上下文:
用户级上下文是子进程用户空间的内容;
寄存器上下文是子进程运行时装入CPU寄存器的内容;
系统级上下文是子进程在Linux内核中的数据结构。
子进程切换时,CPU收到一个软中断,这时上下文将被保存起来,称之为保存现场。子进程再次运行时,上下文将还原到相关设置,称之为还原现场。整个过程称为上下文切换,保存上下文的数据空间称为u区,是Linux内核为进程分配的存储空间。
内核在以下情况会进行上下文切换操作:
子进程进入睡眠态时;
子进程时钟周期结束,被转为用户态时;
子进程再次被调度上CPU运行,转为系统态时;
子进程僵死时。
进程控制
调度时机:指进程何时被调度上CPU执行。例如,转变为睡眠态的进程将获得较高的优先级,一旦所需要的资源被释放,该进程可以立即被调度上CPU执行。被抢占的进程也将获得一个较高的优先级,抢占其CPU时钟周期的进程一旦转为用户态,被抢占的进程立即转为内核态。
调度算法:所关心的内容就是如何为进程分配优先级。
pid_t fork(void):创建子进程
在执行fork函数之前,操作系统只有一个进程,fork函数之前的代码只会被执行一次。
在执行fork函数之后,操作系统有两个几乎一样的进程,fork函数之后的代码会被执行两次。
执行完fork函数后,fork函数会返回两次。
//所需头文件
#include <sys/types.h>
#include <unistd.h>
//函数原型
pid_t fork(void); //创建子进程时,复制父进程的上下文
pid_t vfork(void); //创建子进程时,不复制父进程的上下文
//返回值
成功:0或其它正整数
失败:-1
首先,Linux内核在进程表中为子进程分配一个表项,然后分配PID。子进程表项的内容来自于父进程,fork系统调用会将父进程的进程表项复制为副本,并分配给子进程。然后,Linux内核使父进程的文件表和索引表的节点自增1,创建用户级上下文。最后,将父进程上下文复制到子进程的上下文空间中。fork系统调用结束后,子进程的PID被返回给父进程,而子进程获得的值为0。
exec()函数族:运行一个可执行文件。
//常见后缀
l:以列表形式传参
v:以矢量数组形式传参
p:使用环境变量Path来寻找指定执行文件
e:用户提供自定义的环境变量
//所需头文件
#include <unistd.h>
//函数原型
int execl(const char *path,const char *arg,...);
int execlp(const char *file,const char *arg,...);
int execv(const char *path,char *const argv[]);
int execve(const char *path,char *const argv[],char *const envp[]);
//返回值
成功:不返回
失败:-1
exec系统调用会结束原有进程,然后更新上下文的内容,并从头开始执行一个新的进程。两个进程之间并无父子关系。
void exit(int status):结束进程
//所需头文件
#include <unistd.h>
#include <stdlib.h>
//函数原型
void exit(int status);
void _exit(int status);
//返回值
不返回
进程执行exit系统调用后,Linux内核将删除进程的上下文,但保留进程表项,进程处于僵死态。待合适时,再删除进程表项的内容,释放进程PID。
wait():同步父进程与子进程
//所需头文件
#include <sys/wait.h>
//函数原型
pid_t wait(int *status);
//返回值
成功:退出的子进程PID
失败:-1
//处理子进程退出状态值的宏
WIFEXITED(status):如果子进程正常退出,则该宏为真
WEXITSTATUSA(status):如果子进程正常退出,则该宏获取子进程的退出值
系统调用wait()函数后,父进程的执行被阻断,直到子进程进入僵死态。这时,子进程的退出参数可通过wait()函数返回给父进程。wait系统调用常被用来判断子进程是否已结束。
sleep(秒数):进程睡眠
进程组PGID:对相同类型的进程进行管理,由首进程PID决定
在Shell里面直接执行一个应用程序,对于大部分进程来说,自己就是进程组的首进程,进程组只有一个进程。如果进程调用了fork函数,那么父子进程同属一个进程组,父进程为首进程。
在Shell中通过管道执行连接起来的应用程序,两个程序同属一个进程组,第一个程序为进程组的首进程。
会话SID:管理进程组,由会话首进程决定
调用setsid函数,新建一个会话,应用程序作为会话的第一个进程,称为会话首进程。
用户在终端正确登录后,启动Shell时linux系统会创建一个新的会话,Shell进程作为会话首进程。
前台进程组
Shell进程启动时,默认是前台进程组的首进程。前台进程组的首进程会占用会话所关联的终端来运行,Shell启动其他程序时,其他程序成为首进程。
在Shell进程里启动程序时,把后台进程组切换为前台进程组操作:fg jobid
后台进程组
后台进程中的程序徐不会占用终端。
jobs:查看有哪些后台进程组。
在Shell进程里启动程序时,指定程序运行在后台进程组操作:&、Ctrl+Z。
终端
当终端被关闭之后,会话中的除守护进程外的所有进程都会被关闭。
物理终端:串口终端、LCD终端
伪终端:SSH远程连接产生的终端、桌面系统启动的终端
虚拟终端:Linux内核自带,Ctrl+Alt+F0~F6可以打开7个虚拟终端
守护进程
创建子进程,父进程直接退出(父进程放弃终端使用权)。fork()函数。
创建新会话,不关联任何终端。setsid()函数。
改变守护进程的当前工作目录,改为“/”。chdir()函数。
重设文件权限掩码,新建文件的权限受文件权限掩码影响。umask()函数
关闭默认的文件描述符。close()函数。
实现守护进程的功能。
普通程序伪装成守护进程:nohup
僵死进程
进程正常退出:
子进程调用exit()函数退出
父进程调用wait()函数为子进程处理其他事情
子进程调用exit()函数退出,父进没有调用wait()函数为子进程处理其他事情,子进程就变成僵死进程。
托孤进程
父进程比子进程先退出(_exit()可立即退出进程),子进程变为孤儿进程,Linux系统会把子进程托孤给1号进程(init进程)。