直观概念:程序的一个执行实例,正在执行的程序等
- 内核观点:担当分配系统资源(CPU时间,内存)的实体
操作系统是如何管理进程的呢?先把进程描述起来,再把进程组织起来。
描述进程-PCB:进程信息被放在一个叫做程序控制块的数据结构中,可以理解为进程属性的集合。课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct
- 组织进程:内核采用双向链表来组织task_struct
task_struct部分内容:
进程标识符(PID):别名进程号,在操作系统中不会重复
状态:任务状态,退出代码,退出信号等
优先级:相对于其他进程的优先级
内存指针:指向程序地址空间
程序计数器:保存进程即将要执行的下一条指令的地址
上下文数据:保存上一次执行时,寄存器当中的值
I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
记账信息:当前进程启动的时间,当前进程占用CPU的时间
程序计数器&&上下文数据 应用实例:
- 进程在执行的时候,进程会进行切换,切换是由操作系统调度的
- 在被切换出去的时候,该进程中的程序计数器会保存程序要执行的下一条指令,上下文信息会保存寄存器当中的值。
- 再次切换回来的时候,通过程序计数器和上下文信息来恢复之前的场景,继续运算
进程状态
运行:正在CPU上面运算
就绪状态:程序已经准备好,在就绪队列中等待CPU资源
阻塞状态:等待I/O就绪R:运行状态
S:可中断睡眠状态
D:磁盘睡眠状态,不可以被打断
T:暂停状态(可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。)前台进程:“+”代表前台进程
后台进程:没有“+”代表后台进程t:跟踪状态---gdb调试程序的时候可以发现程序是t状态
X:死亡状态
Z:僵尸状态
了解了进程状态之后,如何来查看进程的一些属性呢
LInux下我们通过ps aux | grep 程序名
来查看一个进程相关信息
//测试代码
#include <stdio.h>
#include <unistd.h>
int main()
{
while(1)
{
//sleep(1);
}
return 0;
}
在另一台终端输入ps aux | head -1; ps aux | grep ./test | grep -v grep
(分号前面的指令是为了打印标题,后面的指令是用来查看进程信息的,grep -v grep是为了过滤)
不加grep -v
加上后效果
执行指令后我们可以读出进程标识符PID,以及进程状态(为R+)。
加上sleep(1)后,我们可以发现此时进程状态为S+。
那么如何把一个前台进程变为后台进程呢,在可执行程序后加 & 即可。
相信大家在Linux下对 ctrl+c 非常熟悉,不管遇见什么难倒我们的事情,第一件事情就是无脑 ctrl+c,看是否解决问题 /滑稽 。我们可以使用 ctrl+c 关闭任何一个我们想要关闭的前台进程,但是对于后台进程他却无能为力。kill -9 pid可谓linux最强的命令了,只需知道进程的pid就可以杀死这个进程。我们来试试
kill命令能干掉后台进程,可是我们不想这么粗暴,还有其他办法吗?答案是有的:Linux下还有一个 fg 命令,他可以把最后一次放到后台的进程恢复为前台进程。然后就可以用ctrl+c关闭啦
僵尸状态我们在后文赘述。
关于进程的相关信息我们还可以通过ll /proc/进程PID
来进行查看,例如查看1号进程打开的文件描述符:ll /proc/1/fd
进程优先级
- 基本概念:CPU资源分配的先后顺序,就是指进程的优先权
- 查看:用top命令查看或更改已存在进程的nice值(NI)
- >先在命令行输入 top
进入top后按“r”–>输入进程PID–>输入nice值 即可完成NI值修改
- 计算公式:PR(new) = PR(old) + NI;
PRI && NI辨析
- > PRI即进程优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小,进程的有限级别越高;NI就是我们所说的nice值,其表示进程可被执行的优先级的修正数值。
- > PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
- > 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行所以,调整进程优先级,在Linux下,就是调整进程nice值
- > nice其取值范围是-20至19,一共40个级别。
- > 需要注意进程的nice值不是进程的优先级,它们是不同的概念,但是进程的nice值会影响到进程的优先级变化;可以将nice值理解为进程优先级的修正数据
fork()函数
- 理解:fork()函数从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程
#include <unistd.h>
pid_t fork(void);
//返回值:子进程中返回0,父进程返回子进程id,出错返回-1
- fork有两个返回值
- 父子进程之间代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
//测试代码
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t ret = fork();
if(ret < 0)
{
//error
perror("fork error");
return 0;
}
else if(ret > 0)
{
//father
while(1)
{
printf("i am father, ret=%d, pid=%d, ppid=%d\n", ret, getpid(), getppid());
sleep(1);
}
}
else
{
//child
while(1)
{
printf("i am child, ret=%d, pid=%d, ppid=%d\n", ret, getpid(), getppid());
sleep(1);
}
}
return 0;
}
解释:
pid_t getpid(void) //谁调用返回谁的进程pid
pid_t getppid(void) //谁调用返回谁的父进程pid
运行结果:
- 通过返回值的不同可以让父子进程执行不同的代码
- 通过 ps -ef可以查看进程的pid以及进程父进程的pid,从而确定哪一个进程是父进程,哪一个进程是子进程
- 通过终端启动的前台程序,父进程是bash(命令行解释器)
- 一旦创建出子进程后,父进程和子进程就是抢占式执行
其他相关概念:
- > 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
- > 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
- > 并行:多个进程,每个进程读独占一颗CPU,在同一时间,同时计算各自程序中的代码
- > 并行:多个进程在同一颗CPU上面运行,每一个进程都独占CPU一小会,操作系统进行进程切换,让每一个进程都能使用CPU
总结:
- fork()函数是在代码当中创建一个进程
- 1.调用fork()函数,fork()函数是一个系统调用
- 2.父进程调用fork()函数,创建子进程,子进程拷贝父进程的PCB(task_struct),父进程和子进程是独立的两个进程
- 3.子进程从fork函数之后的代码开始运行,通过程序计数器(保存了程序即将要执行的下一条指令)和上下文数据(保存了寄存器当中的值)知道自己如何运行