5.5 UNIX实现5.5.1 进程上下文UNIX主要使用两个结构:proc和user来保存进程上下文,分别在proc.h和user.h中定义。1.proc结构/* * 每一个活动的进程分配一个proc结构。 * 它包含了进程被换出时所有需要的数据。 * 其他进程相关的换出数据在user.h中。 *//* * One structure allocated per active * process. It contains all data needed * about the process while the * process may be swapped out. * Other per process data (user.h) * is swapped with the process.*/struct proc{ char p_stat; char p_flag; char p_pri; /* priority, negative is high */ char p_sig; /* signal number sent to this process */ char p_uid; /* user id, used to direct tty signals */ char p_time; /* resident time for scheduling */ char p_cpu; /* cpu usage for scheduling */ char p_nice; /* nice for scheduling */ int p_ttyp; /* controlling tty */ int p_pid; /* unique process id */ int p_ppid; /* process id of parent */ int p_addr; /* address of swappable image */ int p_size; /* size of swappable image */ int p_wchan; /* event process is awaiting */ int *p_textp; /* pointer to text structure */} proc[NPROC]; /* stat codes */#define SSLEEP 1 /* sleeping on high priority */#define SWAIT 2 /* sleeping on low priority */#define SRUN 3 /* running */#define SIDL 4 /* intermediate state in process creation */#define SZOMB 5 /* intermediate state in process termination */#define SSTOP 6 /* process being traced */ /* flag codes */#define SLOAD 01 /* in core */#define SSYS 02 /* scheduling process */#define SLOCK 04 /* process cannot be swapped */#define SSWAP 010 /* process is being swapped out */#define STRC 020 /* process is being traced */#define SWTED 040 /* another tracing flag */注意在定义结构的同时还定义了结构数组变量proc[NPROC],它存放了所有进程的proc上下文。proc结构又称为进程控制块PCB(Process Control Block),它记录了进程相关的很多信息,包括优先级、驻留时间、进程ID、交换区地址和程序段信息等,它主要用于进程调度和同步。NPROC在param.h中定义,为64,这样系统最多支持64个进程。p_stat字段表示进程当前的状态,它包括下列6种状态:(1)SSLEEP表示进程处在睡眠或者等待资源状态,并且可以被signal打断;(2)SWAIT表示进程处在睡眠或者等待资源状态,但不可以被signal打断;(3)SRUN表示进程正在运行;(4)SIDL表示进程正在被创建。通常用于fork函数创建子进程时,父进程状态;(5)SZOMB 在进程退出时的中间状态;(6)SSTOP表示进程处于单步SSTOP状态。p_flag字段是进程标志字段,也可以理解为属性。SLOAD表示进程已经被加载到内存。SSYS表示进程为系统进程(事实上是调度进程,因为系统进程只有它一个)。SLOCK表示进程已经被锁定,不能够被交换出去到磁盘。一般当进程进行I/O操作时,比如读写文件,该标志会被设置,当I/O操作完成时,该标志被清除。SSWAP表示进程正在或者已经被换出到磁盘上。SWTED用于单步调试。p_pri是进程优先级。数值越小优先级越高,可以是负值。该值是动态的,时钟中断函数会对该值周期性地调整以保证各个进程都能够得到调度执行。p_psig表示进程收到的信号,0表示没有信号。p_uid表示进程的用户id。p_time表示进程自从创建或上次换入到内存以来,驻留的时间长度,单位是秒,最大127。这里我们可以称之为进程的“年龄”。该值越大,表示进程越“老”。p_cpu记录进程运行(占用cpu)的近似时间长度,单位是tick(滴答)。参与优先级的计算。该值在时钟中断中动态调整。p_nice也是用来计算优先级的一个参数。p_ttyp标识进程所用的终端类型。事实上它是一个指针,指向结构tty(在tty.h中定义)。p_pid是进程唯一ID标识。p_ppid是父进程ID。p_addr指向进程交换数据地址。它事实上是交换数据在内存中的起始物理块号(进程被加载SLOAD)或者在磁盘上的起始扇区号(进程被换出SSWAP)。p_size是进程交换数据区大小。它是指物理块数,所以字节数是p_size*64。p_wchan指示当前进程所等待的全局锁变量。这也是UNIX的一大特点,在多个进程同时访问资源时,使用全局锁变量对资源保护和管理。p_textp指向进程的正文(程序)区,事实上是text *,text结构在text.h中定义:/* * Text结构,每个程序段分配一个(但fork创建的 * 父子进程会共享)。它的操作函数在text.c中。*//* * Text structure. * One allocated per pure * procedure on swap device. * Manipulated by text.c */ struct text { int x_daddr; /* disk address of segment */ int x_caddr; /* core address, if loaded */ int x_size; /* size (*64) */ int *x_iptr; /* inode of prototype */ char x_count; /* reference count */ char x_ccount; /* number of loaded references */ } text[NTEXT];x_daddr是该程序在磁盘上交换区的起始扇区号。x_caddr是该程序加载到内存中的起始物理块号。x_size是程序所占内存物理块数。x_iptr指向inode结构的指针,它指向程序在磁盘上的源可执行文件。x_count是引用该程序的进程个数,不管该程序是否被加载到内存中。x_ccount是当程序被加载到内存中后,引用该程序的进程个数。最后两个字段也比较好理解,比如:同时打开2个shell终端窗口,那么它们是2个独立的进程,有各自的数据和栈段,但是共享程序段,或者说是同一个可执行文件的2个不同实例。这时,x_count=x_ccount=2。显然我们可以很容易得出下面的结论:只要进程没有退出,那么必然有x_count>=1;当进程被换入(加载)到内存中时,x_ccount>=1。2.文件param.h /* * tunable variables */ #define NBUF 15 /* size of buffer cache */#define NINODE 100 /* number of in core inodes */#define NFILE 100 /* number of in core file structures */#define NMOUNT 5 /* number of mountable file systems */#define NEXEC 3 /* number of simultaneous exec's */#define MAXMEM (64*32) /* max core per process - first # is Kw */#define SSIZE 20 /* initial stack size (*64 bytes) */#define SINCR 20 /* increment of stack (*64 bytes) */#define NOFILE 15 /* max open files per process */#define CANBSIZ 256 /* max size of typewriter line */#define CMAPSIZ 100 /* size of core allocation area */#define SMAPSIZ 100 /* size of swap allocation area */#define NCALL 20 /* max simultaneous time callouts */#define NPROC 50 /* max number of processes */#define NTEXT 40 /* max number of pure texts */#define NCLIST 100 /* max total clist size */#define HZ 60 /* Ticks/second of the clock */ /* * priorities * probably should not be * altered too much */ #define PSWP -100#define PINOD -90#define PRIBIO -50#define PPIPE 1#define PWAIT 40#define PSLEP 90#define PUSER 100 /* * signals * dont change */ #define NSIG 20#define SIGHUP 1 /* hangup */#define SIGINT 2 /* interrupt (rubout) */#define SIGQIT 3 /* quit (FS) */#define SIGINS 4 /* illegal instruction */#define SIGTRC 5 /* trace or breakpoint */#define SIGIOT 6 /* iot */#define SIGEMT 7 /* emt */#define SIGFPT 8 /* floating exception */#define SIGKIL 9 /* kill */#define SIGBUS 10 /* bus error */#define SIGSEG 11 /* segmentation violation */#define SIGSYS 12 /* sys */#define SIGPIPE 13 /* end of pipe */ /* * fundamental constants * cannot be changed */ #define USIZE 16 /* size of user block (*64) */#define NULL 0#define NODEV (-1)#define ROOTINO 1 /* i number of all roots */#define DIRSIZ 14 /* max characters per directory */ /* * structure to access an * integer in bytes */struct{ char lobyte; char hibyte;}; /* * structure to access an integer */struct{ int integ;}; /* * Certain processor registers */#define PS 0177776#define KL 0177560#define SW 0177570文件param.h定义了内核使用的很多宏和两个无名结构,这些宏包括各种信号、进程访问各种资源时的挂起优先级等,第一个无名结构主要用来访问一个字所表示的主从设备号。3.user结构/* * user结构定义,每个进程分配一个。 * 它包含了进程相关数据,但在 * 换出后不需要被访问(进程被换出 * 后只有proc中数据会被访问)。 * user块大小总共USIZE *64字节。 * 驻留在内核虚拟地址0o140000处, * 包含了进程内核栈,该地址被进程的proc结构 * 交叉引用。 *//* * The user structure. * One allocated per process. * Contains all per process data * that doesn’t need to be referenced * while the process is swapped. * The user block is USIZE*64 bytes * long; resides at virtual kernel * loc 140000; contains the system * stack per user; is cross referenced * with the proc structure for the * same process. */ struct user { int u_rsav[2]; /* save r5,r6 when exchanging stacks */ int u_fsav[25]; /* save fp registers */ /* rsav and fsav must be first in structure */ char u_segflg; /* flag for IO; user or kernel space */ char u_error; /* return error code */ char u_uid; /* effective user id */ char u_gid; /* effective group id */ char u_ruid; /* real user id */ char u_rgid; /* real group id */ int u_procp; /* pointer to proc structure */ char *u_base; /* base address for IO */ char *u_count; /* bytes remaining for IO */ char *u_offset[2]; /* offset in file for IO */ int *u_cdir; /* pointer to inode for current directory */ char u_dbuf[DIRSIZ]; /* current pathname component */ char *u_dirp; /* current pointer to inode */ struct { /* current directory entry */ int u_ino; char u_name[DIRSIZ]; } u_dent; int *u_pdir; /* inode of parent directory of dirp */ int u_uisa[16]; /* prototype segmentation addresses */ int u_uisd[16]; /* prototype segmentation descriptors */ int u_ofile[NOFILE]; /* pointers to file structures of open files */ int u_arg[5]; /* arguments to current system call */ int u_tsize; /* text size (*64) */ int u_dsize; /* data size (*64) */ int u_ssize; /* stack size (*64) */ int u_sep; /* flag for I and D separation */ int u_qsav[2]; /* label variable for quits & interrupts */ int u_ssav[2]; /* label variable for swapping */ int u_signal[NSIG]; /* disposition of signals */ int u_utime; /* this process user time */ int u_stime; /* this process system time */ int u_cutime[2]; /* sum of childs’ utimes */ int u_cstime[2]; /* sum of childs’ stimes */ int *u_ar0; /* address of users saved R0 */ int u_prof[4]; /* profile arguments */ char u_intflg; /* catch intr from sys *//* kernel stack per user* extends from u + USIZE*64* backward not to reach here*/ } u;这里在结构定义的同时定义了全局变量u,根据m40.s中的定义,它位于内核地址 0o140000处。(1)进程切换相关变量进程被切换时,u_rsav[0]记录其内核栈指针r6,u_rsav[1]记录其环境指针r5。u_qsav[2]和u_ssav[2]具有相似得功能,所不同的是:u_qsav和u_ssav是在某个操作过程中,由进程主动决定保存的,而u_rsav是被动保存的。如果r6和r5记录在u_qsav或u_ssav中,那么在进程切换后再返回进程中执行时,程序会返回到u_qsav和u_ssav的调用函数中。这用于如果u_qsav和u_ssav的调用函数在进程被切换走再切回时,不想继续在本函数运行,而是期望返回到自己的调用函数中运行的情形,比如在系统调用时被中断所打断。u_fsav记录当进程的浮点运算寄存器值。u_procp是一个proc *的指针,指向当前用户所运行的进程。(2)进程空间管理变量u_uisa[16]记录进程空间的16个用户页地址寄存器值,对于PDP11/40,只使用前面8个。u_uisd[16]记录进程空间的16个用户页描述寄存器值,对于PDP11/40,只使用前面8个。u_tsize记录进程程序段大小(内存块个数)。u_dsize记录进程数据段大小(内存块个数)。u_ssize记录进程栈段大小(内存块个数)。u_sep表示程序空间和数据、栈空间是否隔离分布。对于PDP11/40,应该不隔离。(3)I/O操作相关变量:u_segflg表示当前user结构中I/O相关的变量地址(如u_base)位于内核空间还是用户空间。1表示在内核空间,否则表示在用户空间。u_error是用户最近一次操作的错误码(如打开文件等)。0表示没有错误,否则有错误。错误码在user.h中定义。u_base用于I/O操作时的缓冲。比如,当用户打开一个文件读取数据时,该指针指向用户设定的缓冲区。u_count用于I/O操作时的字节数,通常和u_base配对使用。u_offset[2]表示I/O操作时的偏移,2个16位整数构成一个32位整数。偏移量=u_offset[0]。u_cdir是inode *指针类型,指向用户当前操作的目录。u_dbuf[DIRSIZ]是当前文件名(非全路径)。u_dirp指向当前操作的文件名,通常用于在系统调用时传递参数。它可以是全路径名,比如“/bin/sh”;也可以是非全路径名,比如“sh”。如果是非全路径名,那么从当前目录开始定位本文件。struct { int u_ino; //当前文件在磁盘上的节点号(inode)。 char u_name[DIRSIZ]; //当前文件名(非全路径) } u_dent;u_pdir表示当前操作文件u_dirp的父目录,inode*类型。u_ofile[NOFILE]记录用户所打开的文件引用结构,它的每一个成员都是file *类型(在file.h中定义)。(4)用户权限管理变量u_uid当前进程的用户ID,和proc结构中的p_uid是一样的。0表示超级用户。u_gid表示当前用户所在的组ID。这给文件系统的权限管理提供了很好的机制。u_ruid表示用户(real user)的ID。可以理解为所有者(owner)ID。当进程创建时,它的u_ruid和u.uid都继承自父进程。但它的u_uid在(调用execl/execv)执行某个程序时,会被改变成改程序(可执行文件)中记录的u_uid。系统在判断进程的访问权限时,都根据其u_uid;当进程想修改其u_uid时,它只能把它改成u_ruid。只有超级用户(super user)可以把u_uid改成任意值。u_rgid表示用户的真正组ID。它的含义和u_ruid雷同。(5)系统调用变量u_arg[5]用于系统调用时的参数传递。u_ar0用于系统调用时的参数和返回值传递。u_intflg表示系统调用是否被软中断信号(signal)打断。(6)时间相关变量int u_utime表示进程在用户态下执行的时间(时钟tick数)。intu_stime表示进程在内核态下执行的时间(时钟tick数)。int u_cutime[2]表示所有子进程在用户态下执行的时间(时钟tick数)。int u_cstime[2]表示所有子进程在内核态下执行的时间(时钟tick数)。int u_prof[4]用于统计进程中某段代码在用户态下的CPU使用率,即该段代码在用户态下被调度获得运行的次数。(7)信号操作变量u_signal[NSIG]记录各种信号处理函数指针。用户可以调用系统函数ssig设置。它和proc结构中p_psig通常配对使用。4.错误码定义在user.h中。/* u_error codes *//* See section "INTRO(II)" of* the UNIX Programmer’s manual* for the meanings of these codes. */ #define EFAULT 106 //地址空间访问出错,比如访问了未映射的虚拟地址。#define EPERM 1 //由于不是root(超级用户)而没有权限执行当前操作 #define ENOENT 2 //所访问文件不存在 #define ESRCH 3 //未找到指定进程 #define EINTR 4 //系统调用被中断所打断而未执行完,并不一定出错 #define EIO 5 // I/O访问出错 #define ENXIO 6 //指定设备找不到 #define E2BIG 7 //调用execl/execv时,入参太长 #define ENOEXEC 8 //不正确的可执行文件格式(.out) #define EBADF 9 //错误的文件句柄,或者没有访问权限 #define ECHILD 10 //没有子进程 #define EAGAIN 11 //系统中进程数已达最大值(64) #define ENOMEM 12 //从.out文件中加载进程或创建子进程失败,由于内存不足 #define EACCES 13 //对指定文件没有访问权限 #define ENOTBLK 15 //所加载设备不是块设备 #define EBUSY 16 //加载或卸载一个正在使用的设备 #define EEXIST 17 //所要创建的设备文件或链接文件已存在 #define EXDEV 18 //想要跨设备链接文件 #define ENODEV 19 //设备号对应的驱动程序不存在#define ENOTDIR 20 //所期望访问的文件应该是目录,但事实上不是 #define EISDIR 21 //使用open来打开一个目录 #define EINVAL 22 //无效的参数值(无效信号值等) #define ENFILE 23 //系统中所打开的文件数已达允许的最大数 #define EMFILE 24 //进程中所打开的文件数已达允许的最大数 #define ENOTTY 25 //所访问的tty终端不存在 #define ETXTBSY 26 //想要读取一个正被写入的可执行文件,或者相反 #define EFBIG 27 //文件大小(偏移量)超出范围 #define ENOSPC 28 //磁盘空间不够 #define ESPIPE 29 //对管道进行seek操作 #define EROFS 30 //想要对只读文件系统进行写入操作 #define EMLINK 31 //所要链接的文件已达最大链接数(127) #define EPIPE 32 //在写管道时,对端没有在读它们定义程序处理特别是系统调用过程中所产生的各种错误(在后面章节中的源代码中,会经常看到它们)。5.5.2 进程的两种状态进程上下文的内容我们已经知道,那么进程上下文和进程的程序、数据在内存中是如何分布的呢?要想弄清楚这一点,首先我们需要了解一下进程的两种状态:用户态和内核态。当进程运行在用户态下时,当前的地址空间通过用户活动页寄存器映射到实际物理地址;而它运行在内核态下时,当前的地址空间通过内核活动页寄存器映射到实际物理地址。进程当前状态通过PSW寄存器的位15、14指示(00-内核态、11-用户态)。一个用户进程一般运行在用户态下,它通过两种方式进入到内核态:中断(自陷)和系统调用。系统调用其实也是一种自陷,不过它相对于其他自陷而言不是一种错误,而是访问操作系统服务的一种手段。在进程进入到内核态后,PDP11/40处理器就会使用内核栈,而不是用户栈。这样,除了用户栈之外,每个进程还需要一个内核栈,这是由操作系统为它们分配的。为每一个进程分配一个内核栈也是很显然的,如果多个进程使用同一个内核栈空间,必然会引起进程数据的混乱而引起错误。就和每一个进程拥有一个用户栈同样的道理。所以UNIX进程空间事实上包含两部分,用户态部分和内核态部分。在用户态下包括程序区、数据区(初始化和未初始化全局变量)和栈区;在内核态下包括进程上下文(proc结构变量和u)和内核栈。进程在用户态下执行的是用户程序区,程序访问的是用户数据和栈区;在内核态下执行的是操作系统内核代码,使用内核栈访问的是内核数据。从程序员(用户)视角,只有用户态空间是可见的;从操作系统视角,这两部分空间都是可见的,进程在用户和内核态下的空间映射情况如图5-6所示。由图5-6可见,u变量固定在内核虚拟地址140000处(8进制),显然该虚拟地址的物理地址使用6号内核活动页寄存器映射。由于虚拟地址是不变的,那么对于不同进程的u,就需要改变6号内核活动页地址寄存器的值来它映射到不同的物理地址,这部分功能在m40.s的retu函数中实现。这里巧妙地使用了“偷梁换柱”的方法,就好比在一个电视上,当切换频道时,呈现不同的节目,但屏幕是同一个,我们并不需要换一台电视机来收看其他节目。当进程处于用户态时,它只能操作用户态虚拟空间(上图左侧空间),而各个用户态进程在物理内存是互相隔离的,所以不能互相访问。当用户进程通过中断(自陷)或者系统调用进入到内核态时,由于中断(自陷)向量区已经被映射到内核虚拟空间,是操作系统的一部分,用户无法更改,所以用户也就无法获得内核态的控制权。从而内核态的控制权只有操作系统才拥有,而操作系统是作为可信软件提供的,这就保证了系统的安全性。500)this.width=500;" border="0">图5-6 进程在用户和内核态下的空间映射图proc结构中的p_addr和p_size字段定义了进程数据交换区的起始地址和大小,它包括u上下文、内核栈、数据段和用户栈。当进程被换入或换出时,该区域就会被整个的换入或换出。在一个进程被换出时,如果没有其他进程和它共享程序段,它的程序段也会被换出到磁盘,同时该段内存释放。在它被换入时,进行相反的操作。因此它的程序段又叫做程序交换区。它由proc结构中的p_textp字段定义,p_textp其实指向一个text结构,在进程被换入到内存中时,text结构中的x_caddr指向程序段在内存中的起始物理块,x_size表明程序段大小(物理块数)。如果程序段被换出到磁盘上,那么x_daddr指向它在磁盘上的起始扇区。5.5.3 调度过程UNIX进程调度主要分为三个过程:进程换入换出、进程优先级调整和进程分派切换执行。我们对应地称其处理程序为交换器、优先级调整器和分派切换器。交换器主要是sched()函数;优先级调整器主要是时钟中断函数clock,它和交换器构成图5-5中的进程状态决策器;而分派切换器主要是swtch()函数,它相当于图5-5中的分派器和进程上下文切换器,它主要是由时钟中断和sleep()函数触发的。交换器和分派切换器都运行在0号进程中,0号进程是系统调度进程。slp.c中包含了调度程序的大部分实现(很奇怪为什么当初UNIX的开发人员起这个名字,因为sleep只是其中的一小部分),它包括sched()、swtch()、setpri()等函数。UNIX进程调度的过程如图5-7所示。500)this.width=500;" border="0">图5-7 UNIX进程调度过程1.相关函数和变量(1)中断关闭(打开)定义在m40.s中:.globl _spl0, _spl1, _spl4, _spl5, _spl6, _spl7_spl0: bic $340,PS rts pc_spl1: bis $40,PS bic $300,PS rts pc_spl4:_spl5: bis $340,PS bic $100,PS rts pc_spl6: bis $340,PS bic $40,PS rts pc_spl7: bis $340,PS rts pcspl0设置PSW寄存器的中断屏蔽优先级=0,从而打开所有中断(自陷)。spl1~spl7分别设置PSW寄存器的中断屏蔽优先级为1~7,从而屏蔽对应级别以下(含)的中断。它们主要用于实现全局数据操作的原子性,从而保证数据正确。(2)进程上下文切换1. .globl _savu, _retu, _aretu2. _savu: //保存r6, r5到入参,一般是u.u_rsav[2]3. bis $340,PS4. mov (sp)+,r15. mov (sp),r06. mov sp,(r0)+7. mov r5,(r0)+8. bic $340,PS9. jmp (r1)10. _aretu: //切换栈指针和r5为传入参数,但不改变当前u变量11. bis $340,PS12. mov (sp)+,r113. mov (sp),r014. br 1f15. _retu: //改变u变量,并切换栈指针和r516. bis $340,PS17. mov (sp)+,r1 18. mov (sp),KISA6 19. mov $_u,r0 20. 1:21. mov (r0)+,sp22. mov (r0)+,r523. bic $340,PS 24. jmp (r1)用C程序表示的函数原型分别是(C程序调用时需要去掉下画线):void savu(int p[2]);void aretu(int p[2]);void retu(int *p_addr);savu主要用来保存当前内核栈指针和r5寄存器的值到传入的数组p中。aretu和savu相对应,设置sp= p[0], r5=[p1]。retu设置u/内核栈区(内核虚拟地址0o140000~0o142000)指向新的物理块p_addr(一般是proc.p_addr),再设置sp=u.u_rsav[0],r5=u.u_rsav[1]。retu会改变当前u变量。第1行全局声明,使得C程序可以调用它们。第3行设置处理器(中断屏蔽)优先级为7,从而任何中断都不能打断下面代码的运行。这里也比较好理解,因为在保存进程上下文时,如果有中断产生,导致切换到其他进程中,那么再切换回该进程时,由于上下文丢失,就会出错(事实是切换到了上次调用savu的执行点)。第4行保存返回地址到r1。第5行传递入参p到寄存器r0,第6、7行保存sp到p[0],r5到p[1]。第8行开中断,第9行返回到调用者。500)this.width=500;" border="0">调用savu后,p[0] = sp,p[1]= r5。可以肯定的是:0o140000+sizeof(u)。研究一下savu就会发现,它事实上保存了调用者的返回地址和r5环境指针,如果调用者是C函数的话。假如进程A中函数caller 调用了callee,而callee又调用了savu如下:void caller(){ callee(); printf("called the callee. ");}void callee(){ savu(u.u_rsav);}由于callee在调用savu之前,它已经调用了编译器所加的函数csv,所以在调用savu后,u.u_rsav[0]=sp, u.u_rsav[1]= r5,如图5-8所示:500)this.width=500;" border="0">图5-8 数组u.u_rsav[2]的内容这里的printf语句指的是它被编译器编译生成的第一条汇编指令的地址。对于retu,注意在第18行改变KISA6的值后,变量u的值事实上已经改变了,因为地址0o140000已被映射到新的物理地址,而u和内核栈正是从0o140000到0o142000。这也正是程序设计的一个巧妙之处。和proc[NPROC]数组不同,初看起来u变量全局只有一个,怎么会使每个进程都有一个呢?原因就在于虚存映射,它可以使得在虚拟地址不变的情况下,改变其实际指向的内容。最后,如果进程A被剥夺CPU运行,后来又再次被分配CPU运行时,就可以通过如下代码切换回上次被切走的运行点:retu(proc[i]->p_addr); //这里的proc[i]是进程A的proc结构。return;看一下汇编会更清楚:mov proc[i]->p_addr, -(sp) //这里proc[i]->p_addr是示意性的/* * 调用retu,设置sp= u.u_rsav[0],r5= u.u_rsav[1] , * 并重新映射0o140000,从而改变u和内核栈 */jsr pc, _retu add 2, sp //使栈指针指向cret函数地址rts pc这样,rts pc指令就会跳转到上图的cret中,cret会返回到caller的printf语句处。从而进程A就可以从上次的“断点”处继续运行了。上一章 启动过程 目录 下一章 中断处理过程本书在全国各大书店均有销售! 11-23 06:16