收藏 分享(赏)

进程与线程.ppt

上传人:11xg27ws 文档编号:8288708 上传时间:2019-06-18 格式:PPT 页数:221 大小:2.58MB
下载 相关 举报
进程与线程.ppt_第1页
第1页 / 共221页
进程与线程.ppt_第2页
第2页 / 共221页
进程与线程.ppt_第3页
第3页 / 共221页
进程与线程.ppt_第4页
第4页 / 共221页
进程与线程.ppt_第5页
第5页 / 共221页
点击查看更多>>
资源描述

1、第2讲 进程与线程,叶保留 南京大学计算机科学与技术系,教学目标,理解进程/线程基本概念 理解Linux中进程/线程描述机制 理解Linux内核通用链表结构 理解Linux进程的内核管理机制 理解Linux进程/线程创建与实现机制 掌握Linux线程编程技术,2,3,主要内容,进程与线程基本概念 Linux进程的描述与管理 Linux内核通用链表 Linux进程的实现机制 Linux线程的实现机制 Linux线程编程技术,进程与线程基本概念,4,进程及线程基本定义,进程(process) 处于执行期的程序及其所包含资源的总称 程序:可执行程序代码 资源:打开文件、挂起信号、地址空间、数据段等

2、 线程(thread) 进程中活动的对象 有独立的程序计数器、进程栈及一组进程寄存器 节省主存、减少管理开销、快速切换,进程与线程基本概念,5,进程与线程的区别,从形态角度 一个进程可包含一个或多个线程 从调度角度 进程是资源分配的基本单位 线程是处理器调度的独立单位 从虚拟化角度 进程提供两种虚拟机制 虚拟处理器:进程独享处理器的假象 虚拟内存:进程拥有系统内所有内存资源的假象 线程之间可共享虚拟内存,但各自拥有独立虚拟处理器,对Linux系统而言,线程只是一种特殊的进程!,进程与线程基本概念,6,进程构成要素,正文段存放进程运行的代码,描述进程需完成的功能 进程数据段(地址空间) 存放正文

3、段在执行期间所需的数据和工作区 包括全局变量、动态分配的空间(调用malloc函数) 用户栈也在该数据段开辟,存放函数调用时的栈帧、局部变量等 系统堆栈 每个进程捆绑一个,进程在内核态下工作时使用 保存中断现场、执行函数调用时的参数和返回地址等 其中最重要的数据结构是进程控制块(PCB),进程与线程基本概念,7,进程运行环境状态,背景 现代CPU都有几种不同的指令执行级别(特权等级) intel x86 CPU分成0 3四种不同执行级别 对Linux系统而言 只使用其中0级和3级,分别表示内核态和用户态 运行环境状态分类 内核态(亦称核心态或系统态) 可执行特权指令,访问任意物理地址(包括系统

4、空间) 用户态 只能在对应级别允许的范围内活动(用户空间) 分类作用 禁止用户直接修改操作系统数据或直接调用操作系统内部函数 禁止用户程序直接和底层硬件打交道,进程与线程基本概念,8,进程运行环境状态的区分,CS寄存器最低两位显示当前代码的特权级 CPU每条指令的读取都是通过cs:eip这两个寄存器 cs是代码段选择寄存器 eip是偏移量寄存器 上述判断由硬件完成,进程与线程基本概念,9,进程虚拟地址,用户空间 可执行映象:用户进程本身的程序和数据 进程堆栈:进程运行用户程序时使用的堆栈 进程控制/管理信息:如进程控制块等 系统空间 内核被映射到所有进程的系统空间中 只允许进程在核心态下访问

5、进程只能通过系统调用转换为核心态后,才能访问系统空间,进程与线程基本概念,10,进程虚拟地址结构,以Linux系统为例(共4G空间) 用户空间(0x 0000 0000 0x bfff ffff) 可执行映象 进程运行时堆栈 进程控制信息,如进程控制块 内核空间(0x c000 0000以上) 内核被映射进程内核空间 只允许进程在核心态下访问,进程与线程基本概念,11,进程执行状态和上下文环境,进程上下文 系统提供给进程处于动态变化的运行环境 系统上下文 系统完成自身任务时的运行环境,内核在系统上下文中执行时不会阻塞,进程与线程基本概念,12,主要内容,进程与线程基本概念 Linux进程的描述

