《linux进程和线程编程》由会员分享,可在线阅读,更多相关《linux进程和线程编程(59页珍藏版)》请在金锄头文库上搜索。
1、第第 1 页页Linux进程和线程编程进程和线程编程第第 2 页页1 1 1 1、LinuxLinuxLinuxLinux中的进程相关概念中的进程相关概念中的进程相关概念中的进程相关概念2 2 2 2、LinuxLinuxLinuxLinux中的进程的创建中的进程的创建中的进程的创建中的进程的创建3 3 3 3、LinuxLinuxLinuxLinux中的进程的管理和守护进程中的进程的管理和守护进程中的进程的管理和守护进程中的进程的管理和守护进程第第 3 页页6.1进程的概述时间和空间是计算机的两个概念,操作系统将者两个概念实现为文件和进程。进程是一个可以独立的可有OS调度的活动。进程是一个抽
2、象实体,当它执行的时候需要OS分配资源。进程是一个正在执行的程序。进程是一个程序的一次执行。进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元第第 4 页页6.1进程的概述进程的概念主要有两点:第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(textregion)、数据区域(dataregion)和堆栈(stackregion)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第
3、二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。原因原因原因原因操作系统引入进程的概念的原因:从理论角度看,是对正在运行的程序过程的抽象;从实现角度看,是一种数据结构,目的在于清晰地刻划动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序。第第 5 页页6.1.1进程的描述进程的种类交互进程:由一个Shell启动的进程,交互
4、进程既可以在前台运行,也可以在后台运行。批处理进程:这种进程和终端没有联系,是一个进程序列。监控进程:也称守护进程,Linux系统启动是启动的进程,并在后台运行。进程不但包括程序指令和数据,还包括程序计数器和CPU的所有寄存器及其存储临时数据的进程堆栈。Linux是一个多进程OS,其他进程必须等到当前正在执行的进程交出CPU的控制权后才能被内核调度执行。当正在运行的进程等待其他系统资源时,linux内核获得CPU的控制权,并将CPU分配给其他正在等待的进程,内核中的调度算法决定将CPU分配给那个进程。第第 6 页页内存是计算机的敏感资源,熟练掌握对内存的操作是一个程序员的必备素质。数据的内部存
5、储方式:大小端法在c中,不同的数据类型占用的空间是不同的,例如char类型占用1个字节,而int占用4个字节高高785634低低12345612786.1.1进程的描述-进程内存管理-数据内部存储第第 7 页页时间和空间是计算机的两个基本的概念,操作系统将这两个概念实现未进程和文件,。进程ID是一个进程的基本属性,其作用类似与人的身份证号,根据进程ID用户可以精确的定位一个进程,一个标识符对应于一个进程,多个进程标识符可以对应于一个程序。进程标识符的类型是pit_t,他是一个无符号整数。进程标进程标识符识符1进程标进程标识符识符2进程标进程标识符识符3进程进程1进程进程2进程进程3程序程序2程
6、序程序16.1.1进程的描述-进程标识符进程ID第第 8 页页使用PS查看当前进程ps-u用户名第一列是进程id最后一列是对应的程序名称6.1.1进程的描述-进程标识符进程ID第第 9 页页Linux操作系统包括三种不同类型的进程,每种进程都有自己的特点和属性。交互进程由一个shell启动的进程。交互进程既可以在前台运行,也可以在后台运行(人机交互)。批处理进程这种进程和终端没有联系,是一个进程序列(多个进程)。监控进程(也称守护进程)Linux系统启动时启动的进程,并在后台运行。6.1.1进程的描述-Linux进程分类第第 10 页页运行状态:此时进程或者正在运行或者准备运行等待状态:此时进
7、程在等待一个事件的发生或者某种系统资源停止状态:此时进程已经被终止死亡状态:这是一个停止的进程,但是在进程向量数组中占有一个task_struct结构R(TASK_RUNNING),可执行状态S(TASK_INTERRUPTIBLE),可中断的睡眠状态。D(TASK_UNINTERRUPTIBLE),不可中断的睡眠状态。s进程的领导者+前台进程6.2.1Linux进程运行状态第第 11 页页每一个进程有6个重要的ID,分别是进程ID、父进程ID、有效用户ID、有小组ID、实际用户ID和实际组ID。这六个ID保存在内核中的数据结构中,有些时候用户程序需要用到这些ID。例如在/proc文件系统下每
8、个进程有一个目录,里面存放进程的相关信息,当进程要读取这些文件时,应该先得到当前进程的ID才能确定进入那一个进程相关的子目录,由于这些ID存储在内核之中,因此linux提供一组专门的接口函数访问这些ID值。Linux环境下使用getpid和getppid函数获得进程和父进程的ID,其函数原型如下:#includePid_tgetpid(void)pid_tgetppid(void)函数成功返回进程的id,失败返回-1,6.2.1Linux进程标识符第第 12 页页Linux环境下使用getuid和geteuid函数获得进程用户ID,其函数原型如下:#includeuid_tgetuid(voi
9、d)uid_tgeteuid(void)函数成功返回进程的用户id,失败返回-1.Linux环境下使用getgid和getegid函数获得进程用户组ID,其函数原型如下:#includegid_tgetgid(void)gid_tgetegid(void)函数成功返回进程的用户id,失败返回-1,Pid_t类型参看/usr/include/bits/types.h实际就是int类型6.2.1Linux进程标识符第第 13 页页每6.2.1Linux进程标识符第第 14 页页启动进程和调度进程1手动启动:用户直接输入shell命令后回车直接启动进程前台启动:用户输入一个shell命令后按回车就启
10、动给一个前台进程后台启动:在shell命令后加入一个“&”符号再按回车键就可以启动一个后台作业。6.2.2Linux进程标识符启动第第 15 页页进程的前后台切换1bg命令(background)格式bg作业号功能:将前台作业切换到后台运行,若没有指定的作业则将当前作业切换到后台例如使用vi编辑文件f1然后用ctrl+Z组合挂起vi再切换到后台6.2.2Linux进程进程前后台切换第第 16 页页3Fg挂起程序作用:fg命令使一个被挂起的进程在前台执行。格式:fgjob-specjob-spec:后台任务号码。说明:fg命令和bg命令是相对应的。如果想查看后台程序运行情况,可以使用fg命令把它
11、调回前台查看。bg命令可以使多个进程放到后台中执行。6.2.2Linux进程进程命令第第 17 页页作用:ps命令主要查看系统中进程的状态。格式:ps选项主要选项如下。-A:显示系统中所有进程的信息。-e:显示所有进程的信息。-f:显示进程的所有信息。-l:以长格式显示进程信息。-r:只显示正在运行的进程。-u:显示面向用户的格式(包括用户名、CPU及内存使用情况等信息)。-x:显示所有非控制终端上的进程信息。-p:显示由进程ID指定的进程的信息。-t:显示指定终端上的进程的信息。6.2.2Linux进程进程命令-ps第第 18 页页说明:要对进程进行监测和控制,首先要了解当前进程的情况,也就
12、是需要查看当前进程。ps命令就是最基本、也是非常强大的进程查看命令。根据显示的信息可以确定哪个进程正在运行、哪个进程被挂起、进程已运行了多久、进程正在使用的资源、进程的相对优先级,以及进程的标志号(PID)。所有这些信息对用户都很有用,对于系统管理员来说更为重要。使用psaux命令可以获得终端上所有用户的有关进程的所有信息,下面结合图4-8讲解进程的基本信息6.2.2Linux进程进程命令-ps第第 19 页页USER表示启动进程用户。PID表示进程标志号。%CPU表示运行该进程占用CPU的时间与该进程总的运行时间的比例。%MEM表示该进程占用内存和总内存的比例。VSZ表示占用的虚拟内存大小,
13、以KB为单位。RSS为进程占用的物理内存值,以KB为单位。TTY表示该进程建立时所对应的终端,?表示该进程不占用终端。STAT表示进程的运行状态,包括以下几种代码:D,不可中断的睡眠;R,就绪(在可运行队列中);S,睡眠;T,被跟踪或停止;Z,终止(僵死)的进程,Z不存在,但暂时无法消除;W,没有足够的内存分页可分配;高优先序的进程;N,低优先序的进程;L,有内存分页分配并锁在内存体内(实时系统或I/O)。START为进程开始时间。TIME为执行的时间。COMMAND是对应的命令名。6.2.2Linux进程进程命令-ps第第 20 页页top命令显示进程top命令用来显示系统当前的进程状况。格
14、式:top选项主要选项如下。d:指定更新的间隔,以秒计算。q:没有任何延迟的更新。如果使用者有超级用户,则top命令将会以最高的优先序执行。c:显示进程完整的路径与名称。S:累积模式,会将已完成或消失的子进程的CPU时间累积起来。s:安全模式。i:不显示任何闲置(Idle)或无用(Zombie)的进程。n:显示更新的次数,完成后将会退出top。6.2.2Linux进程进程命令-top第第 21 页页nice命令改变权限优先级作用:nice命令可以改变程序执行的优先权等级。格式:nice-n-help-version命令主要选项如下。-n或-或-adjustment=:设置欲执行的命令的优先权等
15、级。等级的范围从-2019,其中-20为最高,19为最低。-help:在线帮助。说明:应用程序优先权值的范围从-2019,数字越小,优先权就越高。一般情况下,普通应用程序的优先权值(CPU使用权值)都是0,如果让常用程序拥有较高的优先权等级,自然启动和运行速度都会快些。需要注意的是普通用户只能在019之间调整应用程序的优先权值,只有超级用户有权调整更高的优先权值(从-2019)。例如:nicen1ls1.txt6.2.2Linux进程进程命令-nice第第 22 页页renice命令修改优先级作用:renice命令允许用户修改一个正在运行的进程的优先权。格式:renicepriority-pp
16、ids-gpgrps-uusers主要选项如下。priority:优先等级-ppids:改变该程序的优先权等级,此参数为预设值。-gpgrps:使用程序群组名称,修改所有隶属于该程序群组的程序的优先权。-uuser:指定用户名称,修改所有隶属于该用户的程序的优先权。说明:renice命令可重新调整正在执行的程序的优先权等级。默认是以程序识别码指定程序,调整其优先权,也可以指定程序群组或用户名称调整优先权等级,并修改所有隶属于该程序群组或用户的程序的优先权。等级范围从-2019,只有超级用户可以改变其他用户程序的优先权和设置负数等级。普通用户只能对自己所有的进程使用renice命令。应用实例如下
17、:将进程PID为987及32的进程与进程拥有者为daemon及root的优先级别号加1:#renice+1987udaemonrootp326.2.2Linux进程进程命令-Renice第第 23 页页sleep命令暂停进程作用:sleep命令的功能是使进程暂停执行一段时间。格式:sleepnumber选项主要选项如下。number:时间长度,后面可接s、m、h或d。s:以秒为单位。m:以分钟为单位。h:以小时为单位。d:以天为单位。说明:如果没有指定时间,以秒为单位。此命令大多用于shell程序设计中,使两条命令执行之间停顿指定的时间。应用实例如下。下面的命令使进程先暂停60秒,然后查看哪个
18、用户登录到系统中:#sleep60;who6.2.2Linux进程进程命令-sleep第第 24 页页kill命令杀掉进程作用:kill命令终止一个进程。格式:kill-ssignal|-p-apidkill-lsignal主要选项如下。-s:指定发送的信号。-p:模拟发送信号。-l:指定信号的名称列表。pid:要终止的进程的ID号。signal:表示信号。6.2.2Linux进程进程命令-kill第第 25 页页说明:kill可将指定的信息送至程序。预设的信息为SIGTERM(15),可将指定程序终止。若仍无法终止该程序,可使用SIGKILL(9)信息尝试强制删除程序。kill命令的工作原理
19、是,向Linux系统的内核发送一个系统操作信号和某个程序的进程标志号,然后系统内核就可以对进程标志号指定的进程进行操作。当需要中断一个前台进程的时候,通常使用Ctrl+C组合键;但是对于一个后台进程,就不是一个组合键所能解决的了,这时就必须使用kill命令。应用实例如下。命令执行过程如果出错,用户可用kill来结束任务。对于在后台运行的进程,可以使用kill命令终止:$kill-s918276.2.2Linux进程进程命令-kill第第 26 页页1at命令:定时运行命令作用:at命令在指定时刻执行指定的命令序列。#at2:05tomorrowat/home/mj/airplane/home/
20、mj/air-safeatCtrl+D2crontab命令作用:crontab命令用于安装、删除或者列出用于驱动cron后台进程的任务表。然后,该配置由cron守护进程在设定的时间执行。格式:crontab-uuser文件6.2.2Linux进程进程命令-at第第 27 页页fork()创建一个新进程Wake()唤醒一个进程Schedule()进程调度函数Exit()退出进程6.3.1Linux进程进程函数第第 28 页页运行态运行态拥有拥有CPU进就绪态进就绪态不可中断不可中断进就绪态进就绪态可中断可中断僵尸态僵尸态停止态停止态fork()()wake()资源到位或者资源到位或者收到信号收到
21、信号wake()资源到位schedute()进程调度进程调度sleep_uninterruptible()深度睡眠等待资源Sleep_interruptible()浅度睡眠等待资源exit()收到SIG_KILL或者SIGCONT_wake()6.3.2Linux进程状态第第 29 页页睡眠进程可以被程序再次的唤醒,而僵死进程不会被任何程序唤醒,只能通过命令kill掉深度睡眠和浅度睡眠进程得到它需要的资源被唤醒,通过schedule()进入执行态,深度睡眠的进程不能被信号或者定时中断唤醒,只有它申请的资源又有效是才能被唤醒。Linux中,仅等待CPU时间的进程称为就绪进程,它们被放置在一个运行
22、队列中,一个就绪进程的状态标志位为TASK_RUNNING。一旦一个运行中的进程时间片用完,Linux内核的调度器会剥夺这个进程对CPU的控制权,并且从运行队列中选择一个合适的进程投入运行。当然,一个进程也可以主动释放CPU的控制权。函数schedule()是一个调度函数,它可以被一个进程主动调用,从而调度其它进程占用CPU。一旦这个主动放弃CPU的进程被重新调度占用CPU,那么它将从上次停止执行的位置开始执行,也就是说它将从调用schedule()的下一行代码处开始执行。有时候,进程需要等待直到某个特定的事件发生,例如设备初始化完成、I/O操作完成或定时器到时等。在这种情况下,进程则必须从运
23、行队列移出,加入到一个等待队列中,这个时候进程就进入了睡眠状态。6.3.2Linux进程状态第第 30 页页Linux中,仅等待CPU时间的进程称为就绪进程,它们被放置在一个运行队列中,一个就绪进程的状态标志位为TASK_RUNNING。一旦一个运行中的进程时间片用完,Linux内核的调度器会剥夺这个进程对CPU的控制权,并且从运行队列中选择一个合适的进程投入运行。当然,一个进程也可以主动释放CPU的控制权。函数schedule()是一个调度函数,它可以被一个进程主动调用,从而调度其它进程占用CPU。一旦这个主动放弃CPU的进程被重新调度占用CPU,那么它将从上次停止执行的位置开始执行,也就是
24、说它将从调用schedule()的下一行代码处开始执行。有时候,进程需要等待直到某个特定的事件发生,例如设备初始化完成、I/O操作完成或定时器到时等。在这种情况下,进程则必须从运行队列移出,加入到一个等待队列中,这个时候进程就进入了睡眠状态。6.3.2Linux进程状态第第 31 页页父子进程:除了PID号为0号进程以外的其他进程都是有其他进程创建的,创建进程的进程是父进程,被创建的进程称为子进程。进程是系统中执行的基本单位,liunx系统允许任何而一个用户创建一个子进程。创建之后,子进程存在系统之中,并且独立于父进程。该子进程可以接受系统调度,可以分配得到系统资源,系统可以检测到他的存在,并
25、且赋予他的父进程同样的权利。Linux使用fork函数创建一个新的进程,其函数原型如下:#includePid_tfork(void)6.4.1Linux进程系统调用-创建进程第第 32 页页Fork函数不需要参数,返回值是一个进程ID,返回值分为3种。对于父进程,fork函数返回新创建子进程的ID对于子进程,fork函数返回0如果出错则返回-1Fork函数会创建一个新的进程,并从内核中未此进程得到一个新的ID,之后未这个新进程分配内存空间,并将父进程的进程空间代码的内容复制到子进程的进程空间中,包括父进程的数据段和堆栈段,并且和父进程共享代码段。这时系统各种又多了一个进程,这个进程和父进程一
26、模一样,两个进程都要接受系统的调度。6.4.1Linux进程系统调用-创建进程fork函数第第 33 页页6.4.1Linux进程系统调用-创建进程fork函数第第 34 页页子进程完全复制了父进程的地址空间代内容,包括对战段和数据库的内容,子进程并没有复制代码段,而是和父进程公用代码段。Fork函数的出错情况:Fork有两种情况返回-1系统中已有太多的进程存在了调用fork函数的用户进程太多了一般情况下系统会对进程数目进行限制,如果操作系统对其不加限制,则用户可以利用这一漏洞攻击系统父进程的数据段父进程的数据段父进程的代码段父进程的代码段系统中的其他数据系统中的其他数据子进程的数据段子进程的
27、数据段子进程的代码段子进程的代码段指向父进程的代码段指向父进程的代码段6.4.1Linux进程系统调用-创建进程fork函数第第 35 页页由于fork函数完整的拷贝了父进程的整个地址空间,因此,执行速度比较慢,为了加快fork的执行速度,在有些liunx中使用vfork函数。Vfork函数产生的子进程和父进程完全共享地址空间,包括代码段、数据段和堆栈段,子进程对这些共享资源所做的修改,可以影响到父进程Vfork函数产生的子进程一定比父进程先运行,也就是说父进程调用了vfork函数后会等待子进程运行后再运行。Vfork函数创建新的进程,但他不产生父进程的副本,他通过允许父子进程可访问相同物理内
28、存从而为装了对进程地址的真实拷贝,当子进程需要改变内存中的数据时才会拷贝父进程。这就是著名的“写操作时拷贝”copy-on-write技术6.4.1Linux进程系统调用-创建进程vfork函数第第 36 页页上面讲了进程的fork函数,很多人有疑问既然所有的进程都是fork创建的,那么linux所有的进程不就几乎全部一样吗,而且就我们的常识而言,当我们执行一个程序时,新产生的进程内容就因该是程序的内容才对。Linux环境使用exec函数执行一个新程序,该函数在文件系统中搜索指定的路径文件,并将该文件内容复制到调用的exec函数地址空间,取代原来的进程内容。Exec函数提供了一个在进程中启动另
29、一个程序的方法他可以根据指定的文件名和目录找到可执行代码,并用它来取代原调用进程的数据段代码段和堆栈段,在执行之后,远调用进程的内容中除了进程号以外,其他全部被新的进程替换了。可执行文件可以是二进制文件也可以是liunx下的脚本文件。6.4.2Linux进程系统调用-exec函数第第 37 页页在什么情况下使用exec函数当有进程认为自己不能再为系统和用户作出任何新贡献了,就可以调用任何exec函数族,让自己以新的面貌重生。如果一个进程想执行另一程序,它就可以调用fork函数新建一个进程,然后执行任何一个exec函数,这样看起来就像通过执行应用程序而产生了一个新进程一样。6.4.2Linux进
30、程系统调用-exec函数第第 38 页页所需头文件#includeintexecl(constchar*path,constchar*arg,.);intexeclp(constchar*file,constchar*arg,.);intexecle(constchar*path,constchar*arg,.,char*constenvp);intexecv(constchar*path,char*constargv);intexecvp(constchar*file,char*constargv);intexecve(constchar*path,char*constargv,char*c
31、onstenvp);其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息仍保持原样,颇有些神似三十六计中的金蝉脱壳。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回一个-1,从原程序的调用点接着往下执行。6.4.2Linux进程系统调用-exec函数第第 39 页页Execl的第一个参数是包含路劲的可执行文件,后面是参数列表,列表的第一个为命令path,接着为参数列表,最后必须以null结束。Execlp的
32、第一个参数可以使用绝对路径或者是相对路径Excele最后包含指向一个自定义环境变量列表的指针,此列表必须以null结束。Execv,v表示path后面接受的是一个向量,即指向一个参数列表的指针,注意这个列表的最后一项必须是nullExecvepath后面的接受一个参数列表向量,并可以指定一个环境变量列表向量Execvp,第一个参数可以使用相对或者绝对路径,v表示后面接受一个参数列表向量。6.4.2Linux进程系统调用-exec函数第第 40 页页对于l表示list,说明执行程序的命令参数以列表的方式提供,并且用null结束。参数的个数没有限制。如下:Char*arg0,Char*arg1,C
33、har*arg2,nullV表示所有的参数以指针数组方式给出,其语法为*constargv,这个数组的每一行是一个命令参数。E表示传递给新程序的环境变量列表,这个列表是一个数组,数组的每一行表示一个环境变量,如果没有显示的说明环境变量则新程序继承父进程的环境变量。P表示第一参数不是完整的路径名,这是就要从环境变量中读取环境变量参数组合成一个完整的路径名。那么exec函数就会匹配bin/filename、usr/bin/filename直到找到一个正确路径,就将该文件加载到内存执行,如果该文件不存在则,exec函数出错。6.4.2Linux进程系统调用-exec函数第第 41 页页Exec函数调
34、用hello程序使用exec函数调用当前目录下的hello程序第第 42 页页Exec函数调用psef命令第第 43 页页Exec函数调用hello程序带参数的程序执行./helloc1 hello world第第 44 页页Linux进程标识符#includeintmain(intargc,char*argv)char*envp=PATH=/tmp,USER=lei,STATUS=testing,NULL;char*argv_execv=echo,excutedbyexecv,NULL;char*argv_execvp=echo,executedbyexecvp,NULL;char*argv
35、_execve=env,NULL;if(fork()=0)if(execl(/bin/echo,echo,executedbyexecl,NULL)0)perror(Erronexecl);if(fork()=0)if(execlp(echo,echo,executedbyexeclp,NULL)0)perror(Erronexeclp);if(fork()=0)if(execle(/usr/bin/env,env,NULL,envp)0)perror(Erronexecle);if(fork()=0)if(execv(/bin/echo,argv_execv)0)perror(Erronex
36、ecv);if(fork()=0)if(execvp(echo,argv_execvp)0)perror(Erronexecvp);if(fork()=0)if(execve(/usr/bin/env,argv_execve,envp)0)perror(Erronexecve);第第 45 页页Exec函数注意事项在使用exec函数时一定要加上错误判断语句最常见的错误有找不到制定的文件或者路径在使用argv和envp时忘记用NULL结束没有对应可执行文件的运行权限第第 46 页页退出进程exit函数当一个进程需要退出时,需要调用退出函数这个退出函数会深入到内核注销掉进程的内核数据结构,并且释放
37、进程资源。Linux环境下使用exit函数退出进程,其函数原型如下#includeVoidexit(intstatus)intstatus表示退出状态,这一状态是一个整形值,在shell中可以查看这个值。使用echo$?C程序中的return语句被翻译成exit语句如return1-exit(1)第第 47 页页exit和_exit区别Exit是一个标准的库函数,其内部封装了linux的系统调用函数_exit,两者的主要区别在于exit函数会在用户空间做一些善后工作,如清理用户的I/O缓冲区,将其内容写入磁盘文件,之后在释放用户进程的地址空间,而_exit函数直接进入内核释放用户的地址空间,所
38、有用户空间的缓冲区内容都将丢失。在liunx标准函数库中,有一套称为“高级I/O”的函数,我们熟知的printf()fopen()fread()fwrite()都在此列,他们被称为缓冲IO函数,其特征是每次对应打开文件都会在内存中有一片缓冲区,每次度文件时会读取若干条记录,这样下次读文件就可以直接从缓冲去区中读取,每次写文件件时也是先写到缓冲区,等满足一定条件在将缓冲区中的数据一次性写入文件,这样可以大大提到文件的读写速度。但是当我们使用_exit函数时就会产生问题,因为改函数直到内核接释放进程,就会产生缓冲区数据丢失的情况。第第 48 页页exit和_exit区别进程运行进程运行调用退出函数
39、调用退出函数清理清理I/O缓冲区缓冲区调用调用exit系统调用系统调用进程终止运行进程终止运行_exit()exit()_exit函数直接清除进程的内存空间,并销毁其在内核中的各种数据结构,exit函数在调用exit系统调用之前要检查文件的打开情况,吧文件的缓冲区中的内容回写到文件,对应与图中“清理缓冲区”一项第第 49 页页exit和_exit区别说明:在一个进程调用了说明:在一个进程调用了exit之后,该进程并不会马上完全小时,而是留下一之后,该进程并不会马上完全小时,而是留下一个称为僵尸进程(个称为僵尸进程(Zombie)的数据结构。僵尸进程是一种非常特殊的进程,)的数据结构。僵尸进程是
40、一种非常特殊的进程,它几乎已经放弃了所有的内存空间,没有任何可执行代码,也不能被调度,仅它几乎已经放弃了所有的内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其它进程收集,仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其它进程收集,除此之外,僵尸进程不再占有任何内存空间。除此之外,僵尸进程不再占有任何内存空间。第第 50 页页僵尸进程每个进程都有一个父进程,当进程退出时,其退出状态可以被父进程得到,父进程调用wait函数取得子进程的退出状态信息。这些退出状态信息保存在内核中,占用很少一块内存空间,当子进程退出时,这些信息仍然被保存
41、在内核中,子进程的进程ID也同样保存在系统进程里列表中,这是的进程称为僵尸进程。僵尸进程几乎放弃了所有的资源,包括地址空间,僵尸进程不能执行,因此不能被系统调度,也不会占用cpu时间,僵尸进程占用的资源是微乎其微的,僵尸进程会一直存在于系统中直到其父进程得到其结束状态信息。看起来僵尸进程好像对系统没有什么影响,但是僵尸进程会占用进程pid号,在liunx中严格限制了进程的数量,僵尸进程达到一定数量后就不能再创建新的进程。因此当父进程使用fork函数创建进程而不调用wait函数时一个个僵尸进程就产生了。第第 51 页页创建一个僵尸进程第第 52 页页僵尸进程上面的例子在shell中编译gcczo
42、mbile.cozombile执行./zombile打开另一个终端输入psax父进程休眠时子进程的僵尸状态第第 53 页页Wait函数Linux下使用wait函数得到进程结束状态信息,其函数原型如下:#includePid_twait(int*status)调用wait函数的进程会阻塞,直到该进程的任意一个子进程结束,wait函数会取得结束进程的信息并返回该子进程的pid,子进程的结束信息保存在参数statloc所指向的内存空间中,如果该进程没有子进程则立即出错返回,返回-1,如果在调用wait函数时已经有若干个子进程结束运行了,则wait函数立即返回,但是具体取得哪一个子进程的信息是不确定的
43、,需要根据wait的返回值该子进程的id来判断。Status是一个整形指针,是子进程退出时的状态,如果status为NULL则代表任意结束的子进程。如果不为NULL则代表以指定状态结束结束的子进程第第 54 页页Wait函数参数Wait函数的参数用来保存子进程的返回值,内核会将取得的子进程结束信息保存在该指针指向的内存空间,如果该指针为NULL,则表示拥护对返回值不关心。返回值信息是一个整数,不同的位代表不同的信息,他们是进程正常结束状态、终止进程的信息编号和暂停进程的信息号编号。这三个状态并非每次都有效,用户需要根据*stattus的其他位判断哪些状态信息是有效的。Linux系统提供了专门的
44、宏来判断哪些状态有效并且取得相应的状态值。状态判断宏取值宏进程正常结束WIFEXITED(status)WEXITSTATUS(status)进程异常结束WIFSIGNALED(status)WTERMSIG(status)进程暂停WIFSTOPPED(status)WSTOPSIG(status)第第 55 页页Wait函数参数例如当一个进程正常退出时,其父进程得到其结束信息,则需判断如果WFIEXITED(status)值为真,则说明该进程是正常退出,WEXITSTATUS(status)使用返回信息中进程结束状态即可。下面程序首选创建一个子进程,子进程输出一些信息,提示子进程执行,父进程
45、调用wait函数等待子进程退出,并且收回其退出状态信息,接下来创建一个子进程,该子进程产生一个异常(退出)。父进程同样调用wait函数得到子进程的退出信息,并将其结束状态输出。第第 56 页页Waitpid等待指定的进程Wait函数可以等待指定的进程,并且获得退出信息,但是wait函数只能等待第一个结束的子进程,如果需要指定等待一个子进程则需要如下代码实现:Pid_twaitpid(pid_tpid,int*status,intoptions)参数说明:pid0只等待进程ID等于pid的子进程,而不管其他进程是否结束。Pid=-1等待任何一个子进程退出,这是和wait一样Pid=0等待其组id等于调用进程组id的任何一个子进程Pid0Pc=0Pr=0Pr=pc第第 58 页页exit和_exit区别进程运行进程运行调用退出函数调用退出函数清理清理I/O缓冲区缓冲区调用调用exit系统调用系统调用进程终止运行进程终止运行_exit()exit()_exit函数直接清除进程的内存空间,并销毁其在内核中的各种数据结构,exit函数在调用exit系统调用之前要检查文件的打开情况,吧文件的缓冲区中的内容回写到文件,对应与图中“清理缓冲区”一项第第 59 页页