Unix/Linux编程-进程

进程

6.1 进程的开始与终止

6.1.1 mian函数

当内核执行C程序时,在调用main前先调用一个特殊的启动例程。可执行程序文件将此启动例程指定为程序的其实地址,这时由连接编辑器设置的,而连接编辑器则由C编译器调用。启动历程从内核取得命令行参数和环境变量值,然后为按上述方式调用main函数做好安排。

6.1.2 进程终止

有8种方式使进程终止,其中1-5为正常终止,6-8为异常终止,它们是:
(1) 从main返回;
(2) 调用exit;
(3) 调用_exit或_Exit;
(4) 最后一个线程从其启动例程返回;
(5) 从最后一个线程调用pthread_exit
(6) 调用abort
(7) 接到一个信号
(8) 最后一个线程对取消请求做出响应。

6.1.3 退出函数

#include <stdlib.h>

void exit(int status);

void _Exit(int status);

#include <unistd.h>

void _exit(int status);

3个函数用于正常终止一个程序;_exit和_Exit立即进入内核,exit则限制性一些清理处理,然后返回内核。exit函数总是执行一个标准I/O库的清理关闭操作。
3个函数都带一个整型参数,称为终止状态。

6.1.4 函数atexit

按照ISO C的规定,一个进程可登记多至32个函数,这些函数将由exit自动调用。我们称这些函数为终止处理函数,并调用atexit函数来登记这些函数

#include <stdlib.h>

int atexit(void (*fun) (void));

返回值:成功返回0,错处返回非0

参数是一个函数指针,当调用此函数无需向他传递任何参数,也不期望他返回一个值。exit调用这些函数的顺序与登记时候的顺序相反。
下图显示了一个C程序时如何启动的,以及它终止的各种方式:

6.2 命令行参数与环境表

6.2.1 命令行参数

当执行一个程序时,调用 exec的 进程可将命令行参数传递给新程序。

6.2.2 环境表

每个程序都接收到一张环境表,环境表是一个字符指针数组,全局变量environ则包含了该指针数组的地址:
extern char **environ;
其中每个字符串的结尾处都显示的有一个null字节。
6.2.3 环境变量
如同前述,环境字符串的形式是:
name=value
Unix内核并不查看这些字符串,它们的解释完全取决于各个应用程序。
ISO C定义了一个函数getenv,可以用其取环境变量值,但是该标准又称环境的内容是由实现定义的:

#include <stdlib.h>

char *getenv(const char *name);

扫描二维码关注公众号,回复: 4586815 查看本文章

返回值:指向与name关联的value的指针;未找到返回NULL

函数返回一个指针,指向name=value字符串中的value。

除了获取环境变量值,有时也需要设置环境变量。我们可能希望改变现有变量的值,或者是增加新的环境变量,但并不是所有系统都支持这种能力。

#include <stdlib.h>

int putenv(char *str);

返回值:成功返回0,出错返回非0

int setenv(const char *name, const char *value, int rewite);

int unsetenv(const char *name);

返回值:成功返回0,出错返回-1


(1) putenv取形式为name=value的字符串,将其放到环境表中。如果name已经存在,则先删除其原来的定义。
(2) setenv将name设置为value。如果在环境中name已经存在,那么若rewrite非0,则首先删除其现有的定义,若rewrite为0,则不删除现有定义(name不设置为新的value,而且也不出错)。
(3) unsetenv删除name的定义。即使不存在这种定义也不算出错。


putenv和setenv的区别:
setenv必须分配存储空间,以便依据其参数创建name=value字符串。putenv可以自由地将传递给他的参数字符串直接放到环境中。确实,许多实现就是这么做的。因此,将存放在栈中的字符串作为参数传递给putenv就会发生错误,其原因是,从当前函数返回时,其栈帧占用的存储区可能被重用。

6.3 进程标识