6、与管理 Linux内核通用链表 Linux进程的实现机制 Linux线程的实现机制 Linux线程编程技术,13,Linux进程描述符,进程描述符 数据结构:struct task_struct 定义位置:include/linux/sched.h 进程描述符向量结构 数据结构:taskNR_TASKS 定义位置: include/linux/sched.h 定义格式 struct task_struct *taskNR_TASKS = &init_task #define NR_TASKS 512 说明 全局变量NR_TASKS记录系统可容纳进程数,默认大小是512,Linux的进程描述与管

7、理,Linux进程描述符的信息组成,进程状态信息(state, flags, ptrace) 调度信息(static_prio, normal_proi, run_list, array, policy) 内存管理(mm, active_mm) 进程状态位信息(binfmt, exit_state, exit_code, exit_signal) 身份信息(pid, tgid, uid, suid, fsuid, gid, egid, sgid, fsgid) 家族信息(real_parent, parent, children, sibling) 进程耗间信息(realtime, utime

8、, stime, starttime) 时钟信息(it_prof_expires, it_virt_expires, it_sched_expires) 文件系统信息(link_count, fs, files) IPC信息(sysvsem, signal, sighand, blocked, sigmask, pending),14,Linux的进程描述与管理,15,Linux进程描述符与进程资源相关的信息,Linux的进程描述与管理,Linux系统进程系统堆栈结构,每个进程都要单独分配一个系统堆栈 结构组成 内核态的进程堆栈 进程描述符信息(task_struct) 结构特点 8192(

9、213 )字节,两个页框 占据连续两个页框,且第一个页框起始地址为213的倍数,16,Linux的进程描述与管理,17,Linux 2.4进程系统堆栈结构,Linux系统进程个数限制 所有进程的PCB及系统堆栈占用空间1/2的物理内存总和,Linux的进程描述与管理,18,Linux 2.4进程系统堆栈数据结构定义,结构定义(/include/linux/sched.h) 进程描述符管理 分配:alloc_task_struct() 回收:free_task_struct() 访问:get_task_struct(),Linux的进程描述与管理,19,Linux 2.4 当前运行进程描述符的获

10、取,由current来获取当前进程的进程描述符说明 esp寄存器是CPU栈指针,%esp指向内核堆栈,存放栈顶单元地址 8191UL表示最低13位为0,其余位全为1 屏蔽掉%esp最低13位后,得到 “两个连续的物理页面”的开头(基地址),从而得到指向task_struct的指针,8191=8192-1=0x2000-1=0x1fff 取反:0xffffe000(最后13位为0),Linux的进程描述与管理,current在内核堆栈的结构,20,Linux的进程描述与管理,21,Linux 2.6进程系统堆栈结构,进程描述符由slab分配器动态生成 栈底用新结构struct thread_in

11、fo,指向进程描述符,Linux的进程描述与管理,22,thread_info结构定义include/asm-x86/thread_info_32.h thread_info(52个字节),Linux 2.6进程系统堆栈数据结构定义,Linux的进程描述与管理,23,thread_uion定义/include/linux/sched.hTHREAD_SIZE定义include/asm-x86/thread_info_32.h,Linux 2.6进程系统堆栈数据结构定义(续),Linux的进程描述与管理,24,Linux 2.6 当前运行进程描述符的获取,由current来获取当前进程的进程描述

12、符 根据THREAD_SIZE大小,分别屏蔽掉内核栈的12-bit LSB(4K)或13-bit LSB(8K),从而获得内核栈的起始位置对应汇编指令 movl $0xfffe000, %ecx andl %esp, %ecx movl %ecx, p,Linux的进程描述与管理,25,进程标识,成员名:pid_t pid 功能 内核通过pid标识每个进程 pid与进程描述符之间有严格的一一对应关系 数据类型说明 pid_t实际上是一个int类型 取值范围:0 32767 最大值修改:/proc/sys/kernel/pid_max 生成新pid:get_pid() 获取进程pid ps命令

13、访问/proc/pid getpid()sys_getpid(),Linux的进程描述与管理,通过ps命令获取进程信息,功能 查看系统中正在运行的进程 语法 ps -ef-n name-t ttys-p pids-u users-groups,26,运行的命令,用户ID,进程占用CPU的百分比,启动进程的终端号,进程开始的时间和日期,父进程号,进程号,进程已占用的时间,Linux的进程描述与管理,/proc目录下的进程信息,在/proc/pid下 status :进程状态信息 ctl :进程控制文件 psinfo:进程ps 信息 as:进程地址空间 map:进程映射信息 object:进程对象

