linux c 笔记 进程控制(三)

进程退出
    进程结束表示进程即将结束运行,在linux系统中进程的退出方法分为正常退出和异常退出两种.
exit函数
进程有三种正常终止法及两种异常终止法。
(1) 正常终止:
    (a) 在main函数内执行return语句。这等效于调用 exit。
    (b) 调用exit函数。此函数由ANSI C定义,其操作包括调用各终止处理程序(终止处理程序在调用atexit函数时登录),然后关闭所有标准I/O流等。因为ANSI C并不处理文件描述符、多进程(父、子进程)以及作业控制,所以这一定义对 UNIX系统而言是不完整的。
    (c) 调用_exit系统调用函数。此函数由 exit调用,它处理 UNIX特定的细节。
(2) 异常终止:
    (a) 调用abort。它产生SIGABRT信号,所以是下一种异常终止的一种特例。
    (b) 当进程接收到某个信号时,而该信号使程序终止。进程本身(例如调用abort函数)、其他进程和内核都能产生传送到某一进程的信号。例如,进程越出其地址空间访问存储单元,或者除以 0,内核就会为该进程产生相应的信号。
    不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等等。

对退出方式的比较:
    exit和return 的区别:exit 是一个函数,有参数,return 是函数执行完毕后的返回,exit把控制权交给系统,而return将控制权交给调用函数
    exit和about的区别:exit是正常终止程序,about 是异常终止程序
    exit( int exit_code ):exit中的参数exit_code 为0代表进程正常终止,若为其他值表示程序执行过程中有错误发生,比如:溢出,除数为0.
    exit()和_exit()的区别:exit在头文件 stdlib.h中声明,而_exit()声明在头文件unistd.h中,两个函数均能正常终止进程,但是_exit()会执行后立即返回给内核,而exit()要先执行一些清除操作,然后将控制权交给内核.

对上述任意一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于exit和_exit,这是依靠传递给它们的退出状态( exit status)参数来实现的。在异常终止情况,内核(不是进程本身)产生一个指示其异常终止原因的终止状态( termination status)。在任意一种情况下,该终止进程的父进程都能用 wait或waitpid函数得其终止状态。
注意,这里使用了“退出状态”(它是传向exit或_exit的参数,或main的返回值)和“终止状态”两个术语,以表示有所区别。在最后调用 _exit时内核将其退出状态转换成终止状态。明了父进程检查子进程的终止状态的不同方法。如果子进程正常终止,则父进程可以获得子进程的退出状态。

在说明fork函数时,一定是一个父进程生成一个子进程。上面又说明了子进程将其终止状态返回给父进程。但是如果父进程在子进程之前终止,对于其父进程已经终止的所有进程,它们的父进程都改变为 init进程。我们称这些进程由 init进程领养。其操作过程大致是:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止的进程的子进程,如果是,则该进程的父进程 ID就更改为1 ( init进程的I D )。这种处理方法保证了每进程有一个父进程。
  
另一个我们关心的情况是如果子进程在父进程之前终止,那么父进程又如何能在做相应检查时得到子进程的终止状态呢?对此问题的回答是内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用 wait或 waitpid 时,可以得到有关信息。这种信息至少包括进程ID、该进程的终止状态、以反该进程使用的 CPU 时间总量。内核可以释放终止进程所使用的所有存储器,关闭其所有打开文件。在 UNIX 术语中,一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程被称为僵死进程( zombie)。如果编写一个长期运行的程序,它fork了很多子进程,那么除非父进程等待取得子进程的终止状态,否则这些子进程就会变成僵死进程。

执行新程序:
exec函数
用fork函数创建子进程后,子进程往往要调用一种 exec函数以执行另一个程序。当进程调用一种 exec函数时,该进程完全由新程序代换,而新程序则从其 main函数开始执行。
因为调用exec并不创建新进程,所以前后的进程 ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。
有六种不同的exec函数可供使用,它们常常被统称为 exec函数。这些exec函数都是 UNIX 进程控制原语。用fork可以创建新进程,用 exec可以执行新的程序。 exit函数和两个wait函数处理
终止和等待终止。这些是我们需要的基本的进程控制原语。在后面各节中将使用这些原语构造另外一些如popen和system之类的函数。
#include <unistd.h>
int execl ( const char * pathname, const char  *arg 0, . . . / * ( char * ) 0 * / ) ;
int execv ( const char *pathname, char * const argv [] );
int execle ( const char * pathname, const char  * arg 0, ...);
int execve ( const char * pathname, char * const argv [ ] , char * const envp [] );
int execlp ( const char * filename, const char  *arg 0, . . . / * ( char * ) 0 * / ) ;
int execvp ( const charfile * name, char * const argv [] );
六个函数返回:若出错则为- 1,若成功则不返回

