Linux系统编程-进程及相关指令与函数
目录
一. 进程
1.1 什么是进程
进程:
程序:
1.2 进程的创建过程
1.3 进程的调度
并发:
CPU任务调度算法:
1.4 进程的状态
三态图
Linux进程状态:
二. 进程相关的指令
2.1 ps aux
2.2 ps -ef
2.3 pstree
2.4 kill
2.5 fg
三. 进程控制函数
3.1 fork
3.2 getpid与getppid
3.3 孤儿进程
3.4 僵尸进程
四. 进程的退出与消亡
4.1 进程的退出
4.2 如何避免僵尸进程:
一. 进程
单任务:程序运行起来,只能处理一件事情
多任务:程序运行起来,可以处理多件事情
1.1 什么是进程
进程:
进程:进程就是运行起来的程序,需要分配CPU资源与内存,多进程之间有资源的竞争
进程是一个动态的过程,具有生命周期
进程可以实现并发(多任务)
进程是操作系统分配资源的最小单位
程序:
程序:存放在硬盘的数据指令集合
静态的
程序运行起来可以创建多个进程
1.2 进程的创建过程
在创建进程时,操作系统会为其分配0~4G的虚拟内存地址空间
1.3 进程的调度
总结一句话:宏观并行,微观串行
宏观上多个任务同时进行,微观上多个任务按照一定的顺序执行
并发:
并发:在操作系统中,一个时间段内,有多个进程处于已启动运行到运行结束状态之间,但是同一时间点上,只有一个进程在运行
CPU任务调度算法:
1、时间片轮询
2、先来先服务,后来后服务
3、优先级高者先服务,优先级低者后服务
4、短作业优先
1.4 进程的状态
任何操作系统的进程切换图:
三态图
Linux操作系统的进程切换图:
Linux进程状态:
| 运行态(用户运行态,内核运行态) R | 正在被CPU任务调度所执行的进程 |
| 就绪态R | 正在执行,但是没有被CPU任务调度的进程 |
| 可唤醒等待态 S | 也称为睡眠态,阻塞等待资源的进程 |
| 不可唤醒等待态 D | 不想被CPU任务调度打断的进程任务,可设置不可唤醒等待状态 |
| 暂停态 T | 被暂停执行过程 |
| 僵尸态 Z | 进程执行结束,资源没有被回收 |
| 结束态 X | 进程执行结束,空间被回收 |
二. 进程相关的指令
2.1 ps aux
作用:查看操作系统中进程相关的信息
注意:1号进程为init进程,作用:是系统的起点,收养孤儿进程,清理僵尸进程等
| USER | 用户 |
| PID | 进程ID |
| %CPU %MEM | 进程对CPU,内存的占有率 |
| VSZ RSS | 内存相关信息 |
| TTY | 对终端的使用(?表示没有使用终端) |
| STAT | 进程状态 |
| START TIME | 进程开始的日期和时间 |
| COMMAND | 进程名称 |
2.2 ps -ef
作用:查看进程信息,可以查看进程ID与其父进程ID
2.3 pstree
作用:查看进程间的创建关系 加上选项-p可以显示进程ID
2.4 kill
作用:给指定PID的进程发送指定的信号:kill -信号编号/信号名称 PID
信号:可以使用命令kill -l来查看信号的种类及其信号编号
9号SIGKILL:结束指定的进程
19号SIGSTOP:暂停指定的进程
18号SIGCONT:继续指定的进程
2.5 fg
作用:将指定的进程由后台调至前台
使用方法:fg 进程编号
获取进程编号:指令jobs,作用:获取当前终端的后台进程及其编号
三. 进程控制函数
3.1 fork
关键在于duplicating
作用:创建一个子进程
返回值:>0:表示在父进程中返回子进程的PID
=0:表示当前处于子进程中
<0:出错并且设置errno
注意:1、子进程完全拷贝父进程的0~3G进程地址空间(用户空间:堆区,栈区,文本区,数据区
等)
2、但是对内核区的拷贝不完全,比如PCB中的PID号就不一样,但是又拷贝了文件描述符
3、父子进程之间数据不共享,要想通信就得使用进程间通信方式
可以使用下面一段代码来验证:
//int num = 100;全局变量不共享 int main() { //int num = 300;//局部变量也不共享 int *pnum = malloc(sizeof(int));//堆区资源也不共享 if(pnum == NULL){ perror("malloc()"); return -1; } *pnum = 600; pid_t pid = fork(); if(pid < 0){ perror("fork()"); return -1; } else if(pid > 0){ while(1){ printf("---parent: my pid is %d,my parent pid is %d, num = %d\n", getpid(), getppid(), *pnum); sleep(1); } } else{ //num = 200; *pnum = 900; while(1){ printf("-----child: my pid is %d,my parent pid is %d, num = %d\n", getpid(), getppid(), *pnum); sleep(1); } } return 0; }这里有一个小栗子:
问下面这个程序总共可以fork出几个进程?
int main() { fork() && fork() || fork(); getchar(); return 0; }结果:使用pstree -sp 进程ID查看:可以看出总共有五个进程
这里需要注意&&与||操作符的短路效应
表达式1 && 表达式2 || 表达式3 这条语句如果表达式1是假,那么后面是不是就都不执行了?不是这样的:表达式1为假,只能短路掉表达式2,|| 左边整体为假,故表达式3仍要执行。
3.2 getpid与getppid
作用:获取当前进程的pid与当前进程的父进程的pid
3.3 孤儿进程
父进程先于子进程结束,则此子进程成为孤儿进程,则这个子进程的父进程变成了一个系统进程,在早期的操作系统中,孤儿进程直接被1号进程init进程收养,但是init进程的任务繁重,后来又重新封装了一个进程组来回收孤儿进程
孤儿进程的用处:后台的守护进程
可以使用下面的代码来观察孤儿进程:
int main() { pid_t pid = fork(); if(pid > 0){ printf("parent:pid is %d, child is %d\n",getpid(),pid); sleep(10);//父进程10s后死亡,子进程成为孤儿进程 } else if(pid == 0){ while(1){//子进程循环打印 printf("child:pid is %d, parent is %d\n",getpid(),getppid()); sleep(1); } } return 0; }结果:在父进程结束后,子进程的父进程成为了1495这个进程
3.4 僵尸进程
子进程结束后,父进程没有回收子进程残留在内核空间中的资源(PCB),导致该子进程变成了僵尸进程。
注:子进程结束后,其用户空间的所有资源默认由操作系统回收,但是内核空间的PCB还残留在内核中,原因:PCB记录了子进程退出的原因,若是正常退出则保存其退出状态,若是异常退出,则保存使其异常退出的原因,比如信号等等,父进程可以调用wait或者waitpid来获取这些信息,然后彻底清除掉这个进程残留的资源。
使用kill -9命令无法使僵尸进程结束,只能将其父进程结束,然后该子进程成为孤儿进程,然后被收养和回收
使用这段代码可以观察到僵尸进程这个状态:
int main() { pid_t pid = fork(); if(pid > 0){ while(1){ printf("parent:pid is %d, child is %d\n",getpid(),pid); sleep(1);//父进程一直在打印,没有回收子进程,则子进程变成僵尸进程 } } else if(pid == 0){ printf("child:pid is %d, parent is %d\n",getpid(),getppid()); sleep(10);//子进程10s后结束 printf("child die\n"); } return 0; }结果:
10s前:
10s后:可以看到子进程变成了僵尸进程Z+
四. 进程的退出与消亡
4.1 进程的退出
1. 在主函数中return;
2.exit(),库函数,直接结束一个进程,结束前会刷新缓冲区
3._exit(),_Exit(),系统调用,直接结束一个进程,结束前不会刷新缓冲
4.2 如何避免僵尸进程:
1. 子进程结束,由其父进程回收资源空间
2. 让子进程成为一个孤儿进程