14、信息 sigact:进程信号量操作 sysent:进程系统调用信息 lwp/tid:进程核心线程标识符目录 lwp/tid/lwpstatus:核心线程状态 lwp/tid/lwpctl:核心线程控制文件 lwp/tid/lwpsinfo:核心线程ps 信息,27,Linux的进程描述与管理,28,进程组标识,成员名:pid_t tgid 功能 标识进程是否属于同组,组ID是第一个组内线程(父进程)的ID 线程组中的所有线程共享相同的PID 组ID赋值相关操作单线程进程:tgid和pid相等 多线程进程:组内所有线程tgid都相等,且等于父进程pid,Linux的进程描述与管理,29,用户相关

15、的进程标识信息,功能:控制用户对系统资源的访问权限 分类 用户标识uid及组标识gid 通常是进程创建者的uid和gid 有效用户标识euid及有效组标识egid 有时系统会赋予一般用户暂时拥有root的uid和gid(作为用户进程的euid和egid),以便于进行运作 备份用户标识suid及备份组标识sgid 让本来没有相应权限的用户运行这个程序时,可以访问没有权限访问的资源 如果被设置SUID或SGID位,分别表现在所有者或同组用户权限的可执行位上,例如 -rwsr-xr-x :SUID和所有者权限中可执行位被设置 -rwSr-r-:SUID被设置,但所有者权限中可执行位未被设置 -rwx

16、r-sr-x:SGID和同组用户权限中可执行位被设置 -rw-r-Sr-:SGID被设置,但同组用户权限可执行位未被设置,Linux的进程描述与管理,30,用户相关的进程标识信息(续),功能:控制用户对系统资源的访问权限 分类 文件系统标识fsuid及文件系统组标识fsgid 检查对文件系统访问权限时使用,通常与euid及egid相等 构建一个类似文件服务器那样供任意用户操作的程序(如NFS服务器),在保持root权限的同时,在以普通用户身份存取文件前只需要改变fsuid和fsgid,而不改变euid及egid,可避免受恶意攻击,Linux的进程描述与管理,31,获取用户相关的进程标识信息代码

17、示例,获取用户相关的进程标识信息的方法:getXXX(),Linux的进程描述与管理,32,进程状态,成员名:volatile long state 功能:表征进程的可运行性 状态定义include/linux/sched.hLinux 24与2.6的状态定义变化 新增两种状态:TASK_TRACED、TASK_DEAD 宏定义数值有较大差别,Linux的进程描述与管理,进程状态,类型定义说明 运行态/就绪态 TASK_RUNNING:正在运行或已处于就绪只等待CPU调度 TASK_TRACED:供调试使用 被挂起状态 TASK_INTERRUPTIBLE:可被信号或中断唤醒进入就绪队列 TA

18、SK_UNINTERRUPTIBLE:等待资源,不可被其他进程中断 TASK_STOPPED:被调试暂停,或收到SIGSTOP等信号 不可运行态 TASK_ZOMBIE:正在终止(已释放内存、文件等资源,但内核数据结构信息未释放),等待父进程通过wait4()或waitpid()回收 TASK_DEAD:已退出且不需父进程回收的进程的状态 说明 调度器主要处理运行/就绪和被挂起两种状态下的进程,33,Linux的进程描述与管理,34,进程的家族关系,所有进程都是init进程(pid=1)的后代 每个进程必须有一个父进程 每个进程可以拥有零个或者多个子进程 具有相同父进程的进程称为兄弟,Linu

19、x的进程描述与管理,35,Linux 2.4进程家族关系结构定义,Linux的进程描述与管理,36,Linux 2.6进程家族关系相关结构定义,Linux的进程描述与管理,37,Linux 2.6进程家族结构图,最年长 子进程,最年幼 子进程,struct list_head,struct list_head,struct list_head,struct task_struct,Linux的进程描述与管理,38,Linux 2.6进程描述符中的进程链表成员,struct list_head run_list 优先级相同的进程组成的进程链表 struct list_head tasks 链接系

20、统中所有进程的链表 struct list_head children 链接所有孩子节点进程的链表 struct list_head sibling 链接所有兄弟节点进程的链表,Linux内核链表中,需要用链表组织起来的数据通常会包含一个struct list_head成员,Linux的进程描述与管理,39,Linux 2.6进程家族信息的访问,获取父进程 struct task_struct *my_parent = current-parent 获取所有子进程(实质是遍历list_head结构),Linux的进程描述与管理,40,主要内容,进程与线程基本概念 Linux进程的描述与管理 L