这些函数之间的第一个区别是前四个取路径名作为参数,后两个则取文件名作为参数。当
指定filename作为参数时:
• 如果filename中包含/,则就将其视为路径名。
• 否则就按PATH环境变量,在有关目录中搜寻可执行文件。
PATH变量包含了一张目录表 (称为路径前缀 ),目录之间用冒号 ( : )分隔。例如下列 name = value环境字符串:PATH=/bin:/usr/bin:/usr/local/bin :.
指定在四个目录中进行搜索。( 零长前缀也表示当前目录。在 value的开始处可用:表示,在行中间则要用::表示,在行尾以:表示。)

如果execlp和execvp中的任意一个使用路径前缀中的一个找到了一个可执行文件,但是该文件不是由连接编辑程序产生的机器可执行代码文件,则就认为该文件是一个 shell脚本,于是试着调用/bin/sh,并以该filename作为shell的输入。
第二个区别与参数表的传递有关 (  l表示表( list ),v表示矢量 ( vector )  )。函数execl、execlp和execle要求将新程序的每个命令行参数都说明为一个单独的参数。这种参数表以空指针结尾。
对于另外三个函数( execv, execvp和execve ),则应先构造一个指向各参数的指针数组,然后将该数组地址作为这三个函数的参数。
在使用ANSI C 原型之前,对execl , execle和execlp三个函数表示命令行参数的一般方法是:char * arg 0, char * arg 1, . . . , char  * arg n, ( char * ) 0
应当特别指出的是:在最后一个命令行参数之后跟了一个空指针。如果用常数 0来表示一个空指针,则必须将它强制转换为一个字符指针,否则它将被解释为整型参数。如果一个整型数的长度与char * 的长度不同,exec函数实际参数就将出错。
最后一个区别与向新程序传递环境表相关。以 e 结尾的两个函数( execle和 execve)可以传递一个指向环境字符串指针数组的指针。其他四个函数则使用调用进程中的
environ变量为新程序复制现存的环境。通常,一个进程允许将其环境传播给其子进程,但有时也有这种情况,进程想要为子进程指定一个确定的环境。例如,在初始化一个新登录的 shell时, login程序创建一个只定义少数几个变量的特殊环境,而在我们登录时,可以通过 shell起动文件,将其他变量加到环境中

环境变量
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,如:临时文件夹位置和系统文件夹位置等。
环境变量是在操作系统中一个具有特定名字的对象,它包含了一个或者多个应用程序所将使用到的信息。例如Windows和DOS操作系统中的path环境变量,当要求系统运行一个程序而没有告诉它程序所在的完整路径时,系统除了在当前目录下面寻找此程序外,还应到path中指定的路径去找。用户通过设置环境变量,来更好的运行进程。
如同前述,环境字符串的形式是:
name = value
UNIX内核并不关心这种字符串的意义,它们的解释完全取决于各个应用程序。例如, shell使用了大量的环境变量。其中某一些在登录时自动设置(如 HOME,USER等),有些则由用户设置。我们通常在一个 shell起动文件中设置环境变量以控制 shell的动作。例如,若设置了环境变量 MAILPATH,则它告诉Bourne shell和KornShell到哪里去查看邮件。
ANSI C定义了一个函数getenv,可以用其取环境变量值,但是该标准又称环境的内容是由实现定义的。
#include <stdlib.h>
char * getenv ( const char *  name) ;
返回:指向与 name关联的value的指针,若未找到则为 NULL
注意,此函数返回一个指针,它指向 name = value字符串中的value。我们应当使用 getenv 从环境中取一个环境变量的值,而不是直接存取 environ。

execv函数:execv函数是通过路径名的方式调用可执行文件作为新的进程映像.他的argv参数用来提供给main函数的argv参数.argv参数是一个以NULL结尾(最后一个元素必须是一个空指针)的字符串数组
execve函数:在该系统中,参数path是将要执行的程序的路径名,参数argv,envp与main函数的argv,envp对应.
execl函数:该函数与execl函数的用法相似,只是在传参数的时候(参数中使用"..."说明参数的个数是不确定的),要注意的是这些参数要以一个空指针作为结束
execl函数:该函数与execl函数的用法类似,只是要显式指定环境变量.环境变量位于命令行参数最后一个参数的后面,也就是空指针之后.
execvp函数:该函数和execv函数的用法类似,不同的是参数filename. 该参会素如果包含"/",就相当于路径名,如果不包含"/",函数就到PATH环境变量定义的目录中寻找可执行的文件,PATH环境变量的目录之间以冒号隔开.
execlp函数:该函数类似于execl函数,他们的区别和execvp 和execv的区别一样.