每个进程都要一个非负整数表示的唯一进程ID。虽然是唯一的,但是进程ID是可复用的。当一个简称终止后,其进程ID就成为复用的候选者。大多数Unix系统实现延迟复用算法,使得赋予新建进程的ID不同于最近终止进程所使用的ID。
系统中有一些专用进程,ID为0的进程通常是调度进程,场场被称为交换进程,该进程是内核的一部分,它并不执行任何磁盘上的程序,因此被称为系统进程。ID为1的进程通常是init进程,在自举过程结束时由内核调用。init通常读取与系统有关的初始化文件,并将系统引导到一个状态,init进程绝不会终止。

#include <unistd>

pid_t getpid(void);

返回值:调用进程的进程ID

pid_t getppid(void);

返回值:调用进程的父进程ID

uid_t getuid(void);

返回值:调用进程的实际用户ID

uid_t geteuid(void);

返回值:调用进程的有效用户ID

gid_t getgid(void);

返回值:调用进程的实际组ID

gid_t getegid(void);

返回值:调用进程的实际组ID

6.4 创建一个新进程:fork

6.4.1 fork函数

#include <unistd.h>

pid_t fork(void);

返回值:子进程返回0,父进程返回子进程ID;出错返回-1

fork创建的新进程被称为子进程。fork函数被调用一次,但返回两次(一次在父进程中,一次在子进程中)。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新建子进程的进程ID。子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本,子进程获得父进程的数据空间、堆和栈的副本。父进程和子进程并不共享这些存储空间部分,共享正文段。
由于fork之后经常跟着exec,所以现在很多实现并不执行一个父进程数据段、堆和栈的完全副本。所谓替代,使用了写时赋值技术。这些区域由父进程和子进程共享,而且内核将他们的访问权限改变为只读。如果父进程和子集成中的任何一个试图修改进程共享,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储系统中的一页。
一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的,这取决于内核所使用的调度算法。

6.4.2 父进程和子进程之间的区别

(1) fork的返回值不同。
(2) 进程ID不同。
(3) 两个进程的父进程ID不同;子进程的父进程ID是创建它的进程的ID,而父进程的父进程ID不变。
(4) 子进程的tms_utime、tms_stime、tms_cutime和tms_ustime的值设置为0.
(5) 子进程不继承父进程设置的文件锁。
(6) 子进程的未处理信号集合为空集。

6.4.3 fork失败的原因

(1) 系统中已经有了太多的进程。
(2) 该实际用户ID的进程总数超过了系统限制

6.4.4 fork的应用场景

(1) 一个父进程希望复制自己,使父进程和子进程同时执行不同的代码段。
(2) 一个进程要执行不同的程序。这种情况下,子进程从fork返回后立即调用exec。

6.5 wait和waitpid

当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。子进程终止是异步时间,所以这种信号也是内核向父进程发的异步通知。父进程可以选择忽略该信号,或者提供一个该信号发生时被调用的执行函数(信号处理程序)。对于这种信号的系统默认动作是忽略它。

#include <sys/wait.h>

pid_t wait(int *statloc);

pid_t waitpid(pid_t pid, int *statloc, int options);

返回值:成功返回进程ID,错处返回0或-1

我们需要知道,调用wait或waitpid的进程可能会发生什么:
(1) 如果其所有子进程都还在运行,则阻塞。
(2) 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
(3) 如果他没有任何子进程,则立即出错返回。


参数statloc如果不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内。终止状态用第一在<sys/wait.h>中的宏来查看。有4个互斥的宏可用来取得进程终止原因。

说明

WIFEXITED(status)

若为正常终止进程返回的状态,则为真。可执行WEXITSTATUS(status),获取子进程传送给exit或_exit参数的低8位

WIFSIGNALED(status)

若为异常终止子进程返回的状态,则为真(接到一个不捕获的信号)。可执行WTERMSIG(status),获取使子进程终止的信号编号。

WIFSTOPPED(status)

若为当前暂停子进程的返回状态,则为真。可执行WSTOPSIG(status),获取使子进程暂停的信号编号