21、inux内核通用链表 Linux进程的实现机制 Linux线程的实现机制 Linux线程编程技术,41,传统链表数据结构定义模式,基本组成 数据域:存储数据 指针域:建立与下一个节点的联系,单链表,双链表,Linux内核通用链表,42,内核通用链表,通用双向链表结构说明 链表结构作为一个成员嵌入到宿主数据结构内 链表结构可放在宿主结构内的任何位置 一个宿主结构可有多个内核链表结构,通用双向链表优点:避免为每个数据项类型定义自己的链表,Linux内核通用链表,内核通用链表结构关系图,43,含list_head 的定制结构,list_head结构,Linux内核通用链表,44,内核通用链表定义示例

22、,nf_sockopt_ops结构,Linux内核通用链表,45,nf_sockopts链表示意图,链表的头元素没有数据域,Linux内核通用链表,内核通用链表的基本操作,声明与初始化 内核通用链表节点的插入/删除/移动 内核通用链表的遍历与访问 内核通用链表节点合并,46,Linux内核通用链表,47,内核通用链表的声明与初始化,声明时初始化链表 #define LIST_HEAD_INIT(name) INIT_LIST_HEAD(&nf_sockopts),Linux内核通用链表,48,内核通用链表节点的插入,表头插入 static inline void list_add(struct

23、 list_head *new, struct list_head *head); 表尾插入 static inline void list_add_tail(struct list_head *new, struct list_head *head); 说明 Linux内核通用链表是循环表,表头的next、prev分别指向链表第一个和最末一个节点,所以两者区别并不大 举例 将new_sockopt添加到nf_sockopts链表头 list_add(,Linux内核通用链表,49,内核通用链表节点的删除,调用形式 static inline void list_del(struct list

24、_head *entry); 举例: 删除nf_sockopts链表中添加的new_sockopt项 list_del( 说明 删除后new_sockopt.list的prev、next指针分别被设为LIST_POSITION2和LIST_POSITION1,以保证不在节点项不会被访问 对LIST_POSITION1和LIST_POSITION2的访问都将引起页故障,Linux内核通用链表,50,内核通用链表节点的移动,将节点从移动到链表的另一个位置 移动到表头 static inline void list_move(struct list_head *list, struct list_h

25、ead *head); 移动到表尾 static inline void list_move_tail(struct list_head *list, struct list_head *head); 举例 将new_sockopt移动到nf_sockopts的表头 list_move(&new_sockopt.list, &nf_sockopts),Linux内核通用链表,51,内核通用链表的合并,将链表合并到另一个链表 函数原型 static inline void list_splice(struct list_head *list, struct list_head *head); 举

26、例 list_splice(&list1, &list2) 将list1合并到list2,list1被插入到list2和list2.next之间 新list2链表以原list1的第一个节点为首节点,而尾节点不变 但作为原表头的list1的next、 prev仍然指向原来的节点,next指针,Linux内核通用链表,52,内核通用链表的合并(续),改进形式 static inline void list_splice_init(struct list_head *list, struct list_head *head); 将list合并到head链表后,调用INIT_LIST_HEAD(lis

27、t)将list设置为空链,Linux内核通用链表,53,内核通用链表成员的访问,核心问题 内核通用链表仅保存宿主结构项中相应list_head成员变量的地址 如何通过list_head成员访问到作为宿主结构地址,并得到其他成员变量信息 解决途径:list_entry宏定义 list_entry(ptr, type, member),Linux内核通用链表,54,内核通用链表访问方法:list_entry,由list_head链表节点获取对应的宿主结构项指针 list_entry(ptr, type, member) ptr:当前list_head节点的指针(宿主结构项的list_head成员)

28、 type:宿主数据结构类型 member:宿主数据结构中定义的list_head类型变量 举例 访问nf_sockopts链表中首个nf_sockopt_ops变量 struct nf_sockopt_ops *ops = list_entry(nf_sockopts-next, struct nf_sockopt_ops, list);,Linux内核通用链表,55,list_entry的实现机制,list_entry宏定义 #define list_entry(ptr, type, member) container_of(ptr, type, member) container_of宏

29、定义(include/linux/kernel.h) #define container_of(ptr, type, member) ( /将链表中ptr转换成宿主结构type中成员member的类型const typeof( (type *)0)-member ) *_mptr = (ptr); / _mptr减去member成员偏移地址正好是type结构地址(type *)( (char *) _mptr - offsetof(type, member) );),Linux内核通用链表,56,list_entry的实现机制,offsetof宏定义(include/linux/stddef.

30、h) #define offsetof(TYPE, MEMBER) (size_t) &(TYPE *)0)-MEMBER) size_t: 定义为unsigned int(i386) 先求得结构成员在与结构中的偏移量,然后根据成员变量的地址反过来得出属主结构变量的地址 (type *)0)-member: 将0地址强制“转换“为type结构的指针,再访问到type结构中的member成员,Linux内核通用链表,57,offsetof()的宏原理说明,对一给定结构,offsetof(type, member)是一个常量,list_entry()利用该不变的偏移量求得链表数据项的变量地址。,L

31、inux内核通用链表,58,内核通用链表访问方法:list_for_each,遍历整个链表结构的各个list_head成员 list_for_each(pos, head) include/linux/list.h pos: list_head类型结构变量,作为循环变量 head: 链表头节点 利用pos作为循环变量,从表头head开始,逐项向后(next方向)移动pos,直至又回到head 宏定义形式,Linux内核通用链表,59,内核通用链表访问方法:list_for_each_entry,遍历整个链表结构的各个宿主结构项 #define list_for_each_entry(pos,

32、head, member) pos: 宿主数据结构类型变量,而不是list_head类型变量(与list_for_each(pos, head) 的区别) head: 链表头节点 member:宿主数据结构中定义的list_head类型变量 举例,Linux内核通用链表,60,其他内核通用链表遍历方法,反向遍历链表 list_for_each_prev() list_for_each_entry_reverse() 从链表中间开始遍历(已知从某个节点pos开始) list_for_each_entry_continue(pos, head, member) 判断链表是否为空 list_empt

