Linux C编程--进程介绍3--进程终止和等待
- 格式:pdf
- 大小:204.09 KB
- 文档页数:6
void _exit(int status)注意点:1.status表明了进程终止时的状态。
当子进程使用_exit()后,父进程如果在用wait()等待子进程,那么wait()将会返回status状态,注意只有status的低8位(0~255)会返回给父进程。
通常,我们使用0,表示进程成功返回,非负值表示进程不成功返回。
但是,这种约定不是强制的,每个应用程序都可以自己指定。
2.虽然返回值可以是0~255,但是我们通常使用0~128。
因为在shell编程中,如果一个进程被信号打断,shell会返回128+信号编号。
在shell中,这两个值是没有区别的(都是当做进程返回值),如果我们进程中使用了128~255,那么就无法区别到底是信号打断还是进程自己退出了。
void exit(int status)注意点:1.在exit()调用后,退出处理函数(exit handler)首先被执行(这些函数使用atexit()和on_exit()注册)。
2.stdio流缓冲区被刷新。
(还有其他资源的清理)3.最后一步才是调用_exit()退出。
4.在main函数中最后调用return n和调用exit()是相同的作用。
如果在main函数最后调用return或不调用任何退出函数,调用main的运行时函数会自动的调用exit(),但是status会变得不确定(在C89中,status可能会是栈中的某个值,或CPU寄存器中的某个值,这要看编译器是怎么使用的。
在C99中,要求status必须是0)。
注册退出处理函数在程序退出之前,我们都会主动地做一些清理的动作,比如把程序缓冲区的数据保存在文件上。
通常的做法是在exit()之前,调用一些清理函数。
但这种做法的弊端是,在每个可能退出的地方都写上一大堆相同的代码。
另外一种方式就是让程序中所有可能退出的点,都全部集中在一个地方处理。
这样做的问题是会出现大量的判断是否成功语句,让代码显得不清晰。
Linux进程学习总结目录目录 (1)基本概念 (1)fork()和vfork()的学习 (9)孤儿进程和守护进程 (21)exit()和_exit()函数 (32)等待进程结束wait()和waitpid()函数 (37)进程控制函数之exec()函数的学习 (49)基本概念最近一周学习了Linux 进程编程的知识,现对其总结如下。
在第一部分中我们先对进程的基本概念以及在Linux 中是如何来现实进程的进行介绍Tiger-John说明:许多人在学习中只注重如何编程,却忘了注重原理,不去深究其基本原理。
其实操作系统的原理就好比金庸武侠小说的内功一样,而所有的具体实现如:Linux操作系统,uc/os操作系统都只是武功招式而已。
如果我们内功学的很好的话,再来学习具体的实现过程是很快的。
而且也会对其知识有更加本质的了解。
一、进程的基本概念:1.为什么计算机操作系统要引进进程:在操作系统中引入进程的目的是为了使多个程序并发执行,以改善资源利用率及提高系统吞吐量。
2.进程的概念:进程是程序的一次执行,进程是拥有资源的最小单位和调度单位(在引入线程的操作系统中,线程是最小的调度单位)3.进程由什么组成进程由进程控制块(PCB),数据,程序3部分组成。
其中PCB是进程的灵魂。
4.进程的状态:进程的三种最基本的状态是:运行态(running),就绪态(readying), 阻塞态(block)5.进程和程序的区别:进程和程序的主要区别是进程是动态的,程序是静态的。
进程时运行中的程序,程序是一些保存在硬盘上的可执行的代码。
6.进程的优点和缺点(任何事物都是有其两面性。
我们在学习的时候要注意其优点和缺点。
人们也就再发现事物缺点的过程中,不断的去改善它,从而引入了新的事物。
在操作系统的学习过程中,我们会发现很多这样的例子。
人们在不断追求完美的过程中,不断的引入新的知识点--进程和线程的出现就足可以说明这一切)优点:使多个程序并发执行缺点:程序并发执行时付出了巨大的时空开销,每个进程在进行切换时身上带了过多的“累赘”导致系统效率降低。
linux 线程终止的方法线程终止是多线程程序设计中不可或缺的一部分,掌握各种线程终止方法可以提高程序的可读性和稳定性。
本文将介绍线程终止的常见方法,以及如何在Linux系统中实现线程终止。
一、线程终止的常见方法1.使用退出标志位在线程中设置一个退出标志位,当需要终止线程时,将标志位设置为特定值,然后线程在执行过程中检查退出标志位。
当发现退出标志位已设置,线程可以主动结束自己的生命周期。
2.使用信号机制Linux系统提供了丰富的信号,可以通过信号处理函数来响应特定信号,实现线程终止。
例如,可以使用SIGTERM信号来终止线程。
不过,这种方法可能导致线程非正常终止,需要谨慎使用。
3.使用线程同步机制线程同步机制,如互斥锁、信号量等,可以在线程之间传递终止信号。
当主线程需要终止子线程时,通过释放同步资源,子线程在尝试获取同步资源时收到终止信号,从而主动结束线程。
二、Linux中线程终止的机制1.使用pthread_cancel()函数pthread_cancel()函数用于终止指定线程。
该函数会向指定线程发送一个终止信号,如果线程处于可终止状态,则会立即终止线程。
需要注意的是,使用该函数时,线程所在的线程库必须支持线程终止功能。
2.使用线程终止回调函数在线程创建时,可以通过设置线程终止回调函数来实现线程终止。
当线程接收到终止信号时,回调函数会被调用,从而实现线程的有序终止。
三、终止线程的注意事项1.确保线程处于可终止状态在使用线程终止方法前,需要确保线程处于可终止状态。
例如,避免在阻塞状态下使用信号机制终止线程,以免导致数据不一致或系统崩溃。
2.清理资源在终止线程时,需要确保线程所占用的资源得到正确清理。
这包括释放线程局部变量、关闭文件描述符、断开网络连接等。
3.顺序终止线程如果程序中有多个线程,建议按照一定顺序终止线程。
首先终止子线程,然后主线程在所有子线程终止后清理资源,最后终止主线程。
四、总结线程终止是多线程程序设计中重要的一环。
进程的三个基本状态及其转换
摘要:
1.进程基本状态的定义
2.进程状态的转换
3.进程状态转换的实际应用
正文:
1.进程基本状态的定义
在计算机科学中,进程是指正在运行的程序的实例。
进程具有一定的基本状态,这些状态可以反映进程的运行情况。
进程的三个基本状态是:运行(Running)、就绪(Ready)和等待(Waiting)。
- 运行状态:进程正在执行,计算机资源被占用。
- 就绪状态:进程已经准备好执行,但计算机资源尚未分配。
- 等待状态:进程因为等待某项操作完成而暂停执行,如等待输入输出操作完成。
2.进程状态的转换
进程在运行过程中,会根据其所处的环境和操作完成情况,在三个基本状态之间进行转换。
以下是进程状态转换的一些常见情况:
- 就绪状态转运行状态:当进程分配到计算机资源时,它将从就绪状态转换为运行状态,开始执行。
- 运行状态转就绪状态:当进程执行完毕或者遇到等待操作时,它会从运行状态转换为就绪状态,等待下一次执行。
- 等待状态转就绪状态:当进程等待的操作完成时,它会从等待状态转换为就绪状态,等待执行。
- 运行状态转等待状态:当进程需要执行某项操作(如输入输出)时,它会从运行状态转换为等待状态,等待操作完成。
3.进程状态转换的实际应用
了解进程状态转换对于编写高效的程序具有重要意义。
例如,在编写操作系统的调度算法时,需要考虑进程状态转换的规律,以便合理分配计算机资源,提高系统性能。
此外,在编写并发程序时,了解进程状态转换也有助于避免死锁等错误,保证程序正确运行。
Linux 进程概念进程是执行中的程序,这是一种非正式的说法。
进程不只是程序代码,程序代码有时称为文本段。
进程还包括当前活动,通过程序计数器的值和处理器寄存器的内容来表示。
另外,进程通常还包括进程堆栈段(包含临时数据,如方法参数、返回地址和局部变量)和数据段(包含全局变量)。
这里强调,程序本身不是进程;程序只是被动实体,如存储在磁盘上的文件内容,而进程是活动实体,它有一个程序计数器用来表示下一个要执行的指令和相关资源集合。
虽然两个进程可以与同一程序相关,但是它们被当做两个独立的执行序列。
例如,多个用户可运行电子邮件程序中的不同拷贝命令,或者同一用户能调用编辑器程序的多个拷贝命令。
这些都是独立的进程,虽然文本段相同,但是数据段不同。
1.进程状态进程在执行时会改变状态,进程状态部分地由进程的当前活动所定义。
每个进程可能处于下列状态之一:●新建进程正在被创建。
●运行指令正在被执行。
●等待进程等待一定事件的出现(如接收某个信号)。
●就绪进程等待被分配给某个处理器。
●终止进程已完成执行。
这些状态的名称较随意,且随操作系统的不同而变化。
不过,它们所代表的状态可以出现在所有系统上。
有的操作系统更为仔细地描述了进程状态。
在任何时刻一次只能有一个进程可在任何一个处理上运行,尽管许多进程可能处于就绪或等待状态。
2.进程控件块每个进程在操作系统内用进程控制块(Process control block,PCB也称为任务控制块)来表示。
每个任务控制块都包含与特定进程相关的许多信息。
●进程状态状态可包括新的、就绪、运行、等待和停止等。
●程序计数器计数器表示这个进程要执行的下个指令的地址。
●CPU寄存器根据计算机体系结构的不同,寄存器的数量和类型也不同。
它们包括累加器、索引寄存器、堆栈指针、通用寄存器和其他条件码信息寄存器。
与程序计数器一样,这些状态信息在出现中断时也需要被保存,以便进程以后正确地继续执行。
●CPU调度信息这类信息包括进程优先级、调度队列的指针和任何其他高度参数。
Linux 进程1 基本内容进程,作为Linux里面的两大基本抽象之一,笼统来说就是执行中的程序。
这样一个概念涵盖的内容很丰富:汇编代码、数据、状态、各种资源(包括文件、信号、内存等等)。
本文档未包含进程间通信、同步等内容。
1.1 标识一个进程每一个进程,在内核中,都对应了一个task_struct结构体,也称为进程描述符,其中包含了描述一个进程的所有信息(约1.7K字节)。
在内核中,所有的的task_struct都被组织在任务队列这样一个双向链表中,如下图:操作系统通过进程ID pid 来区分不同的进程描述符,该变量为了与老版本的Linux兼容,其最大值的默认值被默认设置为32768。
这样的取值对于一般的个人机是足够使用了,但是有些大型的服务器可能会需要更多的进程。
这个最大值可以进行设置,如果不考虑兼容性,可以设置成超过32768。
既然task_struct记录了所有进程相关的描述信息,不难想象内核大部分对进程的操作都是通过该结构进行,那么高效的访问task_struct就十分重要了。
X86体系中,内核访问task_struct的示意图如下:在进程的内核栈的最底端有一个thread_info结构,其保存了task_struct的地址。
宏current_thread_info()会取到本进程的thread_info,则current_thread_info()->task_struct即可。
current_thread_info的实现值得注意:将栈指针SP的后12 / 13位与0相与。
两种取值分别对应了4k / 8k 的内核栈。
1.2 进程状态Linux进程有5种状态:●TASK_RUNNING:运行状态–正在执行或者在等待执行的任务队列中●TASK_INTERRUPTIBLE:进程睡眠(或者说阻塞)●TASK_UNINTERRUPTIBLE:不可中断–等待时不可被信号唤醒,也因此较上一种使用的较少●TASK_ZOMBIE:僵尸–进程已经结束,但为了父进程能够获得相关信息,内核还保留了该进程的一些相关信息,–直到父进程调用wait4()●TASK_STOPPED / TASK_TRACED:暂停执行、调试状态–一般都是通过特定的信号来实现本状态与运行状态的切换下面是FSM:1.3 进程上下文当一个进程执行了系统调用,或者发生了某个异常,此时它将陷入内核空间,我们称内核将“代表进程执行”,并且处于进程上下文中。
linux结束进程命令
在linux中,进程之间通过信号来通信。
进程的信号就是预定义好⼀个消息,进程能识别它并决定忽略还是做出反应。
信号名称描述
1 HUP 挂起
2 INT 中断
3 QUIT 结束运⾏
9 KILL ⽆条件终⽌
11 SEGV 段错误
15 TERM 尽可能终⽌
17 STOP ⽆条件停⽌运⾏,但不终⽌
18 TSTP 停⽌或暂停,但继续在后台运⾏
19 CONT 在STOP或TSTP之后恢复执⾏
1、kill命令
kill 3940 结束PID是3940的进程,kill会给PID的进程发送⼀个TERM的信号停⽌进程。
有时候⽤kill并不能杀死某些进程,可以加-s指定其他信号杀死进程
如:kill 3940
kill -s HUP 3940
2、killall
killall⾮常强⼤,⽀持通过进程名结束进程。
也⽀持通配符,这在系统因负载过⼤⽽变得很慢时很有⽤。
如:killall http* 结束所有以http开头的进程。
1)Linux程序设计入门--基础知识Linux下C语言编程基础知识前言: 这篇文章介绍在LINUX下进行C语言编程所需要的基础知识.在这篇文章当中,我们将会学到以下内容: 源程序编译 Makefile的编写程序库的链接程序的调试头文件和系统求助--------------------------------------------------------------------------------1.源程序的编译在Linux下面,如果要编译一个C语言源程序,我们要使用GNU的gcc编译器. 下面我们以一个实例来说明如何使用gcc编译器. 假设我们有下面一个非常简单的源程序(hello.c): int main(int argc,char **argv) { printf("Hello Linux\n"); } 要编译这个程序,我们只要在命令行下执行: gcc -o hello hello.c gcc 编译器就会为我们生成一个hello的可执行文件.执行./hello就可以看到程序的输出结果了.命令行中 gcc表示我们是用gcc来编译我们的源程序,-o 选项表示我们要求编译器给我们输出的可执行文件名为hello 而hello.c是我们的源程序文件. gcc编译器有许多选项,一般来说我们只要知道其中的几个就够了. -o选项我们已经知道了,表示我们要求输出的可执行文件名. -c选项表示我们只要求编译器输出目标代码,而不必要输出可执行文件. -g选项表示我们要求编译器在编译的时候提供我们以后对程序进行调试的信息. 知道了这三个选项,我们就可以编译我们自己所写的简单的源程序了,如果你想要知道更多的选项,可以查看gcc的帮助文档,那里有着许多对其它选项的详细说明.2.Makefile的编写假设我们有下面这样的一个程序,源代码如下:main.c#include "mytool1.h"#include "mytool2.h"int main(int argc,char **argv){mytool1_print("hello");mytool2_print("hello");}/* mytool1.h */#ifndef _MYTOOL_1_H#define _MYTOOL_1_Hvoid mytool1_print(char *print_str);#endif/* mytool1.c */#include "mytool1.h"void mytool1_print(char *print_str){printf("This is mytool1 print %s\n",print_str); }/* mytool2.h */#ifndef _MYTOOL_2_H#define _MYTOOL_2_Hvoid mytool2_print(char *print_str);#endif/* mytool2.c */#include "mytool2.h"void mytool2_print(char *print_str){printf("This is mytool2 print %s\n",print_str); }当然由于这个程序是很短的我们可以这样来编译 gcc -c main.c gcc -c mytool1.c gcc -c mytool2.c gcc -o main main.o mytool1.o mytool2.o 这样的话我们也可以产生main 程序,而且也不时很麻烦.但是如果我们考虑一下如果有一天我们修改了其中的一个文件(比如说mytool1.c)那么我们难道还要重新输入上面的命令?也许你会说,这个很容易解决啊,我写一个SHELL脚本,让她帮我去完成不就可以了.是的对于这个程序来说,是可以起到作用的.但是当我们把事情想的更复杂一点,如果我们的程序有几百个源程序的时候,难道也要编译器重新一个一个的去编译? 为此,聪明的程序员们想出了一个很好的工具来做这件事情,这就是make.我们只要执行一下make,就可以把上面的问题解决掉.在我们执行make之前,我们要先编写一个非常重要的文件.--Makefile.对于上面的那个程序来说,可能的一个Makefile的文件是: # 这是上面那个程序的Makefile文件main:main.o mytool1.o mytool2.ogcc -o main main.o mytool1.o mytool2.omain.o:main.c mytool1.h mytool2.hgcc -c main.cmytool1.o:mytool1.c mytool1.hgcc -c mytool1.cmytool2.o:mytool2.c mytool2.hgcc -c mytool2.c有了这个Makefile文件,不过我们什么时候修改了源程序当中的什么文件,我们只要执行 make命令,我们的编译器都只会去编译和我们修改的文件有关的文件,其它的文件她连理都不想去理的.下面我们学习Makefile是如何编写的. 在Makefile中#开始的行都是注释行.Makefile中最重要的是描述文件的依赖关系的说明.一般的格式是:target: componentsTAB rule第一行表示的是依赖关系.第二行是规则. 比如说我们上面的那个Makefile文件的第二行 main:main.o mytool1.o mytool2.o 表示我们的目标(target)main的依赖对象(components)是main.o mytool1.o mytool2.o 当倚赖的对象在目标修改后修改的话,就要去执行规则一行所指定的命令.就象我们的上面那个Makefile第三行所说的一样要执行gcc -o main main.o mytool1.o mytool2.o 注意规则一行中的TAB表示那里是一个TAB键Makefile有三个非常有用的变量.分别是$@,$^,$<代表的意义分别是: $@--目标文件,$^--所有的依赖文件,$<--第一个依赖文件. 如果我们使用上面三个变量,那么我们可以简化我们的Makefile文件为: # 这是简化后的Makefilemain:main.o mytool1.o mytool2.ogcc -o $@ $^main.o:main.c mytool1.h mytool2.hgcc -c $<mytool1.o:mytool1.c mytool1.hgcc -c $<mytool2.o:mytool2.c mytool2.hgcc -c $<经过简化后我们的Makefile是简单了一点,不过人们有时候还想简单一点.这里我们学习一个Makefile的缺省规则 ..c.o: gcc -c $< 这个规则表示所有的 .o文件都是依赖于相应的.c文件的.例如mytool.o依赖于mytool.c 这样Makefile还可以变为: # 这是再一次简化后的Makefilemain:main.o mytool1.o mytool2.ogcc -o $@ $^..c.o:gcc -c $<好了,我们的Makefile 也差不多了,如果想知道更多的关于Makefile规则可以查看相应的文档.3.程序库的链接试着编译下面这个程序/* temp.c */#include <math.h>int main(int argc,char **argv){double value;printf("Value:%f\n",value);}这个程序相当简单,但是当我们用 gcc -o temp temp.c 编译时会出现下面所示的错误. /tmp/cc33Kydu.o: In function `main': /tmp/cc33Kydu.o(.text+0xe): undefined reference to `log' collect2: ld returned 1 exit status 出现这个错误是因为编译器找不到log的具体实现.虽然我们包括了正确的头文件,但是我们在编译的时候还是要连接确定的库.在Linux下,为了使用数学函数,我们必须和数学库连接,为此我们要加入 -lm 选项. gcc -o temp temp.c -lm这样才能够正确的编译.也许有人要问,前面我们用printf函数的时候怎么没有连接库呢?是这样的,对于一些常用的函数的实现,gcc编译器会自动去连接一些常用库,这样我们就没有必要自己去指定了. 有时候我们在编译程序的时候还要指定库的路径,这个时候我们要用到编译器的 -L选项指定路径.比如说我们有一个库在/home/hoyt/mylib下,这样我们编译的时候还要加上 -L/h ome/hoyt/mylib.对于一些标准库来说,我们没有必要指出路径.只要它们在其缺省库的路径下就可以了.系统的缺省库的路径/lib /usr/lib /usr/local/lib 在这三个路径下面的库,我们可以不指定路径. 还有一个问题,有时候我们使用了某个函数,但是我们不知道库的名字,这个时候怎么办呢 ?很抱歉,对于这个问题我也不知道答案,我只有一个傻办法.首先,我到标准库路径下面去找看看有没有和我用的函数相关的库,我就这样找到了线程(thread)函数的库文件(libp thread.a). 当然,如果找不到,只有一个笨方法.比如我要找sin这个函数所在的库. 就只好用 nm -o /lib/*.so|grep sin>;~/sin 命令,然后看~/sin文件,到那里面去找了. 在sin文件当中,我会找到这样的一行libm-2.1.2.so:00009fa0 W sin 这样我就知道了sin在libm-2.1.2.so库里面,我用 -lm选项就可以了(去掉前面的lib和后面的版本标志,就剩下m了所以是 -lm). 如果你知道怎么找,请赶快告诉我,我回非常感激的.谢谢!4.程序的调试我们编写的程序不太可能一次性就会成功的,在我们的程序当中,会出现许许多多我们想不到的错误,这个时候我们就要对我们的程序进行调试了. 最常用的调试软件是gdb.如果你想在图形界面下调试程序,那么你现在可以选择xxgdb.记得要在编译的时候加入 -g 选项.关于gdb的使用可以看gdb的帮助文件.由于我没有用过这个软件,所以我也不能够说出如何使用. 不过我不喜欢用gdb.跟踪一个程序是很烦的事情,我一般用在程序当中输出中间变量的值来调试程序的.当然你可以选择自己的办法,没有必要去学别人的.现在有了许多IDE环境,里面已经自己带了调试器了.你可以选择几个试一试找出自己喜欢的一个用.5.头文件和系统求助有时候我们只知道一个函数的大概形式,不记得确切的表达式,或者是不记得这函数在那个头文件进行了说明.这个时候我们可以求助系统. 比如说我们想知道fread这个函数的确切形式,我们只要执行 man fread 系统就会输出这函数的详细解释的.和这个函数所在的头文件<stdio.h>说明了. 如果我们要write这个函数的说明,当我们执行man write时,输出的结果却不是我们所需要的. 因为我们要的是w rite这个函数的说明,可是出来的却是write这个命令的说明.为了得到write的函数说明我们要用 man 2 write. 2表示我们用的write这个函数是系统调用函数,还有一个我们常用的是3表示函数是C的库函数. 记住不管什么时候,man都是我们的最好助手.------------------------------------------------------------------------好了,这一章就讲这么多了,有了这些知识我们就可以进入激动人心的Linux下的C程序探险活动.2)Linux程序设计入门--进程介绍Linux下进程的创建前言: 这篇文章是用来介绍在Linux下和进程相关的各个概念.我们将会学到: 进程的概念进程的身份进程的创建守护进程的创建----------------------------------------------------------------------------1.进程的概念Linux操作系统是面向多用户的.在同一时间可以有许多用户向操作系统发出各种命令.那么操作系统是怎么实现多用户的环境呢? 在现代的操作系统里面,都有程序和进程的概念.那么什么是程序,什么是进程呢? 通俗的讲程序是一个包含可以执行代码的文件,是一个静态的文件.而进程是一个开始执行但是还没有结束的程序的实例.就是可执行文件的具体实现. 一个程序可能有许多进程,而每一个进程又可以有许多子进程.依次循环下去,而产生子孙进程. 当程序被系统调用到内存以后,系统会给程序分配一定的资源(内存,设备等等)然后进行一系列的复杂操作,使程序变成进程以供系统调用.在系统里面只有进程没有程序,为了区分各个不同的进程,系统给每一个进程分配了一个ID(就象我们的身份证)以便识别. 为了充分的利用资源,系统还对进程区分了不同的状态.将进程分为新建,运行,阻塞,就绪和完成五个状态. 新建表示进程正在被创建,运行是进程正在运行,阻塞是进程正在等待某一个事件发生,就绪是表示系统正在等待CPU来执行命令,而完成表示进程已经结束了系统正在回收资源. 关于进程五个状态的详细解说我们可以看《操作系统》上面有详细的解说。
进程的三个基本状态
(1)就绪状态:进程已获得除CPU外的所有必要资源,只等待CPU时的状态。
一个系统会将多个处于就绪状态的进程排成一个就绪队列。
(2)执行状态:进程已获CPU,正在执行。
单处理机系统中,处于执行状态的进程只一个;多处理机系统中,有多个处于执行状态的进程。
(3)阻塞状态:正在执行的进程由于某种原因而暂时无法继续执行,便放弃处理机而处于暂停状态,即进程执行受阻。
(这种状态又称等待状态或封锁状态)
通常导致进程阻塞的典型事件有:请求I/O,申请缓冲空间等。
一般,将处于阻塞状态的进程排成一个队列,有的系统还根据阻塞原因不同把这些阻塞集成排成多个队列。
在一些系统中,进程还有一种很重要的状态是:挂起状态(是该进程暂时不接受调度)。
另外,在实际系统中,为管理需要,还存在着两种比较常见的状态:创建状态和终止状态。
创建状态:此时,进程已经拥有了字节的PCB,但该进程所必需的资源或其它信息(如主存资源)尚未分配,进程自身还未进入主存,即创建工作尚未完成,进程还不能够被调度运行。
(创建进程的两个步骤:为一个新进程创建PCB,并填写必要管理信息;把该进程转入就绪状态并插入就绪队列。
)
终止状态:进程的终止首先要等待操作系统进行善后处理,然后将其PCB清零,并将PCB 空间返还系统。
(当一个进程到达自然结束点或出现了无法克服的错误,或是被操作系统或其它有终止权的进程所终结,它将进入终止状态。
进入终止状态的进程不能再执行,但在操作系统中依然保留一个记录,其中保存状态码和一些计时统计数据,供其它进程收集。
一旦其它进程完成了对终止状态进程的信息提取之后,操作系统将删除该进程。
进程结束1.在Linux中任何让一个进程结束进程退出表示进程即将结束。
在Linux中进程退出分为了正常退出和异常退出两种。
1>正常退出a. 在main()函数中执行return 。
b.调用exit()函数c.调用_exit()函数2>异常退出a.调用about函数b.进程收到某个信号,而该信号使程序终止。
不管 是哪种退出方式,系统最终都会执行内核中的同一代码。
这段代码用来关闭进程所用已打开的文件描述符,释放它所占用的内存和其他资源。
3.比较以上几种退出方式的不同点(1)exit和return 的区别:a.exit是一个函数,有参数。
exit执行完后把控制权交给系统b.return是函数执行完后的返回。
renturn执行完后把控制权交给调用函数。
(2)exit和abort的区别:a.exit是正常终止进程b.about是异常终止。
exit()和_exit()函数exit()和_exit()的学习1>exit和_exit函数都是用来终止进程的。
当程序执行到exit或_exit时,系统无条件的停止剩下所有操作,清除包括PCB在内的各种数据结构,并终止本进程的运行。
2>exit在头文件stdlib.h中声明,而_exit()声明在头文件unistd.h中声明。
exit中的参数exit_code为0代表进程正常终止,若为其他值表示程序执行过程中有错误发生。
3>exit()和_exit()的区别:a._exit()执行后立即返回给内核,而exit()要先执行一些清除操作,然后将控制权交给内核。
b. 调用_exit函数时,其会关闭进程所有的文件描述符,清理内存以及其他一些内核清理函数,但不会刷新流(stdin, stdout, stderr ...). exit函数是在_exit函数之上的一个封装,其会调用_exit,并在调用之前先刷新流。
exit()函数与_exit()函数最大区别就在于exit()函数在调用exit系统之前要检查文件的打开情况,把文件缓冲区的内容写回文件。
由于Linux的标准函数库中,有一种被称作“缓冲I/O”的操作,其特征就是对应每一个打开的文件,在内存中都有一片缓冲区。
每次读文件时,会连续的读出若干条记录,这样在下次读文件时就可以直接从内存的缓冲区读取;同样,每次写文件的时候也仅仅是写入内存的缓冲区,等满足了一定的条件(如达到了一定数量或遇到特定字符等),再将缓冲区中的内容一次性写入文件。
这种技术大大增加了文件读写的速度,但也给编程代来了一点儿麻烦。
比如有一些数据,认为已经写入了文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时用_exit()函数直接将进程关闭,缓冲区的数据就会丢失。
因此,要想保证数据的完整性,就一定要使用exit()函数。
几个要点:1.不管进程如何终止,最后都会执行内核中的同一段代码:为相应进程关闭所有打开描述符,释放内存等等。
2.若父进程在子进程之前终止了,则子进程的父进程将变为init进程,其PID为1;保证每个进程都有父进程。
3.当子进程先终止,父进程如何知道子进程的终止状态?事实上,内核为每个终止子进程保存了终止状态等信息,父进程调用wait等函数,就可获取该信息。
4.当父进程调用wait等函数后,内核将释放终止进程所使用的所有内存,关闭其打开的所有文件。
5.对于已经终止、但是其父进程尚未对其调用wait等函数的进程,被称为僵尸进程(即已经结束,但尚未释放资源的)。
6.对于父进程先终止,而被init领养的进程会是僵尸进程吗?init对每个终止的子进程,都会调用wait函数,获取其终止状态信息。
综上所述,子进程调用exit后,父进程必须调用wait。
进程等待系统中的僵尸进程都要由wait系统调用来回收,下面就通过实战看一看wait的具体用法:wait的函数原型是:#include <sys/types.h> /* 提供类型pid_t的定义 */#include <sys/wait.h>pid_t wait(int *status);进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程, wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。
但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:pid = wait(NULL);如果成功,wait 会返回被收集的子进程的进程ID ,如果调用进程没有子进程,调用就会失败,此时wait 返回1,同时errno 被置为ECHILD 。
下面给出两个示例1.不同类型结束进程的状态字示例[cpp] view plain copy print ?01. #i n c l u d e <s y s /t y p e s .h > 02. #i n c l u d e <s y s /w a i t .h > 03. #i n c l u d e <s t d i o .h > 04. /* 处理并打印状态字的子函数*/05. v o i d h _e x i t (i n t s t a t u s )06. { 07. i f (W I F E X I T E D (s t a t u s ))08. p r i n t f ("n o r m a l t e r m i n a t i o n , e x i t s t a t u s =%d \n ", W E X I T S T A T U S (s t a t u s )); 09. e l s e i f (W I F S I G N A L E D (s t a t u s ))10. {11.12. p r i n t f ("a b n o r m a l t e r m i n a t i o n , s i g n a l n u m b e r =%d %s \n ", W T E R M S I G (s t a t u s ),13. #i f d e f W C O R E D U M P14. W C O R E D U M P (s t a t u s ) ? " )" : "(c o r e f i l e g e n e r a t e d )");15.#e l s e 16.") "); 17.#e n d i f 18.} 19.} 20./*主函数。
示范三种结束进程的不同方式,并调用h _e x i t 函数处理返回状态字*/ 21.i n t m a i n () 22.{ 23.p i d _t p i d ; 24.i n t s t a t u s ; 25./*子程序正常退出 */ 26.i f ((p i d =f o r k ())<0) 27.{ 28.p r i n t f ("f o r k e r r o r \n "); 29.e x i t (0); 30.} 31.e l s e if (p i d ==0) 32.e x i t (7); 33.i f (w a i t (&s t a t u s )!=p i d ) /*等待子进程*/ 34.{ 35.p r i n t f ("w a i t e r r o r \n "); 36.e x i t (0); 37.} 38.h _e x i t (s t a t u s ); /*打印状态 */ 39./*子进程a b o r t 终止 */ 40.i f ((p i d =f o r k ())<0) 41.{ 42.p r i n t f ("f o r k e r r o f \n "); 43.e x i t (0); 44. }2.waitpid 的使用45.e l s e if (p i d ==0) /*子进程*/ 46. a b o r t (); /*产生信号S I G A B R T 终止进程*/ 47. i f (w a i t (&s t a t u s )!=p i d ) /*等待子进程*/ 48. { 49. p r i n t f ("w a i t e r r o r .\n "); 50. e x i t (0); 51. } 52. h _e x i t (s t a t u s ); /*打印状态*/ 53. /* 子进程除零终止 */ 54. i f ((p i d =f o r k ())<0) 55. { 56. p r i n t f ("f o r k e r r o f \n "); 57. e x i t (0); 58. } 59. e l s e i f (p i d ==0) /*子进程 */ 60. s t a t u s /=0; /*除数为0产生S I G F P E */ 61. i f (w a i t (&s t a t u s )!=p i d ) 62. { 63. p r i n t f ("w a i t e r r o r .\n "); 64. e x i t (0); 65. } 66. h _e x i t (s t a t u s ); /*打印状态*/ 67. 68.e x i t (0); 69. }[cpp] view plain copy print ?01. #i n c l u d e <s y s /t y p e s .h > 02. #i n c l u d e <s y s /w a i t .h > 03. #i n c l u d e <s t d i o .h > 04. i n t m a i n () 05. { 06. p i d _t p i d ; 07. 08. i f ((p i d =f o r k ())<0) 09. { 10. p r i n t f ("f o r k e r r o r .\n "); 11. e x i t (0); 12. } 13. e l s e i f (p i d ==0) 14. { 15. i f ((p i d =f o r k ())<0) 16. { 17. p r i n t f ("f o r k e r r o r .\n "); 18. e x i t (0); 19. } 20. e l s e i f (p i d >0) 21. e x i t (0); 22. s l e e p (2);23. p r i n t f("s e c o n d c h i l d, p a r e n t p i d=%d \n", g e t p p i d());24. e x i t(0);25. }26.27. i f(w a i t p i d(p i d, N U L L, 0)!=p i d)28. {29. p r i n t f("w a i t p i d e r r o r.\n");30. e x i t(0);31. }32. e x i t(0);33. }说明:WIFEXITED(status)如果子进程正常结束则为非0 值。