Download presentation
Presentation is loading. Please wait.
Published byΠέρσις Δασκαλόπουλος Modified 5年之前
1
陈香兰 xlanchen@ustc.edu.cn 助教:陈博、李春华 Spring 2009
嵌入式操作系统 陈香兰 助教:陈博、李春华 Spring 2009
2
进程(任务管理) Linux
3
主要内容 进程描述符 进程切换 进程的创建和删除 2018/12/29 嵌入式操作系统
4
进程的概念 进程是执行程序的一个实例 进程和程序的区别 几个进程可以并发的执行一个程序 一个进程可以顺序的执行几个程序 2018/12/29
嵌入式操作系统
5
进程描述符 为了管理进程,内核必须对每个进程进行清晰的描述。 进程描述符提供了内核所需了解的进程信息
源码include/linux/sched.h定义 struct task_struct 数据结构很庞大 2018/12/29 嵌入式操作系统
6
此图来自ULK3版, 与Linux 已有不符 仅作示意 2018/12/29 嵌入式操作系统
7
Linux2.6进程的状态 简单过一下,与状态相关的一些宏 1)组合状态 2)状态判断 3)状态设置 2018/12/29 嵌入式操作系统
8
进程状态转换图 EXIT_ZOMBIE或者 EXIT_DEAD 或者 TASK_DEAD 2018/12/29 嵌入式操作系统
9
标识一个进程 使用进程描述符地址 使用PID (Process ID,PID)
进程和进程描述符之间有非常严格的一一对应关系,使得用32位进程描述符地址标识进程非常方便 使用PID (Process ID,PID) 每个进程的PID都存放在进程描述符的pid域中 2018/12/29 嵌入式操作系统
10
进程的PID 进程的pid字段 顺序使用 && 循环使用 Pid最大值 2018/12/29 嵌入式操作系统
11
Pid的管理和分配 创建一个进程时, Pid名字空间 Pid位图 do_forkcopy_processalloc_pid
Struct pid的cache 关于名字空间的更多信息,参见: Nsproxy.c以及pid_namespace.ch 2018/12/29 嵌入式操作系统
12
Init_pid_ns 在start_kernel中,调用pidmap_init进行合理的初始化
在kernel_init中,被修改为init进程 2018/12/29 嵌入式操作系统
13
分配第一个位图页 初始化struct pid的cache 2018/12/29 嵌入式操作系统
14
阅读alloc_pid、 alloc_pidmap函数
2018/12/29 嵌入式操作系统
15
进程和进程的内核堆栈 Linux为每个进程分配一个8KB大小的内存区域,用于存放该进程两个不同的数据结构: Thread_info
进程处于内核态时使用 不同于用户态堆栈 内核控制路径所用的堆栈 很少,因此对栈和描述符 来说,8KB足够了 Thread_info 2018/12/29 嵌入式操作系统
16
Thread_union C语言允许用如下的一个union结构来方便的表示这样的一个混合体 2018/12/29 嵌入式操作系统
17
Thread_info的分配/回收/访问
进程描述符的分配/回收/访问 Thread_info的分配/回收/访问 alloc_thread_info free_thread_info 2018/12/29 嵌入式操作系统
18
2018/12/29 嵌入式操作系统
19
从内核堆栈获得thread_info 根据thread_info描述符和内核态堆栈之间的配对,内核可以很容易的从堆栈寄存器的值获得thread_info的指针 参见arch\arm\kernel\entry-header.S 参见include\asm-arm\thread_info.h 2018/12/29 嵌入式操作系统
20
current宏进程描述符 include\asm-arm\Current.h 2018/12/29 嵌入式操作系统
21
Current宏的使用 Current宏可以看成当前进程的进程描述符指针,在内核中直接使用
比如current->pid返回在CPU上正在执行的进程的PID 2018/12/29 嵌入式操作系统
22
进程链表 为了对给定类型的进程(比如所有在可运行状态下的进程)进行有效的搜索,内核维护了几个进程链表 所有进程链表 在进程描述符中:
2018/12/29 嵌入式操作系统
23
进程链表中的插入和删除 使用常规list数据结构操作 list_add list_add_tail list_del list_move
list_empty list_for_each list_for_each_prev list_for_each_safe list_for_each_entry … 进程链表中的插入和删除 使用常规list数据结构操作 参见include/linux/list.h或者lib/list_debug.c 2018/12/29 嵌入式操作系统
24
例如,在do_fork调用的copy_process中
for_each_process宏扫描整个进程链表 RCU read copy update 2018/12/29 嵌入式操作系统
25
const struct sched_class,调度类
TASK_RUNNING状态的进程组织 入列出列等操作: dequeue_task enqueue_task const struct sched_class,调度类 rt_sched_class fair_sched_class idle_sched_class 每个cpu有一个运行队列 关于调度的描述, 参见sched_coding.txt和 sched-design-CFS.txt 2018/12/29 嵌入式操作系统
26
运行队列数据结构 2018/12/29 嵌入式操作系统
27
struct cfs_rq 红黑树 2018/12/29 嵌入式操作系统 ……
28
struct rt_rq 基于优先级的运行队列 …… 2018/12/29 嵌入式操作系统
29
2018/12/29 嵌入式操作系统
30
2018/12/29 嵌入式操作系统
31
调度类 阅读调度类sched_class的定义源码 找到主要与运行队列有关的 Idle相关:idle_sched_class Fair相关
enqueue_task、dequeue_task Idle相关:idle_sched_class no enqueue/yield_task for idle tasks dequeue_task_idle Fair相关 enqueue_task_fair dequeue_task_fair Rt相关 enqueue_task_rt dequeue_task_rt 2018/12/29 嵌入式操作系统
32
Idle类特殊 2018/12/29 嵌入式操作系统
33
Fair类 进而查看 1)enqueue_entity 2)__enqueue_entity (红黑树) 3)sched_entity结构
4) struct rq 5)struct cfs_rq Completely Fair Scheduler 完全公平调度 2018/12/29 嵌入式操作系统
34
Rt类 进而查看: 1)enqueue_rt_entity 2)__enqueue_rt_entity 每个cpu有一个队列
3)sched_rt_entity 4)struct rq 5)struct rt_rq 6) struct rt_prio_array 2018/12/29 嵌入式操作系统 优先级队列
35
激活一个任务 activate_task 相对的:deactivate_task 2018/12/29 嵌入式操作系统
36
pidhash表及链接表 在一些情况下,内核必须能从进程的PID得出对应的进程描述符指针。例如kill系统调用
初始化:pidhash_init Hash函数的使用情况 2018/12/29 嵌入式操作系统
37
可以从进程描述符得到进程的pid相关信息 Task_struct中:
2018/12/29 嵌入式操作系统
38
Pid数据结构 2.6内核为PID专门引入了一个数据结构,Why? 独立的进程;进程组;sessions 使用pid数字的注意之处
考虑进程的删除和创建 2018/12/29 嵌入式操作系统
39
pidhash表及链接表 2018/12/29 嵌入式操作系统
40
进程之间的亲属关系 程序创建的进程具有父子关系,在编程时往往需要引用这样的父子关系。进程描述符中有几个域用来表示这样的关系
2018/12/29 嵌入式操作系统
41
等待队列 当要把除了TASK_RUNNING状态之外的进程组织在一起时,linux使用了等待队列
TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程再分成很多类,每一类对应一个特定的事件。在这种情况下,进程状态提供的信息满足不了快速检索,因此,内核引进了另外的进程链表,叫做等待队列 等待队列在内核中有很多用途,尤其是对中断处理、进程同步和定时用处很大 2018/12/29 嵌入式操作系统
42
等待队列使得进程可以在事件上的条件等待,并且当等待的条件为真时,由内核唤醒它们 等待队列由循环链表实现
阅读相关的宏 2018/12/29 嵌入式操作系统
43
在等待队列上内核实现了一些操作函数 add_wait_queue add_wait_queue_exclusive
remove_wait_queue 2018/12/29 嵌入式操作系统
44
进程等待 等待一个特定事件的进程能调用下面几个函数中的任一个 进程等待由需要等待的进程自己进行(调用) sleep_on
sleep_on_timeout interruptible_sleep_on interruptible_sleep_on_timeout 进程等待由需要等待的进程自己进行(调用) 2018/12/29 嵌入式操作系统
45
sleep_on 相当于 阅读实际的sleep_on代码 2018/12/29 嵌入式操作系统
46
此外,还可能按照如下方式进行sleep 2018/12/29 嵌入式操作系统
47
例如事件等待wait_event__wait_event
等待,直到事件发生(有效,或…) 2018/12/29 嵌入式操作系统
48
进程的唤醒 利用wake_up或者wake_up_interruptible等一系列的宏,都让插入等待队列中的进程进入TASK_RUNNING状态 2018/12/29 嵌入式操作系统
49
__wake_up __wake_up_common 间接 default_wake_function try_to_wake_up
activate_task 2018/12/29 嵌入式操作系统
50
进程切换(process switching)
为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,这叫做进程切换,任务切换,上下文切换 2018/12/29 嵌入式操作系统
51
进程上下文 包含了进程执行需要的所有信息 用户地址空间 包括程序代码,数据,用户堆栈等 控制信息 进程描述符,内核堆栈等 硬件上下文
2018/12/29 嵌入式操作系统
52
尽管每个进程可以有自己的地址空间,但所有的进程只能共享CPU的寄存器。
硬件上下文 尽管每个进程可以有自己的地址空间,但所有的进程只能共享CPU的寄存器。 因此,在恢复一个进程执行之前,内核必须确保每个寄存器装入了挂起进程时的值。这样才能正确的恢复一个进程的执行 硬件上下文: 进程恢复执行前必须装入寄存器的一组数据 包括通用寄存器的值以及一些系统寄存器 通用寄存器 系统寄存器 与具体的体系结构相关 2018/12/29 嵌入式操作系统
53
在linux中 一个进程的上下文主要保存在thread_info, task_struct的thread_struct中,其他信息放在内核态堆栈中 2018/12/29 嵌入式操作系统
54
thread_info ,体系结构相关 2018/12/29 嵌入式操作系统
55
cpu_context_save 2018/12/29 嵌入式操作系统
56
Thread_struct 在task_struct中 2018/12/29 嵌入式操作系统
57
Pt_regs 用户态/内核态 切换 2018/12/29 嵌入式操作系统
58
上下文切换 switch_to宏执行进程切换,schedule()函数通过调用context_switch,间接调用这个宏一调度一个新的进程在CPU上运行 switch_to利用了prev和next两个参数: prev:指向当前进程 next:指向被调度的进程 2018/12/29 嵌入式操作系统
59
2018/12/29 嵌入式操作系统
60
回顾cpu_context_save结构
…… 回顾cpu_context_save结构 2018/12/29 嵌入式操作系统
61
进程切换的关键语句 堆栈的切换 从此,内核对next的内核态堆栈操作,因此,这条指令执行从prev到next真正的上下文切换,因为thread_info和内核态堆栈紧密联系在一起,改变内核态堆栈就意味改变当前thread_info 2018/12/29 嵌入式操作系统
62
什么时候next进程真正开始执行呢? 2018/12/29 嵌入式操作系统
63
?哪里切换了进程的地址空间 2018/12/29 嵌入式操作系统
64
主要内容 进程描述符 进程切换 进程的创建和删除 2018/12/29 嵌入式操作系统
65
进程的创建 许多进程可以并发的运行同一程序,这些进程共享内存中程序正文的单一副本,但每个进程有自己的单独的数据和堆栈区
一个进程可以在任何时刻可以执行新的程序,并且在它的生命周期中可以运行几个程序 又如,只要用户输入一条命令,shell进程就创建一个新进程 2018/12/29 嵌入式操作系统
66
Linux提供了几个系统调用来创建和终止进程,以及执行新程序
Fork,vfork和clone系统调用创建新进程 exec系统调用执行一个新程序 exit系统调用终止进程(进程也可以因收到信号而终止) 2018/12/29 嵌入式操作系统
67
fork fork系统调用创建一个新进程 fork系统调用为父子进程返回不同的值 调用fork的进程称为父进程 新进程是子进程
子进程几乎就是父进程的完全复制。它的地址空间是父进程的复制,一开始也是运行同一程序。 fork系统调用为父子进程返回不同的值 2018/12/29 嵌入式操作系统
68
exec 很多情况下,子进程从fork返回后很多会调用exec来开始执行新的程序
这种情况下,子进程根本不需要读或者修改父进程拥有的所有资源。 所以fork中地址空间的复制依赖于Copy On Write技术,降低fork的开销 2018/12/29 嵌入式操作系统
69
写时复制技术 写时复制技术允许父子进程能读相同的物理页。
只要两者有一个进程试图写一个物理页,内核就把这个页的内容拷贝到一个新的物理页,并把这个新的物理页分配给正在写的进程 2018/12/29 嵌入式操作系统
70
使用fork和exec的例子 If (result = fork() == 0){ /* 子进程代码 */ …
if (execve(“new_program”,…)<0) perror(“execve failed”); exit(1); }else if (result<0){ perror(“fork failed”) } /* result==子进程的pid,父进程将会从这里继续执行*/ 2018/12/29 嵌入式操作系统
71
分开这两个系统调用是有好处的 若单一的系统调用试图完成所有这些功能将是笨重而低效的 比如服务器可以fork许多进程执行同一个程序
有时程序只是简单的exec,执行一个新程序 在fork和exec之间,子进程可以有选择的执行一系列操作以确保程序以所希望的状态运行 重定向输入输出 关闭不需要的打开文件 改变UID或是进程组 重置信号处理程序 若单一的系统调用试图完成所有这些功能将是笨重而低效的 现有的fork-exec框架灵活性更强 清晰,模块化强 2018/12/29 嵌入式操作系统
72
do_fork 不论是fork,vfork还是clone,在内核中最终都调用了do_fork 2018/12/29 嵌入式操作系统
73
阅读do_fork; copy-process; … 了解大致程序流程 ???子进程从哪里开始执行,它的返回值是什么?
阅读copy_thread(arch/arm/kernel) 复制父进程的堆栈??? 父进程的堆栈中有些什么???Fork系统调用??? 2018/12/29 嵌入式操作系统
74
返回值被强制写0 2018/12/29 嵌入式操作系统
75
子进程的内核态堆栈 用户态堆栈sp的值 用户态下pc的值 子进程恢复到用户态时需要的上下文 返回值被强制写0 esp pc sp
子进程的8K union 高地址 用户态堆栈sp的值 子进程的硬件上下文 用户态下pc的值 子进程恢复到用户态时需要的上下文 返回值被强制写0 esp ret_from_fork pc Thread_info sp 低地址 2018/12/29 嵌入式操作系统
76
子进程的执行 fork后,子进程处于可运行状态,由调度器决定何时把CPU交给这个子进程
进程切换后因为pc指向ret_from_fork,所以CPU立刻跳转到ret_from_fork()去执行。 接着这个函数调用ret_from_sys_call(),此函数用存放在栈中的值装载所有寄存器,并强迫CPU返回用户态 2018/12/29 嵌入式操作系统
77
2018/12/29 嵌入式操作系统
78
2018/12/29 嵌入式操作系统
79
关于pt_regs结构的说明 2018/12/29 嵌入式操作系统
80
2018/12/29 嵌入式操作系统
81
内核线程 系统把一些重要的任务委托给周期性执行的进程 内核线程与普通进程的差别 刷新磁盘高速缓存 交换出不用的页框 维护网络链接等待
每个内核线程执行一个单独指定的内核函数 只运行在内核态 只使用大于PAGE_OFFSET的线性地址空间 2018/12/29 嵌入式操作系统
82
例如,0号进程创建1号进程init 2018/12/29 嵌入式操作系统
83
线程和进程的比较 Linux内核中没有线程的概念 在其他系统中(比如windows) 没有针对所谓线程的调度策略
没有数据结构用来表示一个线程 一般线程的概念在linux中只是表现为一组共享资源的进程(每个这样的进程都有自己的进程描述符) 在其他系统中(比如windows) 线程是实实在在的一种运行抽象,提供了比进程更轻更快的调度单元 在linux中“线程”仅仅是表示多个进程共享资源的一种说法 2018/12/29 嵌入式操作系统
84
创建内核线程 Kerenl_thread()创建一个内核线程,并且只能由另一个内核线程来执行这个调用 2018/12/29 嵌入式操作系统
85
2018/12/29 嵌入式操作系统
86
进程树 进程0 进程1 … 2018/12/29 嵌入式操作系统
87
进程0 所有进程的祖先叫做进程0 在系统初始化阶段由start_kernel()函数从无到有手工创建的一个内核线程 Init_task
Init_thread_union 进程0最后的初始化工作创建init内核线程,此后运行cpu_idle,成为idle进程 2018/12/29 嵌入式操作系统
88
由进程0在start_kernel调用rest_init创建
进程1 又称为init进程 由进程0在start_kernel调用rest_init创建 init进程PID为1,当调度程序选择到init进程时,init进程开始执行kernel_init ()函数 2018/12/29 嵌入式操作系统
89
kernel_init() 为常规内核任务初始化一些必要的内核线程,如:
kflushd 刷新‘脏’缓冲区中的内容到磁盘以归还内存 kswapd 执行内存回收功能的线程 最后kernel_init()函数调用execve()系统调用装入可执行程序init。从此,init内核线程变成一个普通的进程。但init进程从不终止,因为它创建和监控操作系统外层的所有进程的活动 2018/12/29 嵌入式操作系统
90
2018/12/29 嵌入式操作系统
91
撤销进程 进程终止 进程终止的一般方式是exit()系统调用。 内核可以强迫进程终止 这个系统调用可能由编程者明确的在代码中插入
另外,在控制流到达主过程[C中的main()函数]的最后一条语句时,执行exit()系统调用 内核可以强迫进程终止 当进程接收到一个不能处理或忽视的信号时 当在内核态产生一个不可恢复的CPU异常而内核此时正代表该进程在运行 2018/12/29 嵌入式操作系统
92
撤销进程 进程终止使用exit系统调用 删除进程 在父进程调用wait()类系统调用检查子进程是否合法终止以后,就可以删除这个进程
2018/12/29 嵌入式操作系统
93
Thanks! The end.
94
堆栈 堆栈是C语言程序运行时必须的一个记录调用路径和参数的空间 C语言编译器对堆栈的使用有一套的规则
函数调用框架 传递参数 保存返回地址 提供局部变量空间 等等 C语言编译器对堆栈的使用有一套的规则 了解堆栈存在的目的和编译器对堆栈使用的规则是理解操作系统一些关键性代码的基础 2018/12/29 嵌入式操作系统
95
一段小程序 源文件:test.c 这是一个很简单的C程序 main函数中调用了函数p1和p2
首先使用(arm-…)gcc生成test.c的可执行文件test 然后使用(arm-…)objdump –S获得test的反汇编文件 2018/12/29 嵌入式操作系统
96
观察从main ip sp fp? fp ip lr pc sp z=? y=2 x=1 c=a sp 2018/12/29 嵌入式操作系统
Bl Pc=??? Lr=??? 2018/12/29 嵌入式操作系统
97
调用p1 ip fp fp ip lr pc z=? y=2 x=1 c=a ip sp fp fp ip lr pc sp sp
2018/12/29 嵌入式操作系统
98
问题 lr的作用 fp的作用 sp的作用 函数调用时,参数是如何传递的? 返回值是如何返回的? 局部变量在哪里分配空间?
2018/12/29 嵌入式操作系统
99
试着画一画从main调用p2的堆栈变化情况
2018/12/29 嵌入式操作系统
Similar presentations