33、y() 仅以头指针的next是否指向自己来判断链表是否为空 list_empty_careful() 同时判断头指针的next和prev,仅当两者都指向自己是才返回真 主要是为了应付另一个CPU正在处理同一链表而造成next, prev不一致情形,Linux内核通用链表,61,Linux 2.6内核通用链表的新特性,HASH链表(hlist) 单指针表头双循环链表 表头仅有一个指向首节点的指针,而没有指向尾节点的指针 在可能是海量的HASH表中存储的表头就能减少一半的空间消耗,Linux内核通用链表,62,hlist与list的区别,Linux内核通用链表,hlist,list_head结构,

34、first,63,hlist的定义,hlist的表头既没有prev,又没有next,只有一个first 为能统一地修改表头的first指针,即表头的first指针必须修改指向新插入的节点,hlist设计了pprev hlist的pprev不再是指向前一个节点的指针,而是指向前一个节点(可能是表头)中的next(对于表头则是first)指针 表头插入的操作可以通过一致的“*(node-pprev)”访问和修改前节点的next(或first)指针,Linux内核通用链表,first,64,hlist相关操作,hlist的常用宏,Linux内核通用链表,65,hlist相关操作,hlist_add_

35、before函数定义(在next前面加入节点n ) Next不能为空,Linux内核通用链表,/修改n的ppre,/修改n的next,/修改下一个节点的ppre,指向前一个节点的next,/修改前一个节点的ppre,对于头节点,*(n-pprev)即为first,first,next,n,hlist与list在遍历上的差别,如果使用其宿主结构的指针进行遍历,则hlist与list也略有不同 hlist在遍历时需要一个指向hlist_node的临时指针 该指针的引入,一是为了遍历,更主要的目的在于判断结束 因为hlist最后一个节点的next为NULL,只有hlist_node指向NULL时才算

36、结束,而这个NULL不包含在任何寄生结构内,不能通过tpos-member的方式访问到,故必须引入临时变量pos list的遍历在list_entry的参数中实现,66,Linux内核通用链表,hlist与list在遍历上的差别(续),67,#define list_for_each_entry(tpos, head, member) for (tpos = list_entry(head)-next, typeof(*tpos), member); prefetch(tpos-member.next), pos = pos-next),Linux内核通用链表,68,RCU操作保护的链表,读拷

