文章目录
全文约 4967 字,阅读时长预计: 13分钟
进程
什么是进程:
- 白话:程序的一个执行实例,正在执行的程序等。
- 内核观点:担当分配系统资源(占用CPU时间,内存)的实体。
- 在磁盘上放着就是一个普通的可执行二进制文件;加载到内存跑起来就是进程。
进程信息被放在一个叫做进程控制块(PCB)的数据结构中,可以理解为进程属性的集合。
Linux操作系统下的PCB是: task_struct,一个结构体。所有运行在系统里的进程都以task_struct链表的形式存在OS操作系统内核里。
OS操作系统通过这样的结构体描述进程,再通过链表的方式组织起来,形成一个运行队列,CPU从运行队列里取指令干活…
- CPU核心工作流程:取指令,解析指令,执行指令…
task_ struct内容分类
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态:任务状态,退出代码,退出信号等。 — 睡觉、吃饭、打游戏
- 优先级:相对于其他进程的优先级。 – 排队拿到某种资源,排队吃饭
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针. – 指向程序的代码和数据(变量常量什么的)
- 上下文数据: 进程执行时处理器的寄存器中的数据。 – 运行中存放在CPU中的临时数据
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。 — 不同的年龄干不同的事情
- 其他信息
PID
- 查看进程属性:
ps ajx | grep 进程名字
- 加上属性列显示:
ps axj | head -1 && ps ajx | grep 进程的PID
top
:任务管理器- 获取进程的PID
int main()
{
while(1)
{
printf("i am a process,my pid is:%d\n",getpid());
printf("i am a process,my father is:%d\n",getppid());
sleep(1);
}
return 0;
}
- 由上面代码可知,bash是父进程,bash创建子进程去执行任务。
- PID : 代表这个进程的代号
- PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
并发、并行、时间片
- 时间片:进程在CPU上运行时,不是一直运行到程序结束,而是有个运行时间单位。
- 进程让出CPU:1,运行队列里,来了一个优先级更高的;2,时间片到了
- 并发:单核CPU,运行多个进程,是通过进程快速切换的方式,在一段时间内,让所有的程序都得到推进。
- 并行:多核多CPU,任何时刻,允许多个进程同时执行。
上下文数据
- 当一个进程在运行时,因为某些原因,需要被暂时停止执行,让出CPU,需要进程保存自己的所有临时数据。
- 这个临时数据就是上下文数据,保存到PCB里的某部分。
- 保存的目的是为了等会儿回复继续运行
Fork
- 操作系统提供的创建子进程的接口。
int main()
{
printf("i am a process,my pid is:%d\n", getpid());
int id = fork();
if (id == 0)
{
sleep(3);
int a = 1 / 0;
}
else if (id > 0)
{
while (1)
{
sleep(2);
printf("i amfather: im still alive\n"); 进程独立性测试
}
}
- 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)。
- 因为代码是只读的,不可修改或者写入
- 数据各自一份,保证互不影响。就算子进程挂了,父进程还能跑。
- OS中,所有进程,具有独立性。
fork
有两个返回值,:0,是子进程;大于0 ,就是父进程。- 会什么由两个返回值?
fork
:一个函数,在返回PID之前;子进程已经创建出来了,代码共享;所有由两个返回值。- 为什么要返回值?:因为PID是给程序员用的…,便于使用;若不用,就
kill 命令
结束掉。 - 那为什么子进程返回的是0,父进程fork返回的是子进程的PID?
- 一个孩子找父亲是很简单的,而一个父亲,会有多个子女,需要给每个孩子进行标识。
fork
之后通常要用if
进行分流,做不同的事情,可以创建多个子进程。if else if
的语句都会被执行,cpu多执行流。- cpu切换时间纳秒级别,如播放时下载,下载时播放。
数据各有一份验证:
#include <stdio.h>
#include <unistd.h>
int g_val = 0;
int main()
{
printf("begin.....%d\n", g_val);
pid_t id = fork();
if(id == 0){ //child
int count = 0;
while(1){
printf("child: pid: %d, ppid: %d, [g_val: %d] [&g_val:%p]\n", getpid(), getppid(), g_val, &g_val);
sleep(1);
count++;
if(count == 5){
g_val=100;
}
}
}
else if(id > 0){
//parent
while(1){
printf("father: pid: %d, ppid: %d, [g_val: %d] [&g_val:%p]\n", getpid(), getppid(), g_val, &g_val);
sleep(1);
}
}
else{
//TODO
}
return 0;
}
创建多个子进程:
int main()
{
pid_ t ids[5] ;
printf("I am father : %d\n", getpid());
for(int i=0 ;i < 5; i++){
ids[i] = fork();
if(ids[i] == 0)
{
/ /chlild
DoThing() ;
exit(1) ;
}
printf("%d, %d, %d, %d, %d\n",ids[0], ids[1], ids[2], ids[3], ids[4]) ;
getchar();
return 0;
}
Linux下的进程状态:
- Linux内核源码下对状态的定义:
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
- R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。如:
kiil -19 PID
- t (tracing stop):打断点调试时候的状态。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。一瞬间的事情。
浅度休眠、深度休眠
- S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
- 浅度休眠,随时可以接受外部的信号,处理外部的请求,对外部事件作出反应。
- 浅度休眠,随时可以接受外部的信号,处理外部的请求,对外部事件作出反应。
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
- 因为外设磁盘的读取写入的速度很慢,需要等待磁盘的回应,写成功或失败;你操作系统也管不了我;
- 如果正在写入过程中,被OS当成闲置资源终止掉(内存这么紧张,你怎么还不干活儿),那么可能导致数据丢失;场景:同一时间内,上百万用户正在注册,往硬盘里写数据时…
- S:后台进程
- S+:前台进程;一旦运行,我们的bash命令就无法进行解释;
Ctrl C
结束运行。
进程优先级
- cpu资源分配的先后顺序,就是指进程的优先权(priority)。
- 权限:决定了能不能拥有某种资源
- 优先级:得到某种资源的先后次序。
- 为什么有优先级?因为全球资源有限,human活着就是为了资源。
- 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
- 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。
ps -l
:
- PRI :代表这个进程可被执行的优先级,其值越小越早被执行
- NI :代表这个进程的nice值
- 优先级由 PRI 和 NI 决定:
- PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:
PRI(new)=PRI(old)+nice
- nice值,其表示进程可被执行的优先级的修正数值,【-20 ~ 19】。
- 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行所以,调整进程优先级,在Linux下,就是调整进程nice值。
用top命令更改已存在进程的nice:
top
- 进入top后
按“r”–>输入进程PID–>输入nice值
- 每次输入计算的
PRI(old)
值都是不变的默认基准值。
竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。
操作系统:公平调度,效率第二。
僵尸状态
- 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(处在等待时期)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。
- 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
- 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
- 白话:
子进程的创建,是为了完成某项任务或者工作;
进程退出,一般不是立马让OS释放进程的所有资源;而是在退出的时候,会自动将自己退出时的相关信息,写入进程的PDB中,供OS或者父进程读取。
读取成功之后,才算真正的死亡。父进程通过调用接口让OS来回收。
相关信息:子进程干的怎么样,完成任务了没有,遇到了什么bug等等。
---------------------------- 验证代码:
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 1;
}
else if(id > 0){ //parent
printf("parent[%d] is sleeping...\n", getpid());
sleep(30);
}else
{
printf("child[%d] is begin Z...\n", getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}
- D、Z,状态下的进程都无法被KILL。
孤儿进程
- 父进程先退出,子进程就称之为“孤儿进程”
- 孤儿进程例子:
int main()
{
printf("i am a process,my pid is:%d\n", getpid());
int id = fork();
if (id == 0)
{
sleep(3);
int a = 1 / 0; //除零错误,停止运行
}
else if (id > 0)
{
while (1)
{
sleep(10);
printf("i amfather: im still alive\n");
}
}
- 孤儿进程被1号init进程领养,当然要由init进程回收喽。
- 操作系统启动前有0号进,当OS完全启动以后,用户登录前,会被1号进程所取代。
自动化查看进程状态的脚本
while :; do ps axj | head -1 && ps axj | grep 进程名字; sleep 1; echo "---------------------"; done
G。
- 资源有限,竞争者不计其数…