200 likes | 263 Views
进程和程序. 程序 指令和数据的集合 存放在磁盘上的一个普通文件里 文件的 i 节点中标为可执行,内容符合系统要求 进程 包括指令段、用户数据段和系统数据段的执行环境 进程和程序的关系 程序用于 初始化 指令段和用户数据段 初始化后,进程和初始化它的程序之间无联系 进程运行时 , 它对应的磁盘上的程序文件不可修改 / 删除 几个同时运行的进程可以由同一程序初始化得到,进程之间没什么联系。核心通过安排这样的进程来共享指令段以节省内存 , 但这种安排对用户来说是透明的。. 进程的系统数据. 进程的系统数据 PCB
E N D
进程和程序 • 程序 • 指令和数据的集合 • 存放在磁盘上的一个普通文件里 • 文件的i节点中标为可执行,内容符合系统要求 • 进程 • 包括指令段、用户数据段和系统数据段的执行环境 • 进程和程序的关系 • 程序用于初始化指令段和用户数据段 • 初始化后,进程和初始化它的程序之间无联系 • 进程运行时,它对应的磁盘上的程序文件不可修改/删除 • 几个同时运行的进程可以由同一程序初始化得到,进程之间没什么联系。核心通过安排这样的进程来共享指令段以节省内存,但这种安排对用户来说是透明的。
进程的系统数据 • 进程的系统数据PCB • 在UNIX内核中,含有进程的属性,包括: • 进程状态,优先级信息 • 核心堆栈 • 当前目录(记录了当前目录的i-节点),根目录 • 打开的文件描述符表 • umask值 • 进程PID,PPID • 进程主的实际UID/GID,有效UID/GID • 进程组组号
user结构和proc结构 • user结构(约5000字节),<sys/user.h > • proc结构(约300字节),<sys/proc.h> • 进程运行时才需要的数据在user结构 • 其它信息存于proc结构 • 用户程序不能直接存取和修改进程的系统数据 • 系统调用可用来访问或修改这些属性 • 如: chdir, umask, open, close • setpgrp, getpid, getppid
进程的基本状态 • 基本状态 • 进程创建之后,主要有运行状态和睡眠状态(也叫阻塞状态,等待状态,挂起状态,等等)。 • 在核心中,将运行状态和睡眠状态的程序组织成不同的队列,系统总是在分时处理运行状态的进程,而不顾那些处于睡眠状态的进程。 • 处于睡眠状态的进程,在条件满足后,转化为运行状态,进入运行状态进程的队列而被调度。 • 进程在睡眠时,不占用CPU时间 • 注意:在编程时,程序不要处于忙等待状态
进程的调度 • 调度优先级 • UNIX核心,将可运行进程按优先级分成几个不同的队列,高优先级进程优先被调度执行。 • 可以用nice()系统调用调整进程的优先级 • 进程的优先级总在不停地发生变化 • 处于睡眠状态的进程一旦被叫醒后,被赋以高优先级,以保证人机会话操作和其它外设的响应速度。
系统调用 • UNIX中系统调用(system call)是UNIX内核与应用程序之间的唯一接口。 • 内核对外提供的所有的功能均以系统调用的方式出现,应用程序只有通过系统调用才能申请和访问硬件资源(内存和磁盘,I/O设备)和内核数据 • 进程不可直接读写I/O端口和接管硬件中断,必须要通过系统调用。 • 进程调用一次系统调用要导致CPU在用户态和核心态之间作一次切换,比在进程自己的地址空间中调用一个子例程要花费更长时间(所以UNIX中提供了文件缓冲I/0库程序) • C程序员在使用系统调用和使用其它函数调用时,用法上没什么区别
获取系统调用返回值 函数返回值 UNIX的大多系统调用都返回一个整数值,如: open(), wrire(), read()等, 当返回值为-1时,表示系统调用执行失败,系统会填写变量errno errno C程序库中定义的全局性整数,说明失败的原因, errno可能的取值在<sys/errno.h>文件中定义了宏,如: EBADF, EINTR, ENOMSG等等。 对于某一系统调用,直接用man命令查阅联机手册, 得知系统调用失败时errno可能的取值及原因 sys_errlist 还有一个sys_errlist表,也可以直接引用,是错误代码对应的错误信息表
perror/strerror 与系统调用执行失败有关的的函数: char *strerror(int errnum); void perror (char *s); perror会在stderr上产生一条消息,描述在系统调用或使用库函数(库函数中发出了系统调用)期间所遇到的最后一个错误。先印出字符串s,再印出一个冒号和空格,然后再印出这条消息和换行符 strerror()根据错误号,得到一个描述这一错误的字符串. 举例程序 执行时,如果文件打开失败(系统调用open),就给出一条信息,根据失败的原因不同,打印出的错误信息内容也不同,如:没有访问权限,文件不存在,等等。
系统调用举例 #include <sys/fcntl.h> extern int errno; extern char *sys_errlist[]; void main(int argc, char **argv) { int fd; fd=open(argv[1],O_RDWR); if(fd==-1) { printf("errno=%d [%s]\n",errno, sys_errlist[errno]); printf("strerror=[%s]\n",strerror(errno)); perror("Open file"); } else { printf("OK\n"); /* do something here */ } } 执行结果 errno=21 [Is a directory] strerror=[Is a directory] Open file: Is a directory
系统调用fork • fork系统调用是创建新进程的唯一方式 • 继承:fork创建一个新进程,新进程的指令,用户数据段和系统数据段几乎都是旧进程的复制 • 父进程和子进程:原先的进程被称做“父进程”,新创建的进程被称作“子进程” • fork返回值:父子两进程都收到返回值,但不相同。返回值很关键,它用于区分父进程(返回值>0,是子进程的PID)和子进程(返回值=0)。fork调用唯一能出错的原因是资源耗尽(返回值=-1) • 内核在实现fork调用时,首先初始化创建一个新的proc结构,然后复制父进程环境(包括user结构和内存资源)给子进程 • 父子进程可以共享程序和数据(例如:copy-on-write技术),但是系统核心的这些安排,对程序员透明
fork举例 执行结果 Hello pid=815 ppid=814,a=6 Bye Pid=814 child=815,a=6 Bye main() { int a,pid; printf("Hello\n"); a=3; pid=fork(); a+=3; if(pid>0) { printf("Pid=%d child=%d,a=%d\n", getpid(),pid,a); } else if(pid==0) { printf("pid=%d ppid=%d,a=%d\n", getpid(),getppid(),a); } else { perror("Create new process"); } printf("Bye\n"); }
命令ps 功能 查阅进程状态(process status)(实际上就是将内核中proc[]和user[]数组的内容有选择地打印出来) 选项 用于控制列表的行数(进程范围)和列数(每进程列出的属性内容) 无选项:只列出在当前终端上启动的进程。 列出的项目有:PID,TTY,TIME,COMMAND e选项:列出系统中所有的进程(进程范围) t选项:列出指定终端上的所有进程(进程范围) f选项:以full格式列出每一个进程(控制列的数目) l选项:以long格式列出每一个进程(控制列的数目)
命令ps举例 例:ps -ef命令的输出 UID PID PPID C STIME TTY TIME COMMAND root 0 0 0 16:11:14 ? 0:00 sched root 1 0 0 16:11:14 ? 0:01 /etc/init root 2 0 0 16:11:14 ? 0:00 vhand root 3 0 0 16:11:14 ? 0:00 bdflush root 6547 335 0 16:15:05 01 0:00 server 4 1 root 175 1 0 16:11:14 ? 0:00 inetd root 173 1 0 16:11:14 ? 0:00 syslogd root 296 1 0 16:11:21 ? 0:00 /tcb/files/no_luid/sdd root 6551 291 80 16:17:16 08 0:37 ifvl2 -trace4 root 335 1 0 16:11:31 01 0:00 server_ctl root 337 1 0 16:11:33 01 0:05 server_ap root 353 1 0 16:12:01 12 0:00 client_ctl 100.1.1.3 root 356 295 0 16:12:07 12 0:04 client_ap 1403 UID:用户ID(注册名), PID:进程ID PPID:父进程的PID, STIME:启动时间 TTY:终端的名字 , COMMAND:命令名 TIME:累计执行时间(占用CPU的时间)
访问环境参数的三种方法 main() { extern char **environ; char **p; p=environ; while(*p) printf(”[%s]\n”,*p++); } main(int argc, char **argv, char **env) { char **p; p=env; while(*p) printf(”[%s]\n”,*p++); } main() { char *p; p=getenv(”HOME”); if(p) printf (”[%s]\n”); }
exec系统调用 用一个指定的程序文件,重新初始化一个进程。 exec可以指定新的命令行参数和环境参数 注意:exec并不创建新进程,只是将当前进程重新初始化了指令段和用户数据段,堆栈段,重新初始化CPU的PC指针 6种exec系统调用 execl / execv / execle execve / execlp / execvp exec前缀,后跟一至两个字母,这里字母代表含义为: l--list, v--vector, e--env, p--path l与v:指定命令行参数的两种方式,l以表的形式,v要事先组织成一个指针数组 e:需要指定envp来初始化进程。 p:使用PATH查寻可执行文件file
exec系统调用(续) int execl(path,arg0,arg1,...,0); char *path,*arg0,*arg1,...,*argn; int execv(path,argv) char *path,**argv; int execle(path,arg0,arg1,...,argn,0,envp) char *path,*arg0,*arg1,...,*argn,**envp; int execve(path,argv,envp) char *path,**argv,**envp; int execlp(file,arg0,arg1,...,argn,0); char *file,*arg0,*arg1,...,*argn; int execvp(file,argv) char *file,**argv;
wait系统调用 功能 等待进程的子进程终止,如果已经有子进程终止,则立即返回 函数原型 #include <sys/types.h> #include <sys/wait.h> pid_t wait(int *stat_loc); 调用结果 函数返回值为已终止的子进程PID pid=wait(&status); status中含有子进程终止的原因 TERMSIG(status)为被杀信号 EXITSTATUS(status)为退出码
字符串库函数strtok char *strtok (char *str, char *tokens) 功能: returns a pointer to the first character after a token
最简单的shell:xsh0 main() { char buf[256],*cmd,*argv[100]; int n,sv; for(;;) { printf("=> "); if(fgets(buf,sizeof(buf),stdin)==NULL) exit(0); cmd=strtok(buf," \t\n"); if(cmd) { if(strcmp(cmd,"exit")==0) exit(0); n=0; argv[n++]=cmd; while(argv[n++]=strtok(NULL," \t\n")); if(fork()==0) { execvp(cmd,argv); fprintf(stderr,"** Bad command\n"); exit(1); } wait(&sv); } } }
执行xsh0 => => who am i root ttyp0 Nov 29 09:56 => ps -f UID PID PPID C STIME TTY TIME CMD root 1012 1011 0 09:56:00 ttyp0 00:00:00 login -c -p root 1020 1012 0 09:56:14 ttyp0 00:00:00 -csh root 1060 1020 2 10:36:37 ttyp0 00:00:00 xsh0 root 1062 1060 4 10:36:46 ttyp0 00:00:00 ps -f => ls -l *.c ls: *.c not found: No such file or directory (error 2) => mmx ** Bad command => ls -l xsh0 -rwxr--r-- 1 root other 43417 Dec 1 1998 xsh0 => exit