37、贝更新(rcu) 以“_rcu”结尾的宏 通过延迟写操作来提高同步性能 RCU常用来保护读操作占多数的链表与数组 读者 不需要获得任何锁即可访问被RCU保护的共享数据 写者 访问时首先拷贝一个副本,然后对副本修改 最后通过回调机制,在适当时机把指向原来数据的指针重新指向新的被修改的数据,Linux内核通用链表,69,RCU操作保护的链表,RCU与rwlock的区别 RCU既允许多个读者同时访问被保护的数据,又允许多个读者和多个写者同时访问被保护的数据 读者没有任何同步开销,而写者的同步开销则取决于使用的写者间同步机制 但RCU不能替代rwlock,因为如果写比较多时,对读者的性能提高不能弥补写

38、者导致的损失,Linux内核通用链表,70,主要内容,进程与线程基本概念 Linux进程的描述与管理 Linux内核通用链表 Linux进程的实现机制 Linux线程的实现机制 Linux线程编程技术,Linux系统启动过程,打开PC电源 BIOS开机自检 按BIOS中设置的启动设备(通常是硬盘)启动 启动设备上安装的引导程序lilo或grub开始引导Linux 内核引导 如果有多个CPU,则先启用一个CPU来启动内核 执行init程序 启动getty,打开终端供用户登录系统 用户登录成功后,进入Shell环境,71,Linux进程的实现机制,72,Linux进程系统启动过程,0号进程,1号进

39、程,1号进程,内核态,用户态,shell1,getty2,gettyn,用户进程1,子进程1,用户进程2,子进程2,bdflush,dpiod,kswapd,执行init()函数1)初始化内核并进行系统配置2)创建若干个用于高速缓存和虚拟主存管理的内核线程3)调用execve( )从文件/etc/inittab中装入可执行程序init( ),0号进程:系统自举过程,实质为内核本身。通过int 0x80系统调用创建1号内核线程1号内核线程:不可使用虚拟存储空间,但可直接使用物理地址空间 判断:比较ESP和ESI。值相等为0号进程,否则为1号内核线程,首个用户进程(init进程)1)按照配置文件/

40、etc/initab的要求,完成系统启动工作2)创建编号若干个终端注册进程getty,注意:没有使用fork()!,Linux进程的实现机制,73,Linux进程系统启动过程说明,Linux的0号进程 称为0号进程或idle进程或swapper进程 维护Linux内核代码段、数据段及堆栈 是唯一一个通过静态分配创建的进程arch/x86/kernel/init_task.c INIT_TASK完成init_task变量中进程描述符初始化 INIT_THREAD_INFO完成对init_thread_info中thread_info及内核堆栈的初始化 INIT_MM, INIT_FS, INIT

41、_FILES, INIT_SIGNALS, INIT_SIGHAND等宏初始化进程描述符的各个对应域 主内核页全局目录存放在swapper_pg_dir变量中 start_kernel函数(/init/main.c)初始化内核所需所有数据结构、激活中断、创建1号内核线程,Linux进程的实现机制,74,Linux进程系统启动过程说明,Linux的1号内核进程 又称为init进程(PID=1)深入Linux内核架构 由0号进程在start_kernel函数中调用rest_init创建调度程序选择到init进程时,init进程开始执行init()函数 创建init进程后,进程0执行cpu_idle

42、()函数 该函数本质上是在开中断的情况下重复执行hlt汇编语言指令,只有没有其他进程处于TASK_RUNNING状态下才执行进程0,Linux进程的实现机制,75,Linux进程系统启动过程说明,init()函数说明 实现从内核态到用户态的切换 为常规内核任务初始化一些必要的内核线程 kflushd:刷新脏缓冲区中内容到磁盘,归还内存 kswapd:执行内存回收功能 调用系统调用execve()装入可执行程序init init内核线程变成一个普通进程(拥有自己的进程内核数据结构) 但init进程从不终止,因为它创建和监控操作系统外层的所有进程的活动,Linux进程的实现机制,Linux用户空间

43、的初始化流程,76,Linux进程的实现机制,77,Linux进程创建特点,传统UNIX操作系统 子进程复制父进程所拥有的资源 缺点 创建过程慢、效率低、子进程复制的很多资源不会被使用 现代UNIX内核:引入三种机制优化进程创建效率 写时复制技术(Copy-On-Writing) 轻量级进程 允许父子进程共享页表、打开文件列表、信号处理等数据结构 但每个进程应该有自己的程序计数器、寄存器集合、核心栈和用户栈 vfork() 新进程可共享父进程的内存地址空间,Linux进程的实现机制,78,Linux进程创建方法,在终端输入命令,由shell进程创建一个新进程 进程创建函数 pid_t fork