WIFCONTUNUED(status)

若在作业控制暂停后已经继续的子进程返回了状态,则为真。

waitpid函数提供以下功能:
(1) pid==-1,等待任一进程,与wait等效。
(2) pid>0,等待进程ID与pid相等的子进程。
(3) pid==0,等待组ID等于调用进程ID的任一子进程。
(4) pid<-1,等待组ID等于pid绝对值的任子进程。
对于waitpid,如果指定的进程或进程组不存在,或者参数pid指定的进程不是调用进程的子进程,都可以能出错。
options参数使我们能进一步控制waitpid的操作,此参数或者是0,或者是下面的常量按位或运算的结果。

常量

说明

WCONTNUED

若实现支持作业控制,那么由pid指定的任一子进程在停止后已经继续,但其状态尚未报告,则返回其状态。

WNOHANG

若pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0。

6.6 waitid函数

#include <sys/wait.h>

int waited(idtype_t idtype, id_t id, siginfo_t *infop, int options);

返回值:若成功,返回0,出错返回-1

waitid允许一个进程指定要等待的子进程,但它使用两个单独的参数表示要等待的子进程所属的类型,而不是将此进程ID或进程组ID合成一个参数。id参数的作用域idtype的值相关。该函数支持的idtype类型如下:

常量

说明

P_PID

等待一特定进程:id包含要等待子进程的进程ID

P_PGID

等待一特定进程组中的任一子进程:id包含要等待子进程的进程组ID

P_ALL

等待任一子进程:忽略id

options参数如下各标志位的按位或运算:

常量

说明

WCONTINUED

等待一进程,它以前曾被停止,此后已继续,但其状态尚未报告。

WEXITED

等待已退出的进程。

WNOHANG

如无可用的子进程退出状态,立即返回而非阻塞。

WNOWAIT

不破坏子进程退出状态。该子进程退出状态可由后续的wait、waitid或waitpid调用取得。

WSTOPPED

等待一进程,它已经停止,但其状态尚未报告。

WCONTINUED、WEXITED或WSTOPPED这3个常量之一必须在options参数中指定。
infop参数是指向siginfo结构的指针。该结构包含了在成子进程状态改变的有关信号的详细信息。

6.7 exec函数族

当进程调用一种exec函数时,该进程执行的程序完全替换为新程序,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用磁盘上的一个新程序替换了当前程序的正文段、数据段、堆段和栈段。

#include <unistd.h>

int execl(const char *pathname, const char *arg0, … /* (char *) 0  */);

int execv(const char *pathname, car *const argv[]);

int execle(const char *pathname, const char *arg0,…/* (char *)0 , char *const envp[] */);

int execve(const char *pathname, char *const argv[], char *const envp[]);

int execlp(const char *filename, const char *arg0,… /* (char *) 0 */);

int execvp(const char *filename, char *const srgv[]);

int fexecve(int fd, char *const argv[], char *const envp[]);

出错返回-1,成功不返回。

前4个函数取路径名作为参数,后两个函数则取文件名作为参数,最后一个取文件描述符作为参数。当指定filename作为参数时:
(1) 如果filename包含/,则就将其视为路径名;
(2) 否则就按PATH环境变量,在它所指定的各目录中搜索可执行文件。


如果execlp或execvp使用路径前缀中的一个找到了一个可执行文件,但是该文件不是由链接编辑器产生的机器可执行文件,则就认为该文件是一个shell脚本。
execl、execlp和execle要求将新程序的每一个命令行参数都说明为一个单独的参数,以空指针结尾。对于另外4个函数,则应先构造一个指向各参数的指针数组,然后将该数组地址作为4个函数的参数。
以e结尾的3个函数可以传递一个指向环境字符串指针数组的指针。

6.8 更改用户ID和更改组ID

可以用setuid函数设置实际用户ID和有效用户ID。于此类似,可以用setgid函数设置实际组ID和有效组ID。

#include <unistd.h>

