欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

高级操作系统——进程管理

程序员文章站 2022-03-05 17:14:48
一、进程描述符 进程控制块PCB:是OS控制进程运行用的数据结构,是一个task_struct结构体。 PCB包括:进程标识信息(进程标识符PID等)、执行现场信息(CPU现场,进程切换时需要保存现场信息)、进程映像信息(进程地址空间,即进程在运行时代码、数据、栈放在什么位置,方便OS对地址空间进行 ......

一、进程描述符

进程控制块pcb:是os控制进程运行用的数据结构,是一个task_struct结构体。

pcb包括:进程标识信息(进程标识符pid等)、执行现场信息(cpu现场,进程切换时需要保存现场信息)、进程映像信息(进程地址空间,即进程在运行时代码、数据、栈放在什么位置,方便os对地址空间进行管理)(现场与地址空间比较重要)、进程资源信息、信号信息。

对pcb,说其中几个重要的字段:

mm_struct:有一个成员mm,标明了进程的地址空间;

thread:记录了进程的现场,最后一个字段;

thread_info在4.4.6版本中改成了stack,包括内核栈(即进程进入内核工作时需要的栈和用户栈是分开的)和一些需要快速访问的数据。

高级操作系统——进程管理

 

 

在4.4.6版本中,stack占用了两个页面,即8k,大部分是放内核栈的,低端约10k存放快速访问的信息。cpu若想访问当前进程的快速访问数据的话,只需要拿到当前的栈指针,即esp寄存器的值,可以推算出数据所在的位置来,因此在查找他的地址的时候,访问速度可以很快。这部分数据可以看作是进程描述符的一部分,在空间上不是连续的,但相互之间有指针,可以相互找得到。

高级操作系统——进程管理

 

 

进程状态转换图,可自行搜索。

在4.4.6中,增加了被跟踪和僵死撤销状态。

进程描述符是管理进程的重要数据结构,故他的组织方式非常重要。0号进程的描述符是由init_task这个变量所存储的。从他出发,所有进程描述符构成了双向链表。task_struct中包含一个成员,叫tasts,tasks类型是list_head类型,tasts本身是嵌入在进程描述符里面的,知道tasks的地址,只要送减去620就能得到进程描述符的首地址。在linux中有很多这样的技巧,即通过嵌入的地址,反推结构体的地址,进而找到结构体的其他成员。

高级操作系统——进程管理

 

 

进程与线程关系

多个线程构成线程组,共享内存,不共享栈。

一个会话对应一个终端,在终端中敲一个命令相当于创建了一个进程组来执行。

下面进行演示,建立一个文件命名为0.gdb,文件内容如下,直接运行

1 target remote localhost:1234
2 dir ~/aos/lab/busybox
3 add-symbol-file ~/aos/lab/busybox/busybox_unstripped 0x8048400
4 display $lx_current().pid
5 display $lx_current().comm
6 b start_kernel
7 b ls_main
8 c

高级操作系统——进程管理

 

 

执行含有下列代码的文件

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <pthread.h>
 4 
 5 void loop(){
 6   while(1);
 7 }
 8 
 9 void *p1(){
10   printf("thread-1 starting\n");
11   loop();
12 }
13 
14 void *p2(){
15   printf("thread-2 starting\n");
16   loop();
17 }
18 
19 void main(){
20   int pid1, pid2;
21   pthread_t t1,t2;
22   void *thread_result;
23 
24   printf("main starting\n");
25 
26   if (!(pid1 = fork())){
27     printf("child-1 starting\n");
28     loop();
29     exit(0);
30   }
31 
32   if (!(pid2 = fork())){
33     printf("child-2 starting\n");
34     loop();
35     exit(0);
36   }
37 
38   pthread_create(&t1, null, p1, null);
39   pthread_create(&t2, null, p2, null);
40   
41   pthread_join(t1, &thread_result);
42   pthread_join(t2, &thread_result);
43 
44   int status;
45   waitpid(pid1, &status, 0);
46   waitpid(pid2, &status, 0);
47   printf("main exiting\n");
48   exit(0);
49 }

 可以看到do-fork可执行文件创建了三个进程,976、977、978

高级操作系统——进程管理

 

 高级操作系统——进程管理

 

979、980是新创建的两个线程

高级操作系统——进程管理

 

 再执行一次,可以看到后台运行了两个

高级操作系统——进程管理

 

 新创建的三个进程是刚创建的

高级操作系统——进程管理

 

 fg %+序号将指定的进程放到前台,ctrl+z放到后台

高级操作系统——进程管理

 

 

用以下三条命令依次查看线程组、进程组和会话的leader

命令的意思是根据进程的描述符,找到线程组leader的描述符,里面对应的字段就是要显示的id

p $lx_task_by_pid(977).group_leader->pids[0].pid->numbers.nr
p $lx_task_by_pid(977).group_leader->pids[1].pid->numbers.nr
p $lx_task_by_pid(977).group_leader->pids[2].pid->numbers.nr

987和988是984创建的,他们处于同一个线程组,leader是976

高级操作系统——进程管理

 

二、进程调度算法

每个进程属于某一个调度器类,每个调度器类都有一个进程队列,不同的队列有不同的调度算法。

先调度硬实时的,软实时次之,普通进程最后。

普通进程使用cfs(完全公平)调度算法:

虚拟时钟,调度器总是选时钟最小的那个进程来执行。

优先级高的进程时钟增长得慢。

所有可运行的进程被放在一个红黑树中。

 

下面进行演示:

再次运行0.gdb,在终端输入ls,使其被捕获

高级操作系统——进程管理

 

 

 

建立文件demo-2-2.gdb,内容如下

 1 break __schedule//进程调度的时候执行这个函数
 2 
 3 break __switch_to//调度时如果切换进程就会调用这个函数
 4   commands
 5     printf "next_p->pid: %d\n", next_p->pid
 6     printf "next_p->se.vruntime: "
 7     print  next_p->se.vruntime
 8   end
 9 
10 break enqueue_task_fair//如果有新进程要进入到cfs队列时,执行这个函数
11   commands
12     printf "p->pid: %d\n", p->pid
13     printf "p->se.vruntime: "
14     print  p->se.vruntime
15   end
16 
17 display   $lx_current().state//显示当前进程的状态
18 display   $lx_current().se.vruntime
19 display   $lx_per_cpu("runqueues").nr_running//cpu里面有多少个进程在运行
20 
21 display ((struct sched_entity *)((void *)$lx_per_cpu("runqueues").cfs.rb_leftmost - 0x8))->vruntime//cfs队列里最左边的节点,即虚拟时钟最小的信息
22 display ((struct task_struct *)((void *)$lx_per_cpu("runqueues").cfs.rb_leftmost - 0x4c))->pid

 

 高级操作系统——进程管理

  由上图可以看到,当前正在运行的是975号进程,当前的虚拟时钟可以从runtime那里看到,state=0表示其当前的状态是就绪的或正在运行,树最左边目前还没有进程。由于enqueue_task_fair函数的作用是往进程队列里面加入新进程,现在已经有一个,可以看到,要加的是7号进程,下面一行的虚拟时钟是个负值,现在还暂时看不到,继续执行。

高级操作系统——进程管理

 

 可以看到7号进程的虚拟时钟小于975号的,下次如果要调度,应该选7号。即将创建的是3号进程。

高级操作系统——进程管理

 

 由上图,975号进程的虚拟时钟增加了,在这两个断点时间,存在中断,这才导致了时钟的增加。运行有一定的随机性,虚拟机在虚拟的时候有一定的随机性。继续

高级操作系统——进程管理

 

 运行了切换函数,下一步要切换7号进程。继续

高级操作系统——进程管理

 

 7号时钟的进程的时钟比之前也增加了,需要注意当前正在运行的进程不放在树里面,但放在了队列里面,队列里面进程就是树里面的进程加当前进程。继续若干次

高级操作系统——进程管理

 

 下一步要创建的是4号进程。

 

除了普通进程有队列之外,其他的硬实时和软实时都有各自的队列。

红黑树的某个节点可以是另外一个树,共用一个时钟。

 

三、进程调度的时机

内核程序的入口,系统调用总控函数,异常处理函数,中断处理函数、内核线程主函数,用bt查看栈顶层,根据函数的种类来确定是哪种内核调用。