目录
掌握:进程概念、进程包含内容、进制状态、查看进程信息、前后台进程切换、改变进行优先级、创建子进程、父子进程、结束进程
1 进程概念
程序
- 存放在磁盘上的指令和数据的有序集合(文件)
- 静态的
进程
- 执行一个程序所分配的资源的总称
- 进程是程序的一次执行过程
- 动态的,包括创建、调度、执行和消亡
2 进程内容
- BSS段:BSS段通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。
- 数据段:数据段通常是指用来存放程序中已初始化的全局变量的一块内存区域。
- 代码段:代码段通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
- 堆(heap):堆是用于存放进程运行中被动态分配的内存段,当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)
- 栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
-
进程控制块(pcb) :进程标识PID 、进程用户 、进程状态、优先级 、文件描述符表
进程放在RAM种,程序放在ROM中(单片机就在flash中)
3 进程类型
- 交互进程:在shell下启动。以在前台运行,也可以在后台运行 (前台 ./test)
- 批处理进程:和在终端无关,被提交到一个作业队列中以便顺序执行
- 守护进程:和终端无关,一直在后台运行
4 进程状态
运行态:进程正在运行,或者准备运行
等待态:进程在等待一个事件的发生或某种系统资源 (分为可中断和不可中断)
停止态:进程被中止,收到信号后可继续运行
死亡态:已终止的进程,但pcb没有被释放(僵尸态)
5 查看进程信息
5.1 相关命令ps top /proc
ps 查看系统进程快照
top 查看进程动态信息
/proc 查看进程详细信息
ps 命令详细参数:
-e:显示所有进程
-l:长格式显示更加详细的信息
-f 全部列出,通常和其他选项联用
表头 | 含义 |
F | 进程标志,说明进程的权限,常见的标志有两个:
|
S | 进程状态。进程状态。常见的状态有以下几种:
|
UID | 运行此进程的用户的 ID; |
PID | 进程的 ID; |
PPID | 父进程的 ID; |
C | 该进程的 CPU 使用率,单位是百分比; |
PRI | 进程的优先级,数值越小,该进程的优先级越高,越早被 CPU 执行; |
NI | 进程的优先级,数值越小,该进程越早被执行; |
ADDR | 该进程在内存的哪个位置; |
SZ | 该进程占用多大内存; |
WCHAN | 该进程是否运行。"-"代表正在运行; |
TTY | 该进程由哪个终端产生; |
TIME | 该进程占用 CPU 的运算时间,注意不是系统时间; |
CMD | 产生此进程的命令名; |
top 查看进程动态信息
shift +> 后翻页
ps -elf|grep watchdog
shift +< 前翻页
top -p PID 查看某个进程
示例:
ps -elf|grep watchdog
//列出所有进程,找到字符串有watchdog的进程
示例:
ls /proc/
/proc
是一个虚拟文件系统,提供了关于运行中进程和系统内核状态的信息。
数字代表某个进程目录
ls /proc/数字
5.2 相关命令 nice renice
nice 按用户指定的优先级运行进程
nice [-n NI值] 命令
NI 范围是 -20~19。数值越大优先级越低
普通用户调整 NI 值的范围是 0~19,而且只能调整自己的进程。
普通用户只能调高 NI 值,而不能降低。如原本 NI 值为 0,则只能调整为大于 0。
只有 root 用户才能设定进程 NI 值为负值,而且可以调整任何用户的进程。
renice 改变正在运行进程的优先级
renice [优先级] PID
示例:
nice -n 10 command //将启动一个新的 shell 进程,并将它的优先级设置为 10。
renice -n 5 -p 1234 //将把进程 ID 为 1234 的进程的优先级设置为 5。
5.3 相关命令job bg fg
示例:
创建一个循环程序,编译执行
#include <stdio.h>
#include <unistd.h>
int main(int argc, char * argv[])
{
while(1)
{
sleep(1);
}
return 0;
}
后台运行两种方式:
- 此时控制台输出,在 Linux 控制端,按下
CTRL+Z
的组合键可以将当前正在运行的前台进程挂起,并放入后台,同时返回一个进程挂起的提示信息。这个被挂起的进程会停止执行,但它的状态不会被清除,直到被唤醒或终止。 - ./a.out & 把test程序后台运行
此时查看进程状态,可以发现状态为T,代表停止。
linux@linux:~/Desktop$ ps -elf|grep a.out
0 T linux 2869 2784 0 80 0 - 506 signal 09:46 pts/1 00:00:00 ./a.out
0 S linux 3226 2784 0 80 0 - 1171 pipe_w 10:18 pts/1 00:00:00 grep --color=auto a.out
//T代表已经a.out程序已经停止
jobs 查看后台进程
bg 将挂起的进程在后台运行
fg 把后台运行的进程放到前台运行,接序号
示例:
linux@linux:~/Desktop$ jobs
[1]+ Stopped ./a.out
linux@linux:~/Desktop$ fg 1
./a.out
linux@linux:~/Desktop$ bg 1
[1]+ ./a.out &
linux@linux:~/Desktop$ ps -elf|grep a.out
0 S linux 2869 2784 0 80 0 - 506 hrtime 09:46 pts/1 00:00:00 ./a.out
0 S linux 3329 2784 0 80 0 - 1171 pipe_w 10:32 pts/1 00:00:00 grep --color=auto a.out
linux@linux:~/Desktop$ jobs
[1]+ Running ./a.out &
6 子进程概念
子进程为由另外一个进程(对应称之为父进程)所创建的进程
除了系统启动时的第一个进程(通常是 init 或 systemd或0号进程)外,所有其他进程都是由父进程创建的。这种父子进程的关系形成了一个进程树或进程层级结构。
7 子进程创建 – fork
#include <unistd.h>
pid_t fork(void);
- 创建新的进程,失败时返回-1
- 成功时父进程返回子进程的进程号,子进程返回0
- 通过fork的返回值区分父进程和子进程
示例:
#include <stdio.h>
#include <unistd.h>
int main(int argc,char **argv)
{
pid_t pid;
printf("before fork\n");
pid = fork();
printf("after fork\n");
printf("pid=%d\n",(int)pid);
}
linux@linux:~/Desktop$ ./a.out
before fork
after fork
pid=3607 //父打印子进程号
linux@linux:~/Desktop$
after fork
pid=0 //子进程打印0
注意:
1 子进程只执行fork之后的代码
2.父子进程执行顺序是操作系统决定的。
如果需要控制谁先执行,需要加一些延时
#include <stdio.h>
#include <unistd.h>
int main(int argc,char **argv){
pid_t pid;
printf("before fork\n");
pid = fork();
if(pid>0){
printf("This is father process\n");
printf("pid=%d\n",(int)pid);
printf("father after fork\n");
}else if(pid==0){
printf("This is child process\n");
printf("pid=%d\n",(int)pid);
printf("child after fork\n");
}else if(pid<0){
perror("fork");
return 0;
}
// printf("pid=%d\n",(int)pid);
// printf("after fork\n");
}
8 父子进程
子进程继承了父进程的内容(从fork以后执行)
父子进程有独立的地址空间,互不影响
若父进程先结束 :
-子进程成为孤儿进程,被init进程收养
-子进程变成后台进程
若子进程先结束:
- 父进程如果没有及时回收,子进程变成僵尸进程
#include <stdio.h>
#include <unistd.h>
int main(int argc,char **argv){
pid_t pid;
printf("before fork\n");
pid = fork();
if(pid>0){
printf("This is father process\n");
printf("pid=%d\n",(int)pid);
printf("father after fork\n");
while(1){
sleep(1);
printf("father sleep\n");
}
}else if(pid==0){
printf("This is child process\n");
printf("pid=%d\n",(int)pid);
printf("child after fork\n");
while(1){
sleep(1);
printf("child sleep\n");
}
}else if(pid<0){
perror("fork");
return 0;
}
// printf("pid=%d\n",(int)pid);
// printf("after fork\n");
}
kill -9 分别演示父进程和子进程先结束的状态。
1代表子进程被init收养了
0代表僵尸进程
9 思考
子进程从何处开始运行?
答:子进程从fork()函数的调用处开始运行。
父子进程谁先执行?
答:父子进程的执行顺序是不确定的,取决于操作系统的调度算法。在一般情况下,父进程和子进程是并发执行的,可能会交替执行,也可能先执行父进程再执行子进程。
父进程能否多次调用fork? 子进程呢?
答:父进程可以多次调用fork()函数创建多个子进程。每次调用fork()函数都会返回两次,在父进程中返回子进程的进程ID(PID),在子进程中返回0。这样可以通过判断返回值来区分父进程和子进程,并根据需要执行不同的代码逻辑。
子进程也可以调用fork()函数创建自己的子进程,从而形成进程的层次结构,但需要注意,子进程创建的子进程与原始父进程无关。
示例:是否创建了5个进程?
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t pid;
int i;
for(i=0;i<5;i++){
pid = fork();
if(pid<0){
perror("fork");
return 0;
}else if(pid==0){
printf("child process\n");
sleep(5);
}else{
printf("Father process\n");
sleep(5);
}
}
sleep(100);
}
实际子进程在fork,父进程也在fork
示例:只让父进程fork
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t pid;
int i;
for(i=0;i<5;i++){
pid = fork();
if(pid<0){
perror("fork");
return 0;
}else if(pid==0){
printf("child process\n");
sleep(5);
break;
}else{
printf("Father process\n");
sleep(5);
}
}
sleep(100);
}
示例:只让子进程fork
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t pid;
int i;
for(i=0;i<5;i++){
pid = fork();
if(pid<0){
perror("fork");
return 0;
}else if(pid==0){
printf("child process\n");
sleep(5);
}else{
printf("Father process\n");
sleep(5);
break;
}
}
sleep(100);
}
10 进程结束 – exit/_exit
#include <stdlib.h>
#include <unistd.h>
void exit(int status);
void _exit(int status);
void _Exit(int status); //与小写一样
结束当前的进程并将status返回
exit结束进程时会刷新(流)缓冲区(区别)
示例1:
主函数(main函数)在结束时默认会调用exit()函数。exit()函数用于正常终止程序,并返回一个退出状态给操作系统。不当主函数执行完毕或遇到return语句时,会自动隐式地调用exit()函数。这会导致程序退出,并将返回值作为退出状态码传递给操作系统。
#include <stdio.h>
#include <stdlib.h> //exit 和_exit引用不一样
int main(void) {
printf(“this process will exit”);
exit(0);
printf(“never be displayed”); //不打印
}
$ ./a.out
this process will be exit //加不加exit始终会打印
示例2:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf(“using exit…\n”);
printf(“This is the end”);
exit(0);
}
$ ./a.out
using exit…
This is the end
示例3:不刷新缓存区的_exit()
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc,char**argv){
printf("hello world"); //不打印
_exit(0);
printf("after exit"); //不打印
return 0;
}
//什么都不打印
11 进程的回收
子进程结束时由父进程回收
孤儿进程由init进程回收
若没有及时回收会出现僵尸进程
11.1 进程回收 – wait
#include <sys/wait.h>
pid_t wait(int *status);
- 成功时返回回收的子进程的进程号;失败时返回EOF
- 若子进程没有结束,父进程一直阻塞
- 若有多个子进程,哪个先结束就先回收
- status 指定保存子进程返回值和结束方式的地址
- status为NULL表示直接释放子进程PCB,不接收返回值
示例:
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char** argv){
pid_t pid;
pid_t rpid;
pid = fork();
int status;
if(pid<0){
perror("fork");
return 0;
}
else if(pid == 0){
sleep(10);
printf("child will exit\n");
exit(2);
}else if(pid >0){
rpid = wait(&status);
printf("Get child status=%x\n",WEXITSTATUS(status));
}
}
返回值是多个内容组成的标志位,需要调用宏定义打印返回值
查看是否有僵尸进程
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char** argv){
pid_t pid;
pid_t rpid;
pid = fork();
int status;
if(pid<0){
perror("fork");
return 0;
}
else if(pid == 0){
sleep(10);
printf("child will exit\n");
exit(2);
}else if(pid >0){
rpid = wait(&status);
sleep(20);
printf("Get child status=%x\n",WEXITSTATUS(status));
}
while(1){
sleep(1);
}
}
如果去除wait,产生了僵尸进程
进程返回值和结束方式
子进程通过exit / _exit / return 返回某个值(0-255)
父进程调用wait(&status) 回收
- WIFEXITED(status) 判断子进程是否正常结束
- WEXITSTATUS(status) 获取子进程返回值
- WIFSIGNALED(status) 判断子进程是否被信号结束
- WTERMSIG(status) 获取结束子进程的信号类型
11.2 进程回收 – waitpid
#include <sys/wait.h>
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
WUNTRACED: 返回终止子进程信息和因信号停止的子进程信息
wait(wait_stat) 等价于waitpid(-1,wait_stat,0)
示例:
waitpid(pid, &status, 0);
waitpid(pid, &status, WNOHANG);
waitpid(-1, &status, 0);
waitpid(-1, &status, WNOHANG);
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char** argv){
pid_t pid;
pid_t rpid;
pid = fork();
int status;
if(pid<0){
perror("fork");
return 0;
}
else if(pid == 0){
sleep(10);
printf("child will exit\n");
exit(2);
}else if(pid >0){
rpid = wait(&status);
sleep(20);
//waitpid(-1,&status,WNOHANG); //只有一个进程,-1和参数pid效果一样
waitpid(pid,&status,0);
printf("Get child status=%x\n",WEXITSTATUS(status));
}
while(1){
sleep(1);
}
}
示例2
waitpid早就执行了,但是exit还会执行,注意这种情况可以考虑waitpid前也加延时函数,晚于exit用于进程回收