44、(void); pid_t vfork(void); int clone(int (*fn)(void * arg), void *stack, int flags, void * arg); 创建轻量级线程 三函数都调用同一内核函数do_fork( ) /kernel/fork.c,Linux进程的实现机制,79,do_fork()内核函数原型,函数调用形式 do_fork(unsigned long clone_flag, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size, int _user

45、 *parent_tidptr, int _user *child_tidptr); 参数说明 clone_flag:子进程创建相关标志 stact_start:将用户态堆栈指针赋给子进程的esp regs:指向通用寄存器值的指针 stack_size:未使用(总设为0) parent_tidptr:父进程的用户态变量地址,若需父进程与新轻量级进程有相同PID,则需设置CLONE_PARENT_SETTID child_tidptr:新轻量级进程的用户态变量地址,若需让新进程具有同类进程的PID ,需设置CLONE_CHILD_SETTID,Linux进程的实现机制,80,CLONE参数标志说

46、明,Linux进程的实现机制,CLONE参数标志分类,资源共享(段、页、打开文件共享) CLONE_VM:页表(不是页) CLONE_FILES:打开文件 CLONE_SETTLS:建一个新tls段 路径和权限设置: CLONE_FS: 共享根目录、当前目录、创建文件初始权限 CLONE_NEWNS: 新的根路径,从自己视野看文件系统 线程通信 CLONE_SIGHAND:信号处理action, 阻塞和悬挂的信号 CLONE_SYSVSEM:共享undoable信号量操作,81,Linux进程的实现机制,CLONE参数标志分类,进程关系 CLONE_PARENT:同父,创建进程与新进程是兄弟

47、,新进程不是创建进程的子进程 CLONE_THREAD:两线程属于同一个进程(线程组) CLONE_PTRACE:都被trace CLONE_UNTRACE:子进程不被trace (内核设置,覆盖clone_ptrace)返回用户态变量地址tid CLONE_PARENT_SETTID:向父进程返回tid CLONE_CHILD_SETTID:向子进程返回tid 子进程的状态 CLONE_STOPPED:子进程开始就stop,82,Linux进程的实现机制,进程创建的主要任务,创建PID,分配内核堆栈 创建进程控制块task_struct 进程ID信息(线程组ID、进程组ID、会话ID) 进程

48、家族关系 进程资源及资源限制 进程状态 父子进程之间的通信 赋给子进程的资源 子进程的栈(父进程alloc的内存地址) 线程局部存储段(tls) 返回子进程tid的地址 父进程用户空间内的地址 子进程用户空间的地址 进程调度,83,Linux进程的实现机制,84,do_fork()处理流程,为新进程分配pid 检查clone_ptrace位 确定子进程是否确实要被trace,而不是参数所称被trace 如果等于0,说明父进程正在被跟踪,do_fork()需要检查debugger程序是否想跟踪子进程 该情形下,如果子进程不是内核线程, do_fork()将设置CLONE_PTRACE标志 调用c

49、opy_process() 创建进程描述符 分配进程所需资源,Linux进程的实现机制,85,do_fork()处理流程,设置子进程的状态 挂信号 若设置CLONE_STOP或CLONE_PTRACE状态,将子进程设成CLONE_STOP, 悬挂一个SIGSTOP信号 只有debugger发出SIGCONT信号后才能进入运行状态 设状态 如果有clone_stopped位, 子进程设为stopped状态; 否则调用wake_up_new_task(), 把子进程加入就绪列队 调整父进程和子进程的调度参数,入列队 如果父子在同一CPU上运行, 且页表不同享(CLONE_VM=0), 子进程在插在

50、父进程前 如果不同CPU上运行, 或者共享页表, 子进程放在列队最后,Linux进程的实现机制,86,do_fork()处理流程,设置子进程的状态如果父进程处于被调试状态, 将子进程的PID存入current的ptrace_message,调用ptrace_notify() 当前进程向debugger进程发SIGCHLD信号,告知自己创建了子进程,并停止自己(进入traced状态), 使debugger运行 debugger可以通过查找current-ptrace_message字段获得子进程的PID debugger发信号, 使父进程继续后, 再进行下一步; 否则父进程一直处于traced状态,

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报