exec函数族的6个函数中只有execve是系统调用,其他五个都是库函数,它们实现时都调用了execve

执行了新程序后的进程除了保存了原有的进程ID,父进程ID,实际用户ID,和实际组ID之外,进程还保持了许多原有的特征,主要有:
    .当前的工作目录
    .根目录
    .创建文件是使用的屏蔽字
    .进程信号屏蔽字
    .未决警告
    .和进程相关的使用处理器的时间
    .控制终端
    .文件锁

等待进程结束
在子进程先于父进程退出时,如果父进程没有调用wait 和waitpid函数,子进程就会进入僵死状态.如果父进程调用了wait 和waitpid函数,就不会使子进程变成僵尸进程,这
当一个进程正常或异常终止时,内核就向其父进程发送 SIGCHLD信号。因为子进程终止是个异步事件 (这可以在父进程运行的任何时候发生 ),所以这种信号也是内核向父进程发的异步通知。父进程可以忽略该信号,或者提供一个该信号发生时即被调用执行的函数 (信号处理程序)。对于这种信号的系统默认动作是忽略它。调用wait或waitpid的进程可能会:
• 阻塞(如果其所有子进程都还在运行 )。
• 带子进程的终止状态立即返回 (如果一个子进程已终止,正等待父进程存取其终止状态 )。
• 出错立即返回(如果它没有任何子进程)。
如果进程由于接收到 SIGCHLD信号而调用 wait,则可期望wait会立即返回。但是如果在一任一时刻调用 wait,则进程可能会阻塞。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait ( int *statloc) ;
pid_t waitpid ( pid_t pid, int * statloc, int options) ;
两个函数返回:若成功则为进程 ID,若出错则为- 1

• 在一个子进程终止前, wait 使其调用者阻塞,而 waitpid 有一选择项,可使调用者不阻塞。
• waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的进程。

如果一个子进程已经终止,是一个僵死进程,则 wait立即返回并取得该子进程的状态,否则w a i t使其调用者阻塞直到一个子进程终止。如调用者阻塞而且它有多个子进程,则在其一个子进程终止时, wait就立即返回。因为 wait返回终止子进程的进程 ID,所以它总能了解是哪一个子进程终止了。

这两个函数的参数 statloc是一个整型指针。如果 statloc不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内。如果不关心终止状态,则可将该参数指定为空指针。
依据传统,这两个函数返回的整型状态字是由实现定义的。其中某些位表示退出状态(正常返回),其他位则指示信号编号(异常返回),有一位指示是否产生了一个 core文件等等。

对于waitpid的pid参数的解释与其值有关:
• pid == -1 等待任一子进程。于是在这一功能方面 waitpid与wait等效。
• pid > 0 等待其进程ID与pid相等的子进程。
• pid == 0 等待其组ID等于调用进程的组I D的任一子进程。
• pid < -1 等待其组ID等于pid的绝对值的任一子进程。
 waitpid返回终止子进程的进程 ID,而该子进程的终止状态则通过 statloc返回。对于wait,其唯一的出错是调用进程没有子进程 (函数调用被一个信号中断时,也可能返回另一种出错。但是对于 waitpid,如果指定的进程或进程组不存在,或者调用进程没有子进程都能出错。
options参数使我们能进一步控制 waitpid的操作。此参数或者是 0,或者是表8 - 2中常数的逐
位或运算。

waitpid函数提供了wait函数没有提供的三个功能:
(1) waitpid等待一个特定的进程 (而wait则返回任一终止子进程的状态 )。
(2) waitpid提供了一个wait的非阻塞版本。有时希望取得一个子进程的状态,但不想阻塞。
(3) waitpid支持作业控制(以 WUNTRACED选择项)。

时间: 2024-08-01 00:13:42

linux c 笔记 进程控制(三)的相关文章

linux c 笔记 进程控制(一)

1.进程简述 进程是一个动态的实体,操作系统资源分配的基本单位,每个进程都有一个非负整型的唯一进程 ID.因为进程 ID标识符总是唯一的,常将其用做其他标识符的一部分以保证其唯一性.    1)进程标识:    每个进程都有一个非负整型的唯一进程 ID.因为进程 ID标识符总是唯一的,常将其用做其他标识符的一部分以保证其唯一性.tmpnam 函数将进程 ID作为名字    的一部分创建一个唯一的路径名.    有某些专用的进程:进程 ID 0是调度进程,常常被称为交换进程 ( swapper )

