目录
文件=内容+属性
可执行程序加载到内存中时,仅是把内容拷贝,在内存会创建一个PCB用以保存了包含进程所有属性的结构体。于是对进程的管理,变成了对进程PCB结构体链表的增删查改。
linux对程序的管理理念是先描述,后管理。
PCB:进程控制块。就是描述进程的结构体。
什么叫做进程:进程=对应的代码和数据+进程对应的PCB结构体。
1. PCB
在不同的操作系统中,PCB名字不一样。在linux操作系统中为 task_struct
2. task_ struct内容分类
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
3. 查看进程
ps axj显示所有进程
ps axj | head -1 拿到第一行
ps axj | grep 'myproc' 显示关键词进程 //myproc是一个一直在运行的进程。
pid :进程id
ls /proc 查看所有进程的目录(动态的)
get pid 获得进程id get ppid 获得父进程id
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
while(1)
{
pid_t id =getpid();//获取的是自己的进程PID
printf("hello world,pid: %d\n",id);
sleep(1);
}
return 0;
}
kill -9 pid 结束对应pid的进程
父进程永远都是bash kill -9 ppid Xshell将不能正常运行。
4. fork
创建一个子进程 #include <unistd.h>
pid_t fork();
返回值:失败时:返回-1
成功时:a.给父进程返回子进程的pid
b.给子进程返回0
从fork往后程序变为两个
pid_t ret=fork();
printf("ret: %d\n",ret);
fork之后父进程子进程共享代码,解决方法:根据返回值不同进行不同程序。
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t id =fork();
if(id<0)
{
//创建子进程失败
perror("fork");
return 1;
}
else if(id==0)
{
//子进程执行
}
else
{
//父进程执行
}
return 0;
}
4.1 创建子进程的逻辑
在内存中struct task_struct结构体中的内部属性要以父进程为模板。
return时核心代码已经执行完了吗?Y
为什么有两个返回值?
1.因为fork内部,父子会各自执行自己的return语句
2.返回两次,并不意味着保存两次。
父子进程谁先运行?不一定,由调度器决定
task_struct结构体按照算法排成队列(数据结构),CPU按照队列顺序先后执行。所以操作系统和cpu运行某一个进程,本质上就是从task_struct形成的队列中挑一个来执行它的代码。
进程调度:从task_struct形成的队列中挑一个
5. 进程状态
新建:字面意思
运行:task_struct结构体在运行队列中排队,就叫做运行态
阻塞:系统中存在各种资源(cpu,硬盘,网卡等),不在cpu队列中排队而在其他资源中排队称阻塞态。
挂起:当内存不足的时候,操作系统通过适当的置换进程的代码和数据到磁盘,进程的状态就叫做挂起。
进程分为 前台进程 和 后台进程(&)
只有就绪态和运行态可以相互进行转换。其他都是单向转换。
就绪态的进程通过调度算法从而获得CPU时间,转为运行状态。而运行状态的进程,在分配给他的CPU时间片结束之后,就会转为就绪态,等待下次CPU调度。
当外部事件发生时,阻塞态可以转为就绪态,如果此时没有其他进程运行,便转为运行态。
5.1 僵尸进程
当自进程退出,父进程运行,子进程必须等到父进程捕获到了子进程的退出状态才真正结束,在子进程退出后,父进程读取状态之前,此时称为僵尸状态。
僵尸进程的目的是为了维护子进程的信息,为了方便父进程以后在某个时候获取。但是僵尸进程会占据内核资源,需要避免僵尸进程的产生或者立即结束子进程的僵尸状态。
5.1.1 僵尸进程的危害
5.1.2 如何避免僵尸进程
- ⽗进程调⽤wait/waitpid等函数等待⼦进程结束,如果尚⽆⼦进程退出wait会导致⽗进程阻塞。 waitpid只会等待由pid参数指定的⼦进程,同时也是⾮阻塞,⽬标进程正常退出返回⼦进程PID,还没结束返回0。
- 通过 signal() 函数⼈为处理信号 SIGCHLD。只要有⼦进程退出⾃动调⽤指定好的回调函数,因为⼦进程结束后, ⽗进程会收到该信号 SIGCHLD ,可以在其回调函数⾥调⽤ wait() 或 waitpid() 回收;
- 忽略SIGCHLD信号,这常⽤于并发服务器的性能的⼀个技巧因为并发服务器常常fork很多⼦进程,⼦进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理⽅式设为忽略,可让内核把僵⼫⼦进程转交给init进程去处理,省去了⼤量僵⼫进程占⽤系统资源。
6. 孤儿进程
7. 进程优先级
因为CPU有限,需要用过某种方式竞争资源。
ps -la 显示更详细进程
PRI:优先级.就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
NI:nice值,其表示进程可被执行的优先级的修正数值.nice其取值范围是-20至19,一共40个级别
优先级=老的优先级+nice值。在Linux环境下调整优先级,就是调整nice值。每次设置优先级都是根据默认值的优先级基础上增删。
7.1 查看进程优先级的命令
bash: top
7.2 进程调度算法
1.先来先服务
非抢占式的调度算法,按照请求的顺序进行调度。
有利于长作业,但不利于短作业,因为短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长。
2.短作业优先
非抢占式的调度算法,按估计运行时间最短的顺序进行调度。
长作业有可能会饿死,处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来,那么长作业永远得不到调度。
3.最短剩余时间优先
最短作业优先的抢占式版本,按剩余运行时间的顺序进行调度。
当一个新的作业到达时,其整个运行时间与当前进程的剩余时间作比较。
如果新的进程需要的时间更少,则挂起当前进程,运行新的进程。否则新的进程等待。
4.时间片轮转
注意:在所有非抢占CPU调度算法中,系统平均响应时间最优的是 短任务优先算法
7.3 调度优先级
当进程的时间片用完,此时降低进程的优先级,以给其他进程调度的机会,是合理的。
进程刚刚完成IO操作,刚进入就绪态等待被调度,此时降低他的优先级是不合理的。
进程持久处于就绪队列,一直没有被调度,此时更应该提高他的优先级。
进程刚开始运行就降低优先级,有可能会被其他进程抢断,也是不合理的。
8. 环境变量
现象:系统命令pwd ls不用跟路径,自己写的程序执行时需要带路径 ./myproc 。
查看环境变量: echo $PATH
8.1 常见的环境变量
PATH :指定命令的搜索路径
HOME :指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
SHELL :当前Shell,它的值通常是/bin/bash
普通用户和root用户的环境变量不相同。
8. 2相关命令
8.3 通过系统调用获取或设置环境变量
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%s\n", getenv("PATH"));
return 0;
}
8.4 环境变量通常是具有全局属性的
在命令行上使用export 相当于定义了一个全局变量