int setuid(uid_t uid);

int setgid(gid_t gid);

函数返回值:成功返回0,出错返回-1

更改用户ID(组ID)的规则:
(1) 若进程具有超级用户特权,则setuid函数将实际用户ID、有效用户ID以及保存的设置用户ID设置为uid。
(2) 若进程没有超级用户去特权,但是uid等于实际用户ID或保存的设置用户ID,则setuid将有效用户ID设置为uid。
(3) 若上面两个条件都不满足,则errno设置为EPERM,并返回-1。


关于内核所维护的3个用户ID,还需要注意一下几点:
(1) 只有超级用户进程可以更改实际用户ID。通常,实际用户ID是在用户登录时,有login(1)程序设置的,而且决不会改变它。因为login是一个超级用户进程,调用它时,设置所有3个用户ID。
(2) 仅当对程序文件设置了设置用户ID位时,exec才设置有效用户ID。如果设置用户ID位没有设置,exec函数不会改变有效用户ID,而将维持其现有值。 任何时候都可以调用setuid,将有效用户ID设置为实际用户ID或保存的设置用户ID。自然地,不能将有效用户ID设置为任一随机值。
(3) 保存的设置用户ID是由exec赋值有效用户ID而得到的。如果设置了文件的设置用户ID位,则在exec根据文件的用户ID设置了进程的有效用户ID以后,这个副本就被保存起来了。
更改3个用户ID不同的方法:

ID

exec

setuid

设置用户ID关闭

设置用户ID为打开

超级用户

非特权用户

实际用户ID

不变

不变

设为uid

不变

有效用户ID

不变

设置为程序文件的用户ID

设为uid

设为uid

保存的设置用户ID

从有效用户ID复制

从有效用户ID复制

设为uid

不变

6.9 进程调度

Unix系统历史上对进程提供的只是基于调度优先级的粗粒度的控制,调度策略和调度优先级是由内核确定的。进程通过调整nice值选择以更低优先级运行,只有特权进程允许提高调度权限。nice值越小,优先级越高。
进程可以通过nice函数获取或更改他的nice值,只影响自己的nice值,不能影响其他任何进程的nice值。

6.9.1 nice函数

#include <unistd.h>

int nice(int incr);

返回值:成功返回新的nice值NZERO,出错返回-1

incr参数被增加到调用进程的nice值上。如果incr太大,系统直接把它降低到最大的合法值,由于-1是合法的成功返回值,在调用nice函数之前需要清除errno,在nice函数返回-1时,需要检查他的值。如果nice调用成功,并且返回值为-1,那么errno仍然为0,。如果errno部位0,说明nice调用失败。

6.9.2 getpriority函数

getpriority函数可以像nice函数那样用于获取进程的nice值,但是getpriority还可以获取一组相关进程的nice值。


#include <sys/resource.h>

int getpriority(int which, id_t who);

返回值:成功返回-NZERO~NZERO-1之间的nice值,出错返回-1.

which参数可以取一下三个值之一:PRIO_PROCESS表示进程,PRIO_PGRP表示进程组,PRIO_USER表示用户ID。which参数控制who参数是如何解释的,who参数选择感兴趣的一个或多个线程。如果who参数为0,表示调用进程、进程组或用户(取决于which参数的值)。

6.10 进程时间

任一进程都可以调用times函数获得它自己以及已终止子进程的上述值

#include <sys/timies.h>

clock_t times(struct tms *buf);

车才能更改,返回流逝的墙上时间(以时钟滴答数为单位),出错返回-1

tms结构体定义如下:

struct tms {

               clock_t tms_utime;  /* user time */

               clock_t tms_stime;  /* system time */

               clock_t tms_cutime; /* user time of children */

               clock_t tms_cstime; /* system time of children */

           };

此结构没有包含墙上时钟时间,times函数返回墙上时钟时间作为其函数值。

猜你喜欢

转载自blog.csdn.net/water_3700348/article/details/78331054
今日推荐