linux c 笔记 进程控制(二)---守护进程

守护进程(Daemon),一说精灵进程,是指在后台运行的,没有控制终端与之相连的程序.它独立于控制终端周期性地执行某种任务或等待处理某些发生的事件.它是一个生存期较长的进程,守护进程常常在系统引导装入时启动,在系统关闭时终止.Linux系统有很多守护进程,大多数服务都是通过守护进程实现的,同时,守护进程还能完成许多系统任务,例如,作业规划进程crond.打印进程lqd等(这里的结尾字母d就是Daemon的意思).由于在Linux中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的

linux c 笔记 进程控制(四)

一.更改用户 I D和组I D可以用setuid函数设置实际用户ID和有效用户ID.与此类似,可以用 setgid函数设置实际组ID和有效组ID.int setgid(gid_t gid) ;两个函数返回:若成功则为 0,若出错则为- 1关于谁能更改 ID有若干规则.现在先考虑有关改变用户 I D的规则(在这里关于用户 ID所说明的一切都适用于组 ID).(1) 若进程具有超级用户特权,则 setuid函数将实际用户 ID.有效用户 ID,以及保存的设置-用户- ID设置为uid.(2) 若进程

linux下的 进程控制 以及常见的进程控制函数

进程控制: 1. 进程创建函数: fork(); 头文件: #include<sys/types.h> #include<unistd.h> 函数原型: pid_t fork(void); 函数返回值:0:表示此进程现在是子进程: -1:表示出错: 子进程ID号:(大于零的整数):表示现在此进程时父进程,接收到的ID号是子进程的ID号: 2.fork()返回-1(也就是进程创建出错的原因) 1.系统中拥有了太多的进程:超过了系统的限制:(系统级) 2.该用户有了太多的进程,超过了C

linux 命令及进程控制

main.c  main.o/main.obj  main/main.exe          编译                连接 程序运行;      两步: gcc/g++  -c  main.c/main.cpp  -> main.o              gcc/g++ -o main  main.o -> main(可执行文件)     一步:  gcc -o main mian.c  -> main    工程文件:       rm  *.o     gcc  -

linux与Windows进程控制

进程管理控制 这里实现的是一个自定义timer用于统计子进程运行的时间.使用方式主要是 timer [-t seconds] command arguments 例如要统计ls的运行时间可以直接输入timer ls,其后的arguments是指所要运行的程序的参数.如:timer ls -al.如果要指定程序运行多少时间,如5秒钟,可以输入timer -t 5 ls -al.需要注意的是,该程序对输入没有做异常检测,所以要确保程序输入正确. Linux 程序思路 获取时间 时间获取函数使用get

linux c 笔记 线程控制(三)

错误检查函数执行错误时,一般都会返回一个特定的值,比如-1,空指针,这些值只能说明有错误发生,但错误的原因没有说明,头文件<errno.h>定义了变量errno,它储存了错误发生时的错误码,通过错误码可以得到错误的描述信息,#include <errno.h>#ifndef errnoextern int errno;#endif程序开始执行时,变量errno被初始化为0 ,许多库函数在执行过程中遇到错误就会将errno设置为相应的错误码,函数被成功调用时,它们不修改errno的值

linux c 笔记 线程控制(一)

线程线程是计算机中独立运行的最小单位,运行时占用很少的系统资源,由于每个线程占用的cpu时间是由系统分配的,因此可以把线程看成是系统分配cpu 时间的基本单位,在于用户看来,多个线程是交替执行的,系统不停的在各个线程之间切换,每个线程只有在系统分配给他的时间片内才能取得cpu的控制权,执行线程中的代码 线程的优点:节约,节约资源,节省时间,提高应用程序的响应速度,可以提高多处理器的效率,可以改善程序的结构 线程也有很多私有数据:线程号,寄存器,堆栈,信号掩码,优先级,线程私有的存储空间 一.创建

linux学习笔记——进程的查看和管理、systemctl命令

###########################################################################第七单元##########################################################################1.什么是进程.线程进程是一个具有独立功能的程序关于某个数据集合的一次运动活动,进程也就是系统正在做的事情线程是进程作为分配资源的基本单位. 2.进程状态运行休眠停止继续结束僵尸进程(已经结束了