1 / 64

Unix 进程管理

Unix 进程管理. 1. 进程的基本概念 2. 进程结构 3 . 进程映像 4. 进程状态 5. 进程调度 6. 进程控制 7. 进程通信 (IPC, INTER-PROCESS COMMUNICATION). 1. 进程的概念. 程序在其上下文中的一次执行 . 除 0 # 和 1 # 进程外 , 由 fork 创建的实体的集合. * 讲授以 UNIX System v 为例 , 为理解方便,有些篡改。. 2.UNIX 进程结构. 1 #. Login shell. shell. $ ls|sort. prog1. ls. pwd.

mick
Download Presentation

Unix 进程管理

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Unix 进程管理 1. 进程的基本概念 2. 进程结构 3 .进程映像 4. 进程状态 5. 进程调度 6.进程控制 7. 进程通信(IPC, INTER-PROCESS COMMUNICATION)

  2. 1.进程的概念 • 程序在其上下文中的一次执行. • 除0#和1#进程外,由fork创建的实体的集合 *讲授以UNIX System v为例,为理解方便,有些篡改。

  3. 2.UNIX进程结构 1# Login shell shell $ ls|sort prog1 ls pwd ls program1 sort program2 program3

  4. 3.UNIX进程上下文 • 进程上下文:指进程的用户地址空间内容、寄存器内容及与进程相关的核心数据结构;包括三部分"上下文":用户级、寄存器级、系统级 • 用户级上下文:正文段即代码(text);数据段(data);栈段(user stack):用户态执行时的过程调用;共享存储区(shared memory) • 把地址空间的段称为"区(region)":进程区表和系统区表(前者索引指向后者) • 系统上下文: • proc结构:总在内存,内容包括阻塞原因; • user结构:可以调出到外存,进程处于执行状态时才用得着,各种资源表格; • 进程区表:从虚拟地址到物理地址的映射; • 核心栈:核心态执行时的过程调用的栈结构;

  5. 进程PCB(proc + user) • Proc: struct proc{ }proc[N]; p_stat; 进程状态如:SRUN,SSLEEP,SZOMB... p_flag ; 每位代表一个含义,如:SLOAD,… p_pri ; 优先级,如:-100 - 127 p_cpu ; p_nice ;计算进程动态优先数 p_sig ;接受软中断信号 p_uid ;进程的用户标识 p_pid; 进程标识符 p_ppid; 父进程的标识符 p_time ;进程在内存或外存的驻留时间 p_addr ;u区的地址(现代 :p_ubptb,p_regin) p_size; u区的大小 p_ttyp;进程相关终端 p_textp ; 指向共享正文段表 p_wchan; 进程等待原因

  6. 进程PCB(proc + user) u_procp;(指向proc) u_tsize; u_dsize; u_ssize;(正文段,数据段,用户栈大小) u_rsav[2] ; u_qsav[2]; u_ssav[2];(保留进程现场) u_ttyp;(终端) u_cdir; (当前目录) u_signal[NSIG];(软中断处理程序) u_uid; u_gid; u_ruid; u_rgid;(用户,组标识) u_arg[6];(传递系统调用参数) u_ofile[NFILE];(用户打开文件表) u_base; u_count; u_offset;(文件系统调用) u_utime; u_stime; u_cutime; u_cstime;(CPU时间) • User : struct user {…} ;主要包括:

  7. 系统共享正文段表 text • Struct text{….} text[M]; x_caddr; ( 内存始地址) x_daddr;(磁盘始地址) x_size;(正文段大小) x_count; (共享进程计数)

  8. 进程上下文之间的联系 proc[N] text[ ] p_textp x_caddr p_addr 核心区 用户区 用户栈 数据段 核心栈 user 正文段 u_procp

  9. 4. 进程状态 • 进程有9个状态,由p_stat 和p_flag及处理机状态字决定: • 创建SIDL • 核心态运行SRUN • 用户态运行SRUN • 内存就绪SRUN&SLOAD • 外存就绪SRUN&!SLOAD • 内存睡眠 SSLEEP&SLOAD(SWAIT or SSTOP) • 外存睡眠SSLEEP&!SLOAD(SWAIT or SSTOP) • 可抢先SRUN • 僵死SZOMB 1 2 3 4 5 6 7 8 9

  10. 进程状态(续) 注意:状态“被抢先”与“内存就绪”的地位相同,要等到下一次进程调度时,才能回到“用户态执行”。 3 8 1 5 4 9 2 7 6

  11. 5. 进程调度 • Swtch:采用动态优先数法 p_pri=p_cpu/2 + PUSER + p_nice +NZERO 25 20 系统进程优先级高:SCHED=-100,打印机=10,I/O缓存=-50 .计算方法: 1.每个时钟周期,当前进程 p_cpu+1; 2.每秒钟时钟中断时,全部进程的 p_cpu/2,计算所有进程的 p_pri ,若当前进程不是最小,设runrun=1 3.进程从中断或陷入返回时,计算 当前进程的 p_pri 基本上每个进程占用CPU时间不会超过一秒

  12. 进程调度(续) • 调度时机 1. 进程结束。 2. 进程睡眠SSLEEP or SWAIT。 3.进程暂停 SSTOP。 4. 当前进程需扩充内存,内存没空,被交换到外存。 5. 等待共享段调入内存 6.进程从核心态返回时,runrun=1 当前进程状态如 何变换? .唤醒进程时可能设runrun .计算进程p_pri时可能设runrun

  13. Swtch流程图 当前进程现场-->核心栈 栈指针--〉u_rsav[2] 在proc中搜索SRUN&SLOAD&p_pri最小的进程P 从P的u_rsav[2]-->栈指针寄存器 根据栈指针寄存器恢复P现场 ?

  14. 6. 进程控制 • fork:创建子进程 • wait:父进程等待子进程结束 • exit:进程结束 • exec:进程更换正文段

  15. fork---创建进程 1。调用格式 { 0---子进程返回 子进程pid---父进程返回 fork( )= 例: main() {int x; while((x=fork())== -1); if(x==0) printf(“a”); else printf(“b”); printf(“c”); } 2。使用方法 main() { …. while((x=fork())== -1); if(x==0) { 子进程语句} else {父进程语句} 父、子都执行语句; } 结果 ? abcc? bcac? abcc? acbc? cabc?

  16. fork() 内存有空? n 在proc 数组中找一个空表项 找到否? 在磁盘复制 y y 为子进程申请空间; 复制user,数据段; u_procp指向proc; u_utime=0; u_stime=0; 栈指针-->u_rsav[2]; p_pid=系统赋标识号码 p_stat=SRUN; p_ppid=当前进程pid; 复制父进程的p_textp,p_size p_uid,p_ttyp,p_nice... 子进程返回0; 父进程返回子进程pid; x_count++; f_count++ 子:0 父: 子pid

  17. 父进程创建子进程 proc[N] text[ ] p_textp 父 x_caddr p_textp 子 p_addr p_addr 核心区 用户区 用户栈 数据段 核心栈 user 用户栈 数据段 核心栈 user 正文段 u_procp u_procp

  18. fork例 1.fork error 2 . i=5 i=10 i=7 3. i=7 i=5 i=10 4. i=5 i=7 i=10 main() { int child, i=2; if((child=fork())==-1) {printf("fork error. ");exit();} if(child==0) {i=i+3; printf(“i=%d\n”,i); } i=i+5; printf(“i=%d\n”,i); } 插入else呢?

  19. 子1 父 子1 子2 父 子2 main( ) { if(fork()==0) { 子1的代码段; if(fork()==0) {子2的代码段} else { 子1的代码段} } else {父代码段} } main( ) { if(fork()==0) { 子1的代码段} else {if(fork()==0) {子2的代码段} else {父代码段} } } 去掉这个else 谁执行这一段?

  20. exec-执行文件 • 更换进程执行代码,更换正文段,数据段 • 调用格式:exec (文件名,参数表,环境变量表) • 例:execlp(“max”,15,18,10,0); execvp(“max”,argp) main() { if(fork()==0) {printf(“a”); execlp(“file1”,0); printf(“b”); } printf(“c”); } file1: main() { printf(“d”); } acd? cad? adc? abdc? adbcc?

  21. exec(文件名,参数表) 参数复制到系统空间 取文件i节点,验证文件 释放老分区 按文件头指定的文件大小, 分配新分区,装入新分区 Exec参数复制到新用户栈 初始化各分区 使能返回用户态 释放文件i节点

  22. text 正文段 可执行文件 proc 头 正文段 数据段 工作区 数据段 核心栈 user 数据段 核心栈 user

  23. wait--等待子进程结束exit---终止进程 • 使用方法: main( ) { int n; …. if(fork()==0) {printf(“a”); exit(0); } wait(&n); printf(“b”); } printf(“c”);

  24. exit(int status) 释放user,所有栈,数据段 关闭打开文件表 工作目录。 u_ofile,u_cdir p_stat=SZOMB 将所有子进程的父进程 改为1号进程(p_ppid) 释放正文段 x_count--; ... wakeup(p_ppid); status-->user user-->磁盘 p_addr-->盘user swtch

  25. wait(int * stat) 从盘上取出子进程user 有子进程结束? (father) ( sun) u_cutime+=u_utime; u_cstime+=u_stime; u_cutime+=u_cutime; u_cstime+=u_cstime sleep(u_procp,PWAIT) user中的status-->stat 释放子进程的proc 返回子进程的pid

  26. shell程序 …… while(true) { printf(“$”); scanf(“% s”,&command); 检查命令的语法; if (fork()==0) {….. execlp(command,参数表,0); } if (command后缀!=‘&’) wait( …); ….. }

  27. 上机作业 • getpid()---获取进程的pid 作业一: 每个进程都执行自己独立的程序,打印自己的pid,父进程打印两个子进程的pid; 父 子1 子2 子 1 父 作业二: 同作业一要求。子2与父代码同 子2 作业三:写一个命令处理程序,能处理max(m,n), min(m,n) average(m,n,l)这几个命令(前后台都可以)。

  28. 实例:UNIX_wait 演示子进程与父进程的关系和fork、exec、wait的使用; 程序main.c 功能是进行10次循环,创建2个子进程。循环到第3次时,等待子进程结束。 #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <sys/wait.h> pid_t wait(int *stat_loc); void perror(const char *s); #include <errno.h> int errno; int global;

  29. main() { int local,i; pid_t child; if ((child=fork()) == -1) { // 创建失败 printf("Fork Error.\n"); } if (child == 0) {// 子进程 printf("Now it is in child process.\n"); if (execl("/home/xyong/work/ttt","ttt",NULL) == -1) { // 加载程序失败 perror("Error in child process"); } global=local + 2; exit(); }

  30. // 父进程 printf("Now it is in parent process.\n"); for (i=0; i<10; i++) { sleep(2); printf("Parent:%d\n",i); if (i==2) { if ((child=fork()) == -1) { // 创建失败 printf("Fork Error.\n"); } if (child == 0) {// 子进程 printf("Now it is in child process.\n"); if (execl("/home/xyong/work/ttt","ttt",NULL) == -1) { // 加载程序失败 perror("Error in child process"); } global=local + 2; exit(); } }

  31. if (i==3) { pid_t temp; temp=wait(NULL); printf("Child process ID: %d\n", temp); } } global=local + 1; exit(); }

  32. 程序test.c #include <sys/types.h> #include <unistd.h> pid_t getpid(void); pid_t getppid(void); int global; main() { int local; int i; pid_t CurrentProcessID, ParentProcessID; CurrentProcessID=getpid(); ParentProcessID=getppid(); printf("Now it is in the program TEST.\n"); for (i=0; i<10; i++) { sleep(2); printf("Parent: %d, Current: %d, Nunber:%d\n",ParentProcessID, CurrentProcessID,i); } global=local + 1; exit(); } 功能是进行10次循环。

  33. 7. 进程间通信(IPC, INTER-PROCESS COMMUNICATION) • U7.1 进程间通信的类型 • U7.2 信号(signal) • U7.3 共享存储区(shared memory) • U7.4 消息(message) • U7.5 信号量 • U7.6 管道(pipe) • U7.7 套接字(socket) 返回

  34. U7.1 进程间通信的类型 • 低级通信和高级通信 • 低级通信:只能传递状态和整数值(控制信息),包括进程互斥和同步所采用的信号量和管程机制。优点的速度快。缺点是: • 传送信息量小:效率低,每次通信传递的信息量固定,若传递较多信息则需要进行多次通信。 • 编程复杂:用户直接实现通信的细节,编程复杂,容易出错。 • 高级通信:能够传送任意数量的数据,包括三类:共享存储区、管道、消息。 返回

  35. 直接通信和间接通信 • 直接通信:信息直接传递给接收方,如管道。 • 在发送时,指定接收方的地址或标识,也可以指定多个接收方或广播式地址; • 在接收时,允许接收来自任意发送方的消息,并在读出消息的同时获取发送方的地址。 • 间接通信:借助于收发双方进程之外的共享数据结构作为通信中转,如消息队列。通常收方和发方的数目可以是任意的。

  36. U7.2 信号(signal) 信号相当于给进程的“软件”中断;进程可发送信号,指定信号处理例程;它是单向和异步的。 返回

  37. 1.信号类型 • 一个进程向另一个进程或进程组(或自己)发送信号(kill系统调用),信号送到p_sig中。:发送者必须具有接收者同样的有效用户ID,或者发送者是超级用户身份 • 某些键盘按键,如:中断字符(通常是Ctrl+C或Del)、暂停字符(如Ctrl+Z) • 硬件条件,如:除数为零、浮点运算错、访问非法地址等异常条件 • 软件条件,如:Socket中有加急数据到达

  38. 2.对信号的处理 • 进程可以设置信号处理例程(signal系统调用),在接收到信号时就被调用,称为“捕获”该信号。信号处理例程的参数是接收到信号的编号。(u_signal[20]) • 进程也可以忽略指定的信号(SIG_IGN)。 • 只有SIGKILL信号(无条件终止进程)和SIGSTOP(使进程暂停)不能被忽略。 • 在库函数system()的实现中,通过fork和exec加载新程序之后,在父进程中对SIGINT和SIGQUIT都要忽略,然后wait直到子进程终止,才恢复对SIGINT和SIGQUIT的原有处理例程。 • 进程创建后为信号设立了默认处理例程(SIG_DFL),如:终止并留映象文件(core)

  39. 信号例 Q.user Q.proc 接受进程Q: main() { signal(n,f()); … interrupt … f() { … } … } 发送进程P: main() { … kill(pid,n); … … f() p_sig n u_signal[n] 中断处理 程序

  40. U7.3 共享存储区(shared memory) 相当于内存,可以任意读写和使用任意数据结构(当然,对指针要注意),需要进程互斥和同步的辅助来确保数据一致性 • 创建或打开共享存储区(shmget):依据用户给出的整数值key,创建新区或打开现有区,返回一个共享存储区ID。 • 连接共享存储区(shmat):连接共享存储区到本进程的地址空间,可以指定虚拟地址或由系统分配,返回共享存储区首地址。父进程已连接的共享存储区可被fork创建的子进程继承。 • 拆除共享存储区连接(shmdt):拆除共享存储区与本进程地址空间的连接。 • 共享存储区控制(shmctl):对共享存储区进行控制。如:共享存储区的删除需要显式调用shmctl(shmid, IPC_RMID, 0); 1. UNIX的共享存储区系统调用

  41. 共享存储区系统调用 段标识shmid -1 错误 • int shmget(key,nbyte,flag)={ • char * shmat(shmid,addr,flag)=段虚地址shmaddr • int shmdt(shmaddr) • int shmctl(shmid,cmd,sbuf) 例: #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #define KEY 75 #define k 1024

  42. 生产者进程 消费者进程 main() {int shmid;*p,i; char * shmaddr; shmid=shmget(KEY,K, 0777|IPC_CREAT); shmaddr=shmat(shmid,0,0); p=(int *) shmaddr; for(i=0;i<K;i++) *p++ = i*i; shmdt(shmid); shmctl(shmid,IPC_RMID,0); ….... } main() {int mid;*q,*t,i; char * maddr; mid=shmget(KEY,K, 0777); maddr=shmat(mid,0,0); q=(int *) maddr; for(i=0;i<K;i++) printf(“%d”,*q++); shmdt(mid); …..... } 是否有问题? t=(int*)shmat(mid,0,0);

  43. U7.4 消息(message) 与窗口系统中的“消息”不同。通常是不定长数据块。消息的发送不需要接收方准备好,随时可发送。 返回

  44. UNIX消息 • 消息队列(message queue):每个message不定长,由类型(type)和正文(text)组成 • UNIX消息队列API: • msgget依据用户给出的整数值key,创建新消息队列或打开现有消息队列,返回一个消息队列ID; • msgsnd发送消息; • msgrcv接收消息,可以指定消息类型;没有消息时,返回-1; • msgctl对消息队列进行控制,如删除消息队列; • 通过指定多种消息类型,可以在一个消息队列中建立多个虚拟信道 • 注意:消息队列不随创建它的进程的终止而自动撤销,必须用msgctl(msgqid, IPC_RMID, 0)。另外,msgget获得消息队列ID之后,fork创建子进程,在子进程中能否继承该消息队列ID而不必再一次msgget。

  45. 消息的系统调用格式及使用 • Message define: struct msgbuf{ long mtype; char mtext[N]; } • int msgget(key,flag)={msgid,-1); • int msgsnd(msgid,msgbuf,nbyte,flag); • int msgrcv(msgid,buf,nbyte,flag); • int msgctl(msgid,cmd,sbuf); cmd={IPC_STAT,IPC_RMID,IPC_SET) wait? wait?

  46. 服务器进程 客户进程 main() {int msgid,p,i; struct msgbuf msg; msgid=msgget(KEY, 0777|IPC_CREAT); for(;;){ msgrcv(msgid,&msg,256,1,0); p=(int*)msg.mtext; msg.mtype=*p; msg.mtext=“OK”; msgsnd(msgid,&msg,2,0);} ….. } main() {int mid;*q,*t; struct msgbuf m; mid=msgget(KEY, 0777); t=getpid(); m.mtype=1; q=(int*)m.mtext; *q=t; msgsnd(mid,&m,sizeof(int),0); …..… msgrcv(mid,&m,256,t,0); printf(“%s”,m.mtext); …. } for(i=0;i<=20;i++) signal(i,clearnup); cleanup() {msgctl(shmid,IPC_RMID,0); exit();} } ....

  47. U7.5 Unix 信号量(Semaphore) • 用于进程之间的同步和互斥 • 实现信号量集机制,即一次可同时对多个信号量做P,V操作,减少死锁发生的概率。

  48. 信号量APIs • struct sem{ushort semval;---信号量值 • ushort sempid;-- 最后操作信号量的pid • ushort semncnt;--等待信号量值增加的进程个数 • ushort semzcnt;--等待信号量值为零的进程个数 • } • ●struct sembuf{ushort sem_num;信号量序号 ushort sem_op;信号量操作 ushort sem_flag; 访问标志 } 1.semget(key,count,flag)={semid,-1} 2.semop(semid,oplist,count) (struct sembuf (*oplist)[ ]) 3.semctl(semid,snum,cmd,arg) cmd={SETALL,GETALL,IPC_RMID)

  49. 信号量APIs(cont.) 1.sem_op>0 semval+sem_op,唤醒所有等待该值增加的进程 2. sem_op=0 semval=0,继续后面操作,否则semzcnt+1,进程睡眠 3.sem_op<0 .|sem_op|<semval semval+sem_op .|sem_op|>semval semncnt+1,进程睡眠 .|sem_op|=semval semval=0,唤醒所有等待该值为零的进程 。信号量值始终 ≥0 。两个等待队列---信号量值增加、信号量值为零 。一旦进程睡眠,唤醒后重新开始sem_op操作

  50. 用unix信号量实现P、V操作producer--concumer • sid=semget(KEY,3,0777|IPC_CREAT) • P(mutex)=>struct sembuf Wm={0,-1,SEM_UNDO}; semop(sid,Wm,1); • V(mutex)=>struct sembuf SIGm={0,1,SEM_UNDO}; semop(sid,SIGm,1); • P(s1)=>struct sembuf W1={1,-1,SEM_UNDO}; semop(sid,W1,1); • V(s1)=>struct sembuf SIG1={1,1,SEM_UNDO}; semop(sid,SIG1,1); • P(s2)=>struct sembuf W2={2,-1,SEM_UNDO}; semop(sid,W2,1); • V(s2)=>struct sembuf SIG2={2,1,SEM_UNDO} ; semop(sid,SIG2,1);

More Related