第一次作业:深入源码分析进程模型
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第一次作业:深入源码分析进程模型相关的知识,希望对你有一定的参考价值。
1.作业内容
挑选一个开源的操作系统,深入源码分析其进程模型,具体包含如下内容:
- 操作系统是怎么组织进程的
- 进程状态如何转换(给出进程状态转换图)
- 进程是如何调度的
- 谈谈自己对该操作系统进程模型的看法
2.进程描述
Linux下的进程结构
Linux系统是一个多进程的系统,它的进程之间具有并行性、互不干扰等特点。
Linux中的进程中包含3个段,分别为“数据段”、 “代码段”和“堆栈段
“数据段”存放的是全局变量、常数以及动态数据分配的数据空间根据存放的数据,数据段又可以分成普通数据段(包括可读可写/只读数据段,存放静态初始化的全局变量或常量)、BSS数据段(存放未初始化的全局变量) 以及堆(存放动态分配的数据)
“代码段”存放的是程序代码的数据。
“堆栈段”存放的是子程序的返回地址、子程序的参数以及程序的局部变量。如下图所示。
Linux系统是一个多进程的系统,它的进程之间具有并行性、互不干扰等特点。
Linux中的进程中包含3个段,分别为“数据段”、 “代码段”和“堆栈段
“数据段”存放的是全局变量、常数以及动态数据分配的数据空间根据存放的数据,数据段又可以分成普通数据段(包括可读可写/只读数据段,存放静态初始化的全局变量或常量)、BSS数据段(存放未初始化的全局变量) 以及堆(存放动态分配的数据)
“代码段”存放的是程序代码的数据。
“堆栈段”存放的是子程序的返回地址、子程序的参数以及程序的局部变量。如下图所示。
在Linux系统中,进程的执行模式分为用户模式和内核模式。如果当前运行的是用户程序、应用程序或内核程序之外的系统程序,那么对应程序就在用户模式下进行; 如果在用户程序的执行过程中出现系统调用或者发生中断事件,那么就要运行操作系统(内核) 程序,进程模式就变成内核模式。在内核模式下运行的程序可以执行机器的特权指令, 而且此时运行的程序不受用户的干扰,即使是root 用户也不能干扰内核模式下进程的运行。
用户进程可以在用户模式下运行,也可以在内核模式下运行,如下图所示
进程表中的task(0)和task(1)在Linux[ 中是特别的进程。task(0)即init_task,是系统启动时第一个产生的进程,扮演个特殊的角色。task(1)即PID为1的进程,是Linux系统中第一个“真正的”进程。因为它通常执行init程序,所以也成为init进程。这两个进程在核心中需要一直存在,因此它们的进程号(0和1)只负责“无主的”系统时间的使用,即空转进程。因此在调度等过程中需1描进程表链,它通常被跳过。
在Linux中进程又称为任务。进程表是由NR_TASKS个task结构组成的静态数组:
struct task_struct *task[NR_TASKS] ;
每个进程在进程表中占有一项(一个task_struct 结构)。在该表中,所有进程通过一个双向环形链相连,链头由外部变量init_task指定:
struct task_struct ini t_task;
init.task对应于系统的第一个任务INIT TASK,它在进程表链被扫描时通常被跳过。当前进程由变量current 指定:
struct task_struct current;
必运行态: 该任务是“活的”,而且正在非特权的用户方式下运行。
必中断处理状态:当硬件发出一一个中断处理信号时(可能是用户键入一
个字符,或时钟每隔10ms发出一次中断等),中断处理例程被激活
必系统调用期间: 系统调用通过软件中断启动。一个系统调用可能将相
应程序进程挂起等待- 一个事件。
必等待态: 进程正等待- 一个外部事件。只有在这个事件发生后,进程才
能继续工作。
必从系统调用返回: 每个系统调用后自动进入该状态,每个慢中断处理
完毕后也自动进入该状态。在该状态下,要查看是否需要调用程序是否有信号要处理。调用程序可能将该进程切换为就绪态,同时激活另一进程。
必就绪态:.处于此状态的进程正在竞争处理机,但处理机正在被另- 一个
进程占用。
amd: 自动安装NFS (网络文件系统) 守侯进程。apmd: 高级电源管理。
Arpwatch: 记录日志并构建一个在LAN接口上看到的以太网地址和IP地址对数据库。 Autofs: 自动安装管理进程automount,与NFS相关
依赖于NIS。
Bootparamd:引导参数服务器,为LAN 上的无盘工作站提供引导所需的相关信息,用于无盘客户端,通常都不需要。crond: Linux 下的计划任务。
Dhcpd: 启动- 一个DHCP (动态IP地址分配) 服务器。
Gated: 网关路由守候进程,使用动态的OSPF路由选择协议。Httpd: WEB 服务器。
Inetd: 支持多种网络服务的核心守候程序。Innd: Usenet 新闻服务器。
Linuxconf: 允许使用本地WEB服务器作为用户接口来配置机器。Lpd: 打印服务器。
M ars-nwe : Netware 文件和打印服务器。Mcserv: Midnight命令文件服务器。nam ed : DNS服务器。
netfs: 安装NFS、Samba和NetWare网络文件系统。network: 激活已配置网络接[ 1的脚本程序。nfs: 打开NFS服务。
nscd: nscd(Name Switch Cache daemon) 服务器,用于NIS的一个支持服务,它高速缓存用户口令和组成成员关系。
portmap: RPC portmap管理器,与inet d 类似,它管理基于RPC服务的连接。postgresql :一种SQL数据库服务器。
routed: 路由守候进程, 使用动态RIP路由选择协议。
rstatd: 个为LAN 上的其它机器收集和提供系统信息的守候程序。
ruserd: 远程用户定位服务,这;是一一个基于RPC的服务,它提供关于当前记录到LAN上一个机器日志中的用户信息。
rwalld: 激活rpc.rwal 1服务进程,这是- 一项基于RPC的服务,允许用户给每个主册到LAN机器上的其他终端写消息。
rwhod: 激活rwhod服务进程,它支持LAN的rwho 和rupt ime服务。
sendmai 1: 邮件服务器sendmai 1,如果不需要接收或转发电子邮件应关闭,此时仍可发送电子邮件。
sound: 保存声卡设置。
在Linux中进程又称为任务。进程表是由NR_TASKS个task结构组成的静态数组:
struct task_struct *task[NR_TASKS] ;
每个进程在进程表中占有一项(一个task_struct 结构)。在该表中,所有进程通过一个双向环形链相连,链头由外部变量init_task指定:
struct task_struct ini t_task;
init.task对应于系统的第一个任务INIT TASK,它在进程表链被扫描时通常被跳过。当前进程由变量current 指定:
struct task_struct current;
必运行态: 该任务是“活的”,而且正在非特权的用户方式下运行。
必中断处理状态:当硬件发出一一个中断处理信号时(可能是用户键入一
个字符,或时钟每隔10ms发出一次中断等),中断处理例程被激活
必系统调用期间: 系统调用通过软件中断启动。一个系统调用可能将相
应程序进程挂起等待- 一个事件。
必等待态: 进程正等待- 一个外部事件。只有在这个事件发生后,进程才
能继续工作。
必从系统调用返回: 每个系统调用后自动进入该状态,每个慢中断处理
完毕后也自动进入该状态。在该状态下,要查看是否需要调用程序是否有信号要处理。调用程序可能将该进程切换为就绪态,同时激活另一进程。
必就绪态:.处于此状态的进程正在竞争处理机,但处理机正在被另- 一个
进程占用。
amd: 自动安装NFS (网络文件系统) 守侯进程。apmd: 高级电源管理。
Arpwatch: 记录日志并构建一个在LAN接口上看到的以太网地址和IP地址对数据库。 Autofs: 自动安装管理进程automount,与NFS相关
依赖于NIS。
Bootparamd:引导参数服务器,为LAN 上的无盘工作站提供引导所需的相关信息,用于无盘客户端,通常都不需要。crond: Linux 下的计划任务。
Dhcpd: 启动- 一个DHCP (动态IP地址分配) 服务器。
Gated: 网关路由守候进程,使用动态的OSPF路由选择协议。Httpd: WEB 服务器。
Inetd: 支持多种网络服务的核心守候程序。Innd: Usenet 新闻服务器。
Linuxconf: 允许使用本地WEB服务器作为用户接口来配置机器。Lpd: 打印服务器。
M ars-nwe : Netware 文件和打印服务器。Mcserv: Midnight命令文件服务器。nam ed : DNS服务器。
netfs: 安装NFS、Samba和NetWare网络文件系统。network: 激活已配置网络接[ 1的脚本程序。nfs: 打开NFS服务。
nscd: nscd(Name Switch Cache daemon) 服务器,用于NIS的一个支持服务,它高速缓存用户口令和组成成员关系。
portmap: RPC portmap管理器,与inet d 类似,它管理基于RPC服务的连接。postgresql :一种SQL数据库服务器。
routed: 路由守候进程, 使用动态RIP路由选择协议。
rstatd: 个为LAN 上的其它机器收集和提供系统信息的守候程序。
ruserd: 远程用户定位服务,这;是一一个基于RPC的服务,它提供关于当前记录到LAN上一个机器日志中的用户信息。
rwalld: 激活rpc.rwal 1服务进程,这是- 一项基于RPC的服务,允许用户给每个主册到LAN机器上的其他终端写消息。
rwhod: 激活rwhod服务进程,它支持LAN的rwho 和rupt ime服务。
sendmai 1: 邮件服务器sendmai 1,如果不需要接收或转发电子邮件应关闭,此时仍可发送电子邮件。
sound: 保存声卡设置。
smb: Samba 文件共享/打印服务。snmpd : 本地简单网络管理候进程。squid: 激活代理服务器squid。
syslog: 一个让系统引导时起动syslog和klogd系统E 志守候进程的脚本。
xfs: X Window字型服务器,为本地和远程X服务器提供字型集。xntpd: 网络时间服务器。
ypbind: 为NIS (网络信息系统) 客户机激活ypbind服务进程,如果系统运行NIS服务器,则必需此服务。
yppasswdd: NIS[ ]令服务器,如果系统运行NIS服务器,则必需此服务。
ypserv: NIS.主服务器。gpm : 鼠标的管理。
identd: AUTH服务,在提供用户信息方面与finger 类似。
syslog: 一个让系统引导时起动syslog和klogd系统E 志守候进程的脚本。
xfs: X Window字型服务器,为本地和远程X服务器提供字型集。xntpd: 网络时间服务器。
ypbind: 为NIS (网络信息系统) 客户机激活ypbind服务进程,如果系统运行NIS服务器,则必需此服务。
yppasswdd: NIS[ ]令服务器,如果系统运行NIS服务器,则必需此服务。
ypserv: NIS.主服务器。gpm : 鼠标的管理。
identd: AUTH服务,在提供用户信息方面与finger 类似。
1.慢中断处理最初保存现场时,需要保存所有寄存器的值。而快中断处理最初保存现场时,只需要保存那些被常规C函数修改的寄存器的值(但如果在中断处理过程用到汇编代码,则其余寄存器也必须保存和恢复)。
2.在慢中断处理时,通常不屏蔽其他中断。而快中断处理时,通常要屏蔽其他所有中断(除特别说明)
3.慢中断处理完毕后,通常不立即返回被中断的进程,而是调用程序
调用程序的调度结果不一定是被中断的进程,因此这种情况称为抢先式调度或抢先式中断。而快中断处理完毕后,通常恢复现场返回被中断的进程继续执行,称为非抢占式调度或非抢占式中断。慢中断是最常见的中断。而快中断则通常用于短时间的、不太复杂的中断处理任务。
每一个普通进程都有一个静态优先级。这个值会被调度器用来与作为参考来调度进程。在内核中调度的优先级的区间为[100,139],数字越低,优先级越高。一个新的进程总是从它的父进程继承此值。从进程管理篇(一)中了解到每个进程都有分配时间片。那么时间是如何分配的呢? 时间片的计算公式如下(摘自《UnderstandingThe LinuxKernel Version3》) :
base time q anti m | 140- static prionity) x 20 it staticprioritys 120 (1)
(in mill iseconds) l( 140-static priority)x 5if static priority2 120
由此可见时间片的分配只有静态优先级相关。静态优先级越高,时间片分配的越多。系统在实际的调度过程中不仅要考虑静态优先级,也要考虑进程的属性。
系统调度时,会计算进程的动态优先级。系统会严格按照动态优先级高低的顺序安排进程执行。动态优先级高的进程进入非运行状态
或者时间片消耗完毕才会轮到动态优先级较低的进程执行。动态优先级的计算主要考虑两个因素: 静态优先级,进程的平均睡眠时间也即bonus。计算公式如下: 动态优先级= max (100,min( 静态优先级一奖励+ 5,139))。
每一个实时进程都会与一个实时优先级相关联。实时优先级在1到99之间。不同与普通进程,系统调度时,实时优先级高的进程总是先于优先级低的进程执行。知道实时优先级高的实时进程无法执行。实时进程总是被认为处于活动状态 如果有数个优先级相同的实时进程,那么系统就会按照进程出现在队列上的顺序选择进程。假设当前CPU运行的实时进程A的优先级为a, 而此时有个优先级为b的实时进程B进入可运行状态,那么只要b<a,系统将中断A的执行,而优先执行B,直到B无法执行(无论A,B为何种实时进程)。不同调度策略的实时进程只有在相同优先级时才有可比性:意味着只有当前进程执行完毕才会轮到其他进对于FIFO的进程,程执行。由此可见相当霸道。对于RR的进程。一旦时间片消耗完毕,则会将该进程置于队列的然后运行其他相同优先级的进程,如果没有其他相同优先级的未尾,进程,则该进程会继续执行。
3.代码段:
得到PID和PPID:
int main(void) { printf("pid = %d\\n", getpid()); printf("ppid = %d\\n", getppid()); return EXIT_SUCCESS; }
得到登陆的用户号、用户名,宿主目录信息:
char *login = getlogin(); struct passwd *ps = getpwnam(login); printf("user name = %s\\n",ps->pw_name); printf("uid = %d\\n",ps->pw_uid); printf("home dir = %s\\n",ps->pw_dir);
fork 函数:
int main(void) { printf("begin\\n"); pid_t child = fork(); if(child == -1) { return -1; } if(child == 0) { printf("is child\\n"); }else { printf("is parent\\n"); } printf("end\\n"); return EXIT_SUCCESS; }
进程描述符
就是用于描述一个进程的结构体,每个进程有且只有一个进程描述符,它里面包含了这个进程相关的所有信息。
struct task_struct { ...... /* 进程状态 */ volatile long state; /* 指向内核栈 */ void *stack; /* 用于加入进程链表 */ struct list_head tasks; ...... /* 指向该进程的内存区描述符 */ struct mm_struct *mm, *active_mm; ........ /* 进程ID,每个进程(线程)的PID都不同 */ pid_t pid; /* 线程组ID,同一个线程组拥有相同的pid,与领头线程(该组中第一个轻量级进程)pid一致,保存在tgid中,线程组领头线程的pid和tgid相同 */ pid_t tgid; /* 用于连接到PID、TGID、PGRP、SESSION哈希表 */ struct pid_link pids[PIDTYPE_MAX]; ........ /* 指向创建其的父进程,如果其父进程不存在,则指向init进程 */ struct task_struct __rcu *real_parent; /* 指向当前的父进程,通常与real_parent一致 */ struct task_struct __rcu *parent; /* 子进程链表 */ struct list_head children; /* 兄弟进程链表 */ struct list_head sibling; /* 线程组领头线程指针 */ struct task_struct *group_leader; /* 在进程切换时保存硬件上下文(硬件上下文一共保存在2个地方: thread_struct(保存大部分CPU寄存器值,包括内核态堆栈栈顶地址和IO许可权限位),内核栈(保存eax,ebx,ecx,edx等通用寄存器值)) */ struct thread_struct thread; /* 当前目录 */ struct fs_struct *fs; /* 指向文件描述符,该进程所有打开的文件会在这里面的一个指针数组里 */ struct files_struct *files; ........ /* 信号描述符,用于跟踪共享挂起信号队列,被属于同一线程组的所有进程共享,也就是同一线程组的线程此指针指向同一个信号描述符 */ struct signal_struct *signal; /* 信号处理函数描述符 */ struct sighand_struct *sighand; /* sigset_t是一个位数组,每种信号对应一个位,linux中信号最大数是64 * blocked: 被阻塞信号掩码 * real_blocked: 被阻塞信号的临时掩码 */ sigset_t blocked, real_blocked; sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */ /* 私有挂起信号队列 */ struct sigpending pending; ........ }
组织进程
所有处于TASK_RUNNING状态的进程都会被放入CPU的运行队列,它们有可能在不同CPU的运行队列中。 系统没有为TASK_STOPED、EXIT_ZOMBIE和EXIT_DEAD状态的进程建立专门的链表,因为处于这些状态的进程访问比较简单,可通过PID和通过特定父进程的子进程链表进行访问。所有TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE都会被放入相应的等待队列,系统中有很多种等待队列,有些是等待磁盘操作的终止,有些是等待释放系统资源,有些是等待时间经过固定的间隔,每个等待队列它的唤醒条件不同,比如等待队列1是等待系统释放资源A的,等待队列2是等待系统释放资源B的。因此,等待队列表示一组睡眠进程,当某一条件为真时,由内核唤醒这条等待队列上的进程。我们看看内核中一个简单的sleep_on()函数:
/* wq为某个等待队列的队列头 */ void sleep_on (wait_queue_head_t *wq) { /* 声明一个等待队列结点 */ wait_queue_t wait; /* 用当前进程初始化这个等待队列结点 */ init_waitqueue_entry (&wait, current); /* 设置当前进程状态为TASK_UNINTERRUPTIBLE */ current->state = TASK_UNINTERRUPTIBLE; /* 将这个代表着当前进程的等待队列结点加入到wq这个等待队列 */ add_wait_queue (wq, &wait); /* 请求调度器进行调度,执行完schedule后进程会被移除CPU运行队列,只有等待队列唤醒后才会重新回到CPU运行队列 */ schedule (); /* 这里进程已经被等待队列唤醒,重新移到CPU运行队列,也就是等待的条件已经为真,唤醒后第一件事就是将自己从等待队列wq中移除 */ remove_wait_queue (wq, &wait); }
以上是关于第一次作业:深入源码分析进程模型的主要内容,如果未能解决你的问题,请参考以下文章