进程学习笔记
1.进程概念
进程指的是正在执行的程序
或者程序的一个执行实例
,站在内核
的角度,进程担当分配系统资源(cpu时间、内存)的实体
。
2.描述进程(PCB)
(1)怎么管理进程?
操作系统是一款搞管理
的软件,那么要怎么才能管理好进程呢?
- 先把用
结构体
进程描述
起来(进程控制块
) - 在用一些数据结构将这些描述块
组织
起来
(2)描述进程
- 所有的进程信息都放在
进程控制块中(PCB)
,linux操作系统
下的PCB
叫做task_struct
,它可以理解为进程属性的一个集合。 task_struct
是linux内核中的一种数据结构
(可以把它理解为结构体
)。它会被加载到内存中并且包括描述进程的所有信息
。
(3)task_struct中的内容
下边是task_struct中的一小部分信息。实际上,描述一个进程需要很多的信息,这个结构体非常的大,有兴趣的朋友可以自行上网查一下task_struct中的所有信息。
标识符(PID):描述进程的唯一标识符,用来区别其他进程。
进程状态:进程的任务状态、退出代码、退出信号等。
优先级:相对于其他进程的执行次序,优先级高的进程应优先获得处理。
程序计数器:程序中即将被执行指令的下一条指令的地址。(cpu里一个重要的寄存器–PC指针)。
上下文数据:进程执行时处理器的寄存器中的数据。
I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
内存指针:程序代码和进程相关数据的指针(程序和数据的地址)和其他进程共享的内存块的指针。
资源清单:列出了进程在运行期间所需的全部资源(除CPU),还有一张已分配到该进程的资源的清单。
其他信息等等。
上下文数据是指程序执行时处理器各种寄存器中的数据。包括:
- 通用寄存器。
- 程序计数器:程序中即将执行的
下一条指令的地址
。 - 程序状态字PSW:含有
状态信息
,如执行方式
,中断屏蔽字
等。 - 用户栈指针:指每个用户进程都有一个或若干个与之相关的
系统栈
,用于存放过程和系统调用参数及调用地址
。
上下文数据存在的必要性:处理机处于执行状态时,正在处理的许多信息都是放在寄存器中。当进程被切换时,处理机状态信息都必须保存在相应的PCB中,以便在该进程重新执行时能再从断点继续执行。
3.组织进程
PCB是进程的唯一描述块,为了动态插入和删除
,PCB由链表
组织。进程创建
时,为该进程生成一个PCB
,进程终止
时,回收PCB
。
4.查看进程信息
ls /proc/
。要查看几号进程信息,就查看对应的文件。例如ls /proc/1
ps aux
。例如ps aux | grep test | grep -v grep
查看关于test的进程信息top
。相当于windows下的任务管理器
5.获取进程的标识符(PID)
我们可以直接通过系统调用接口获得进程的PID:它们在头文件<sys/type.h>和<unistd.h>
中包括。
- 子进程id:
getpid()
- 父进程id:
getppid()
6.创建进程(fork)
新进程的创建,首先在内存中为新进程创建一个task_struct
结构,然后将父进程的task_struct内容复制
其中,再修改部分数据
。分配新的内核堆栈、新的PID、再将task_struct 这个PCB添加到链表中
。
(1)fork函数
fork函数的作用是从已经存在的进程中创建一个子进程。此时,新进程为子进程,源进程为父进程。fork函数位于头文件<unistd.h>
中,它被定义为:
pid_t fork(void);
fork函数的返回值:(fork函数有两个返回值)
- 在
父进程
中,fork返回新创建的子进程的PID
- 在
子进程
中,fork返回0
- 当fork
失败
,返回-1
(PCB是要占内存的,当内存不够或者实际用户进程超过了限制fork会调用失败)
为什么fork后,父进程要返回子进程的PID子进程要返回0呢?
- 一个
父进程
有多个子进程
- 一个
子进程
有一个父进程
(fork之后父进程需要知道是哪一个子进程在完成任务)
(2)写时拷贝
fork
之后,父子进程的代码和数据是共享的
,但是当任意一个进程试图写入
时(既修改数据
),这样便会以写时拷贝的方式父子进程各自拥有一份数据副本
。下图很清晰的反应了写时拷贝的场景。
为什么要有写时拷贝,而不是直接在创建PCB时拷贝一份呢?
我们可以站在以下角度思考:
内存
资源性能
问题
写时拷贝可以让我们更合理的使用内存空间
,不直接在创建PCB时拷贝一份,可以避免拷贝时的一些系统开销
。
(3)fork的常规用法
- 父进程希望子进程复制自己,执行不同的代码块,既用
if else
分流。
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<unistd.h>
4 #include<stdlib.h>
5
6 int main()
7 {
8 pid_t id=fork();
9 int i=1;
10 if(id<0){
11 perror("use fork");
12 exit(1);
13 }
14 else if(id==0){//child
15 i--;
16 printf("I am child ,pid is %d,My parent is %d , i is %d\n",getpid(),getppid(),i);
17 }
18 else{//parent
19 i++;
20 printf("I am perent,pid is %d,My parent is %d , i is %d\n",getpid(),getppid(),i);
21 sleep(3);
22 }
23 return 0;
24 }
运行结果如下:
一般来说,fork之后父、子进程执行顺序是不确定的,这取决于内核调度算法。进程之间实现同步需要进行进程通信。
- 一个进程要执行一个不同的程序。fork返回后,调用exec函数(下边详解)
(4)vfork和fork的区别
- vfork也是用于创建子进程。而vfork之后的父子进程共享地址空间,也就是说他们除了PCB不一样,在都一样,fork的子进程则具有独立的地址空间。
- vfork的子进程保证让子进程先运行,在调用exec后父进程才可能被调度。
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<unistd.h>
4 #include<stdlib.h>
5
6 int num=10;
7 int main()
8 {
9 pid_t id=vfork();
10 if(id<0){
11 perror("use fork");
12 exit(1);
13 }
14 else if(id==0){//child
15 sleep(5);
16 num=20;
17 printf("child:num=%d\n",num);
18 exit(0);
19 }
20 else{//parent
21 printf("parent:num=%d\n",num);
22 }
23 return 0;
24 }
运行结果如下:
运行结果是先等待5秒,然后先执行子进程,而子进程对数据修改为20,在父进程中num也输出为20,说明父子进程共享地址空间。
7.进程状态
(1)有哪些进程状态呢?
R (running)
运行状态:并不意味着进程一定在运行中,表明进程要么是在运行中要么在运行的队列里。S(sleeping)
睡眠状态:意味着进程在等待事件完成。(这里的睡眠也叫做可中断睡眠(interruptible sleep)
,既浅度睡眠,它可以被Kill掉
)D(Disk sleep)
磁盘休眠状态:有时候也叫做不可中断睡眠
状态(uninterruptible sleep)
,在这个状态的进程通常会等待IO的结束
,它不能被Kill
掉。T(stopped)
停止状态:可以通过发送SIGSTOP
信号给进程来停止进程
。被暂停的进程可以通过发送SIGCONT
信号让进程继续运行
。X(dead)
死亡状态:这个状态只是一个返回状态
,不会在任务列表里看到这个状态。
(2)修改进程状态
我们可以根据命令向相应的进程发送信号来改变进程的状态:
kill -l
查看系统支持的信号列表kill -SIGSTOP pid
使pid号进程停止(例如:kill -SIGSTOP 1234
使1234号进程变为T
状态)kill -SIGCONT pid
使pid号进程继续运行
8.僵尸进程和孤儿进程
单独总结于我的另一边博客:
https://blog.csdn.net/hansionz/article/details/82829587
9.进程的优先级
cpu资源分配的先后顺序
,就是指进程的优先级(priority)
。优先级高
的进程有优先执行
权利。
(1)查看系统的进程
命令ps -l
用来查看系统进程
- UID:执行者身份
- PID:进程的PID
- PPID:父进程的PID
- PRI:进程的优先级,值越小优先级越高
- NI:进程的nice值
(2)PRI和NI
- PRI值越小就越快被执行,加入nice值后,PRI变为:
PRI(new)=PRI(old)+nice
- 当
nice值为负值
时,那么该程序的优先级值将变小
,其优先级会变高,则会越快被执行。所以,调整进程优先级,在Linux下,就是调整进程nice值。 nice
的取值范围为-20~19
,一个40个级别。- 进程的nice值
不是
进程的优先级,而是进程优先级的修正数据
。
为什么调整优先级要存在上下限呢?
为了让调度器调度资源均衡
(3)调整进程优先级
-
启动进程前调整:
nice
nice -n -10 ./test
: 调整test进程的nice为-10 -
调整已存在进程的nice:
renice
renice -10 -p 2189
:pid为2189的进程nice设为-10 -
top命令更改已存在进程的nice:
top->进入top后按"r"->输入进程pid->输入nice值