目录
- 体系结构 -- 硬件上
- 操作系统 -- 软件上
- 进程
- PCB内部属性
- 1、在linux里面看程序
- 2、标识符
- 获取程序的标识符
- 父进程标识符 PPID
- 查看进程的另一种方法
- 通过系统调用创建进程 - fork
- 杀掉一个进程
- for循环创建多个代码
- 3、进程状态
- 进程排队 - 队列
- 教程上关于进程状态表述
- 运行
- 阻塞
- (阻塞)挂起
- Linux下的具有进程状态
- R(running)运行状态
- S(sleep)睡眠状态
- D(Disk sleep)磁盘休眠状态
- t(stopped)暂停状态
- T(tracing stop)暂停状态
- Z(zombie)僵尸状态
- 孤儿进程
- X(dead)死亡状态
- 信号
- 进程的优先级
- 是什么?
- 为什么存在优先级
- 调整优先级
- Linux的调度与切换
- 概念
- 进程切换
- 进程调度
- PCB外部,整体使用
体系结构 – 硬件上
1、冯诺依曼体系结构
独立
CPU:运算器(算数和逻辑运算)+控制器(协调CPU内部之间 信息流动)
输入设备:话筒、摄像头、键盘、鼠标、磁盘、网卡等
输出设备:声卡、显卡、网卡、磁盘、显示器、打印机等
有的设备只做输入,只做输出,有的即做输入又做输出
存储器:内存(掉电易使)
设备是链接的:总线(主板)链接起来
链接不是目的,是手段。目的是设备之间的数据流动(本质:设备之间会进行数据的来回拷贝)
操作系统 – 软件上
进行软硬件管理的软件
什么时候读、什么时候写、都多少、写多少是操作系统管的
对任何事务做管理:先描述,在管理
操作系统给用户提供一系列的系统调用接口对操作系统内部的数据进行访问,不允许用户直接访问操作系统内部数据
系统调用接口,就是C语言设计的函数,系统调用函数(函数就有输入和输出)
操作系统给用户提供:数据方面的支持、功能方面的支持
进程
教材观点:
加载到内存的程序 – 进程
正在运行的程序
事实
(1)我们可以启动多个程序(每个程序都有一个exe) — 我们将多个exe加载到内存
(2)操作系统需要管理多个加载到内存的程序
(3)操作系统如何管理加载到内存的程序?先描述,在组织
一个加载到内存的程序我们称为进程
struct xxx
{
//状态
//优先级
//内存指针字段
//标识符
//……包含进程几乎所有的属性字段
struct xxx* next;
}
上述的描述进程的结构体,我们称为PCB(process ctrl block)进程控制块
struct PCB
{
//状态
//优先级
//内存指针字段
//标识符
//……包含进程几乎所有的属性字段
struct PCB* next;
}
对进程的管理转换成对PCB对象的管理,也就是对链表的增删查改
进程 = 内核PCB对象(内核数据结构) + 可执行程序
PCB,操作系统学科的叫法
具体的linux,PCB称为task_struct,操作系统内部的数据
PCB内部属性
1、标识符
2、状态
3、优先级
4、程序计数器:程序中即将要被执行的指令的地址
5、内存指针:找到代码和数据的指针
6、I/O状态信息:
7、记账信息
1、在linux里面看程序
创建myprocess.c
创建Makefile
运行程序
第一条程序是myprocess进程
第二条程序是grep本身是一个指令,也就是一个程序,它是包含myprocess
几乎所有的独立的指令,就是程序,运行起来也要变成进程
看进程结束的过程
while :; do ps ajx | head -1 && ps ajx | grep myprocess | grep -v grep; sleep 1; done
1、只有打印到显示屏上的内容才能被重定位和追加重定位
(1)'>'
:重定向
(2)‘>>’
:追加重定向
2、‘echo’
:后面跟着string直接打印string,后面跟着$sht则打印变量sht的值
3、'|'
:表示管道,上一条命令的输出,作为下一条命令参数进行传递,如:ls | grep “aa”,在ls的输出中查找aa字符串。
4、'head'
:打印一个文件的前多少行
(1)head:默认打印前十行
(2)head -n:打印前n行
5、&&
:表示前一条命令执行成功时,才执行后一条命令 ,如 echo '1‘ && echo ‘2’
6、grep
(1)grep xxx:过滤出来包含xxx的
(2)grep -v grep:反向过滤,不把含义grep的过滤出来
2、标识符
PID就是标识符
ps axj
COMMAND代表你这个进程在启动的时候对应的可执行程序是谁
获取程序的标识符
函数调用
getpid
man getpid
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4
5 int main()
6 {
7 pid_t id = getpid();
8 printf("%d\n", id);
9 printf("hello zyh\n");
10 return 0;
11 }
在Linux中,普通进程,一般都有父进程
父进程标识符 PPID
getppid
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4
5 int main()
6 {
7 pid_t id = getpid();
8 pid_t fid = getppid();
9 printf("id:%d\n", id);
10 printf("fid:%d\n", fid);
11 printf("hello zyh\n");
12 return 0;
13 }
每一次启动进程的pid都会变化,因为我们每次启动的都是一个新进程
其中bash就是命令行解释器
查看进程的另一种方法
ls
-l 参数 以详细格式列表
-d 参数 仅列当前目录本身
通过/proc
系统文件夹进行查看
当我们要查看某特定进程
为何删了之后还能正常运行,因为程序正在运行被加载到了内存,删除磁盘上的可执行程序没有影响
cwd是当前工作目录
fopen(“./文件名”, “w”);
如果没有此文件就在当前路径下创建
改变当前的工作目录:chdir()
当前路径:当前进程所在的路径cwd
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4
5 int main()
6 {
7 pid_t pid = getpid();
8 printf("pid: %d\n", pid);
9
10 FILE *fp = fopen("./zyh.txt","w");
11 if(fp == NULL) return 0;
12 printf("zyh.txt文件创建成功!\n");
13 fclose(fp);
14 sleep(20);
15 return 0;
16 }
如果进程的所在路径cwd
被修改,那么文件也会被创建在修改后的路径
改变当前的工作目录:chdir()
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4
5 int main()
6 {
7 pid_t pid = getpid();
8 printf("pid: %d\n", pid);
9 sleep(20);
10 printf("修改当前路径\n");
11 chdir("/home/hui");
12 printf("修改当前路径成功\n");
13 sleep(20);
14 FILE *fp = fopen("./zyh.txt","w");
15 if(fp == NULL) return 0;
16 printf("zyh.txt文件创建成功!\n");
17 fclose(fp);
18 sleep(20);
19 return 0;
20 }
通过系统调用创建进程 - fork
对于fork的返回:对于父进程返回创建进程的id,对于子进程返回0
一般而言,我们想让父子做不同的事情
pid都是大于等于0的数字
父进程先被创建了
此时子进程也被创建了
进程 = 内核数据结构 + 可执行程序的代码和数据
创建子进程时,是以父进程为模板的。内核数据结构大部分和父进程一样,但也有些少部分不一样(如,pid、ppid)
(1)fork为何会返回两个值为什么会这样?
把子进程的pid返回给父进程,目的是方便父进程对子进程的控制和唯一性确认。子进程不需要这些,只需要知道创建成功没成功,因此返回0就可以了(父:子 = 1:n,子进程找父进程是唯一的,父进程找子进程就需要pid)
(2)fork为何会返回两次?
如果一个函数,已经运行到了return
的时候,说明这个函数的核心逻辑已经做完了
在调用fork函数的时候,运行到return
这条语句之前,子进程就已经被创建,因此return id
这条语句是同时被父进程和子进程执行的
(3)id怎么可能同一个变量,既大于0,又等于0
一个进程崩溃了,不会影响其他进程
进程(任意)之间是具有独立性的,互相不受影响。(os在设计的时候就必须保证的)
杀掉一个进程
kill -9 进程id
linux中可以使用同一个变量名,表示不同的内存
当代码被编译成二进制,代码中的变量名就不在了
for循环创建多个代码
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 const int num = 10;
6
7 void work()
8 {
9 int x = 10;
10 while(x > 0)
11 {
12 printf("I am a child process, my id: %d\n",getpid());
13 sleep(1);
14 --x;
15 }
16 }
17
18 int main()
19 {
20 for(int i = 0; i < num; i++)
21 {
22 pid_t id = fork();
23 sleep(1);
24 if(id < 0) break;
25 else if(id == 0)
26 {
27 //子进程
28 work();
29 exit(1);
30 }
31 //父进程
32 printf("creat %d child process, subid:%d\n", i, id);
33 }
34 sleep(20);
35 return 0;
36 }
正在创建子进程
正在杀死子进程
3、进程状态
进程排队 - 队列
进程排队一定是在等待某种资源
进程不是一直在运行的,进程放在CPU上,也不会一直运行 — 时间片
排队:是进程的task_struct在排队
Linux中进程pcb的排序
教程上关于进程状态表述
运行、阻塞、挂起
所谓的状态,就是一个整型变量,再task_struct中的一个整型变量
#define New 1
#define Ready 2
#define running 3
#define block 4
struct task_struct
{
int status;
……
}
运行
把进程的PCB放到一个CPU的运行队列中,我们就称为这个队列的运行状态
一个CPU一个运行队列
阻塞
当我们的进程在进行等待软硬件资源的时候,资源如果没有就绪,我们进程的task_struct只能
1、将自己设置为阻塞状态
2、将自己的pcb连入等待的资源提供的等待队列
状态的变换,就是把进程的PCB放到不同的队列中
(阻塞)挂起
挂起状态
前提:计算机资源(eg:内存资源)已经比较吃紧了
阻塞挂起:当进程在阻塞的状态下,资源吃紧了,操作系统就会把阻塞进程的代码和数据从内存写回到磁盘的swap分区上
进程的PCB(task_struct)不会换出
创建进程时先创建对应进程的PCB(内核数据结构)
Linux下的具有进程状态
R(running)运行状态
并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
代码在运行为何显示是S,因为代码里面只有一行printf,printf导致进程大部分时间都在等待
把进程改成一个死循环就是R状态了
“+”:是指这个进程在前台,运行此进程时,无法执行其他命令
./myprocess &
后台进程
(1)后台进程ctrl+c无法杀掉,只能kill -9 进程id
前台进程
后台进程
S(sleep)睡眠状态
意味着进程在等待事件完成,也就是一种阻塞状态,Linux中也称为浅度睡眠
ctrl+c可以结束进程
D(Disk sleep)磁盘休眠状态
称为不可中断睡眠,又称深度睡眠。D状态也是阻塞状态,有些进程执行的内容很重要,操作系统不能把它杀死
t(stopped)暂停状态
让进程处于暂停状态,当某些进程操作比较危险,操作系统会将其暂停。也是阻塞状态
(1)程序开始运行,创建了进程此时进程是前台进程
(2)kill -9 进程id
对进程进行暂停
(3)kill -19 进程id
对进程进行启动,再次启动的进程有前台进程变为后台进程
T(tracing stop)暂停状态
进程处于被追踪状态,等待gdb给的控制命令,gdb不让进程跑了。也是阻塞状态
(1)在生成可执行程序的时候注意加-g
(2)进入gdb; l + 行号:从第几行开始显示代码
(3)p:打断点; r:运行到断点
此时的进程就是t状态,等待gdb的下一步指令
Z(zombie)僵尸状态
也是终止状态,当进程完成时或发生意外,代码和数据可以释放,但进程的PCB不能被释放,父进程或操作系统还需要去读其退出信息
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t id = fork();
//子进程
if(id == 0)
{
int cnt = 5;
while(cnt)
{
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
cnt--;
}
exit(0); //子进程退出
}
//只有父进程,父进程一直死循环就不会读取子进程的退出信息,子进程就处于僵尸状态
while(1)
{
printf("I am father, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
return 0;
}
为何会有Z状态?
创建进程是希望这个进程给用户完成工作的,子进程必须有结果数据,PCB中的。
什么是Z?
进程已经退出,但当前进程的状态需要自己维持住,供上层读取,必须处于Z
如果父进程就不读取?
僵尸状态会一直存在,task_struct对象也要一直存在,都是要占据内存的。内存泄漏
最初创的进程的父进程是bash,会自动回收退出信息,如果在自己创建的进程下面再fork一个子进程,此时子进程的父进程不会自动回收退出信息
bash进程会自动读取其下面子进程的退出信息,但这里创建的father进程不会自动读出子进程的退出信息
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id = fork();
//子进程
if(id == 0)
{
int cnt = 5;
while(cnt)
{
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
cnt--;
}
exit(0); //子进程退出
}
//只有父进程,父进程一直死循环就不会读取子进程的退出信息,子进程就处于僵尸状态
int cnt = 10;
while(cnt)
{
printf("I am father, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
cnt--;
}
//父进程读取子进程的退出信息
wait(NULL);
printf("father wait child done...\n");
sleep(5);
孤儿进程
一个进程的父进程先退出了,那该进程会被1号进程领养,1号进程是操作系统
一个进程被领养后,不仅变成孤儿进程,还会变成后台进程
pid_t id = fork();
//子进程
if(id == 0)
{
while(1)
{
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
cnt--;
}
//exit(0); //子进程退出
}
//只有父进程,父进程一直死循环就不会读取子进程的退出信息,子进程就处于僵尸状态
int cnt = 10;
while(cnt)
{
printf("I am father, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
cnt--;
}
X(dead)死亡状态
即终止状态,是一个瞬时状态很难查到
信号
kill -l
9:SIGKILL
19:SIGSTOP
18:SIGCONT
进程的优先级
是什么?
前提:进程要访问某种资源,进程通过一定的方式(排队),确认享受资源的先后顺序
优先级和权限的区别
优先级:你先还是我先的问题
权限:能不能的问题
为什么存在优先级
资源过少,相对的概念
PRI (Priority):进程的优先级
linux的优先级在进程运行中或运行前是可以被修改的,优先级范围[60,99]–>40
nice值为**[-20, 19]**
数字越小优先级越高
调整优先级
1、top进入任务管理器
2、r(renice)-> 输入对于pid -> 要修改的nice -> q退出
输入r之后的界面
Linux系统允许用户调整优先级,但是不能直接让修改pri,而是修改nice值
nice:进程优先级的修正数据[-20, 19]
pri = pri(old) + nice
pro(old)每次都是80
为何优先级调整在60-99之间
如果不加限制,将自己进程的优先级调整的非常高,别人的优先级调成非常低
优先级较高的进程,优先得到资源,后面还有源源不断的进程产生。常规进程很难享受到CPU资源 — 进程饥饿
Linux的调度与切换
概念
1、进程在运行的时候,放在CPU上,直接必须把进程跑完,才行吗?
不行,现在操作系统都是基于时间片进行轮转执行的
2、
竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
进程切换
进程在运行时,会产生大量的临时数据放到CPU的寄存器中
所有的保存都是为了最终的恢复,所有的恢复,都是为了继续上一次的运行位置继续运行。可以认为寄存器里的数据保存在PCB– 保护上下文。
虽然寄存器数据放在一个共享的CPU里面,但是所有的数据,其实都是被进程私有的
CPU内部的所有临时数据,称作进程的硬件上下文
CPU内的寄存器只有一套,寄存器内保存的数据可以有多套
进程调度
Linux实现进程调度的算法,考虑优先级、饥饿、效率
分时操作系统:公平,每个进程在一定时间段内享受到资源。即便有优先级,大家优先级也不明显。eg:互联网买东西,大家优先级都一样
实时操作系统:进程跑起来必须跑完才能跑下一个,必须严格按照顺序跑,有更高优先级的进程允许插队。 eg:做车载系统,一脚油门下去必须立马反应,不能基于时间片慢慢跑
0-99不考虑实时优先级
100-139:这四十个对应着进程的优先级**[60,99]**
每次从头查数组各个位置是否为空还是很麻烦的:int bitmap[5]
32*5=160
比特位的位置,表示哪个队列
比特位的内容,表示该队列是否为空
检测那个队列中是否有进程,检测对应的比特位是否为1
如果一直有高优先级的进程排队就会产生饥饿问题,解决方法:过期进程和活跃进程
CPU从活跃队列选择进程,新来的进程在过期队列
struct q* active
、struct q* expired
CPU只会去活跃队列里面拿进程,当进程运行时间片结束或运行结束或新建进程,操作系统会将其放入到过期队列
struct q* active = &array[0]
struct q* expired = & array[1]
活跃队列里面的进程不断增多,过期队列中的进程不断减少,对后只需要对active和expired进行交换指针的内容即可
swap(&active, &expired)