您的位置:首页 > 编程语言

UNIX环境高级编程——第八章—进程控制

2017-08-27 23:51 633 查看

8.2 进程标识

1、每个进程都有一个非负整型表示唯一进程ID。

2、进程ID是可复用的。当一个进程终止后,其进程ID就复用的候选者。

3、进程ID为0的进程通常是调度进程,常常被称为交换进程(swapper)。该进程是内核的一部分,它并执行任何磁盘上的程序。

4、进程ID1通常是init进程,在自举过程结束时由内核调用。此进程负责在自举内核后启动一个UNIX系统。病将系统引导到一个状态(如多用户)。init进程决不会终止。它是一个普通用户进程(与交换进程不同,它不是内核中的系统进程),但是它以超级用户特权运行。

5、进程ID2是页守护进程(page daemon),此进程负责支持虚拟存储器系统的分页操作。

6、除了进程ID,每个进程还有一些其他标识符。下列函数返回这些标识符。

#include <unistd.h>
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。


8.3 函数fork

1、一个现有的进程可以调用fork函数创建一个新进程。

#include <unistd.h>
pid_t fork(void);
返回值:子进程返回0,父进程返回子进程ID;若出错,返回-1。


(1)由fork创建的新进程被称为子进程(child process)。

(2)fork函数被调用一次,但返回两次,两次返回的区别是子进程的返回值是0,而父进程的返回值则是创建子进程的进程ID。

(3)将子进程ID返回给父进程的理由是:一个进程的子进程可以由多个,并没有一个函数使一个进程可以获得其所有子进程的进程ID。

(4)fork使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getppid以获取其父进程的进程ID(进程ID0总是由内核交换进程使用,所以一个子进程的进程ID不可能为0)。

2、子进程是父进程的副本。子进程获得父进程数据空间、堆和栈的副本。父进程和子进程并不共享存储空间部分。父进程和子进程共享正文段。

3、由于fork之后经常跟随exec,所以现在的很多实现并不执行一个父进程数据段、栈和堆的完全副本。作为替代,使用了写时复制(Copy-On-Write,COW)技术,这些区域由父进程和子进程共享,而且内核将它们的访问权限改变为只读。如果父进程和子进程中的任一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储系统中的一”页“。

4、演示fork函数,从中可以看到子进程对变量所做的改变并不能影响父进程中该变量的值。

/*************************************************************************
> File Name: ins8_1.c
> Author: Elliot
************************************************************************/

/*
* 程序演示了fork函数,从中可以看到子进程对变量所做的改变并不影响父进程中该变量的值。
*/

#include "apue.h"

int     globvar = 6;            /*  external variable in initialized data  */
char    buf[] = "a write to stdout\n";

int
main(void)
{
int     var;                /*  automatic variable on the stack  */
pid_t   pid;

var = 88;
if (write(STDOUT_FILENO, buf, sizeof(buf) - 1) != sizeof(buf) - 1)
err_sys("write error");

printf("before fork\n");            /*  we don't flush stdout  */

if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0)                  /*  child */
{
globvar++;                      /*  modify variables  */
var++;
}
else
sleep(2);                       /*  parent  */

printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var);
exit (0);
}


5、一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。如果要求父进程和子进程之间互相同步,则要求某种形式的进程通信。

6、在重定向父进程的标准输出时,子进程的标准输出也被重定向。

7、实际上,fork的一个特性是父进程的所有打开文件描述符都被复制到子进程中。就像执行dup函数一样。

8、父进程和子进程共享同一个文件偏移量。如果父进程和子进程写同一描述符指向的文件,但又没有任何形式的同步(如果父进程等待子进程),那么它们的输出就会互相混合。

9、处理打开文件之外,父进程的很多其他属性也由子进程继承,包括:

(1)实际用户ID、实际组ID、有效用户ID、有效组ID。

(2)附属组ID。

(3)进程组ID。

(4)会话ID。

(5)控制终端。

(6)设置用户ID标志和设置组ID标志。

(7)当前工作目录

(8)根目录

(9)文件模式创建屏蔽字

(10)信号屏蔽和安排。

(11)对任一打开文件描述符的执行关闭标志(close-on-exec)标志。

(12)环境

(13)连接的共享存储段

(14)存储映像。

(15)资源限制。


10、父进程与子进程之间的区别具体如下:

(1)fork的返回值不同
(2)这两个进程的父进程ID不同。
(3)子进程的 tms_utime、tms_stime、tms_cutime和 tms_ustime 的值设置为0.
(4)子进程不继承父进程设置的文件锁。
(5)子进程的未处理闹钟被清除。
(6)子进程的未处理信号集设置为空集。


11、fork失败的两个主要原因是:

(1)系统已经有了太多的进程

(2)该实际用户ID的进程总数超过了系统限制。

12、fork有以下两种用法:

(1)一个父进程希望复制自己,使父进程和子进程同事执行不同的代码段。

(2)一个进程要执行一个不同的程序,子进程从fork返回后立即调用exec。

8.4 函数vfork

1、vfork函数的调用序列和返回值与fork相同,但两者的语义不同。

#include <unistd.h>
pid_t vfork(void);
返回值:子进程返回0,父进程返回子进程ID;若出错,返回-1。


(1)vfork函数用于创建一个新进程,而该新进程的目的是exec一个新程序。

(2)vfork和fork一样会创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或exit),于是也就不会引用该地址空间。

(3)vfork在子进程调用exec或exit之前,它在父进程的空间中运行。

(4)如果子进程修改数据(除了用于存放vfork返回值的变量)。进行函数调用、或者没有调用exec或exit就返回都可能会带来未知的结果。

(5)vfork在调用exec或exit之后父进程才可能被调度运行,当子进程调用这两个函数中的任一一个时,父进程会恢复运行。(如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁)

2、实例:

/*************************************************************************
> File Name: ins8_3.c
> Author: Elliot
************************************************************************/

#include "apue.h"
int     globvar = 6;                /*  external variable in initialized data  */

int
main(void)
{
int     var;                    /*  automatic variable on the stack  */
pid_t   pid;

var = 88;

printf("before vfork\n");           /*  we don't flush stdio  */
if ((pid = vfork()) < 0)
err_sys("vfork error");
else if(pid == 0)
{
globvar++;                      /*  child  */
var++;                          /*  modify parent's variables  */
_exit(0);
}

/*  parent continus here  */
printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,
var);
exit(0);

}


8.5 exit函数

1、进程有5种正常终止及3种异常终止方式。

(1)在main函数内执行return语句。这等效于调用exit。

(2)调用exit函数,其操作包括调用个终止处理程序(终止处理程序在调用atexit函数时登记),然后关闭所有标准I/O流等。

(3)调用 _exit 或 _Exit 函数。其目的是为进程提供了一种无需运行终止处理程序或信号处理程序而终止的方法。对标准 I/O 流 是否进行重启,这取决于实现。在UNIX系统中,_Exit 和 _exit 是同义的,并不冲洗标准I/O流。

(4)进程最后一个线程在其启动例程中执行return语句。但该线程的返回值不用作进程的返回值。当最后一个线程从其启动例程返回时,该进程以终止状态0返回。

(5)进程的最后一个线程调用 pthread_exit函数。进程的终止状态总是0,与传递给 pthread_exit 的参数无关。

三种异常终止具体如下:

(6)调用abort。它产生SIGABRT信号。

(7)当进程接收到某些信号时。信号可由进程自身(如调用abort函数)、其他进程或内核产生。

(8)最后一个线程对“取消”(cancellation)请求作出相应。

2、不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。

3、如果一个父进程在子进程之前终止,对于父进程已经终止的所有进程,它们的父进程都改变为init进程。我们称这些进程由init进程收养。

4、init收养孤儿进程的操作大致是“在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止进程的子进程,如果是,则该进程的父进程ID就更改为1(init进程的ID)。这种处理方法保证了每个进程都有一个父进程。

5、内核为每个终止进程保存了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到这些信息。这些信息至少包括

(1)进程ID

(2)该进程的终止状态

(3)该进程使用的CPU时间总量

6、一个已经终止、但是其父进程尚未对其进行善后处理(获取终止进子进程的有关信息、释放它扔占用的资源)的进程被称为僵死进程(zombie)

8.6 函数wait 和 waitpid

1、当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。因为子进程终止是一个异步事件(这可以在父进程运行的任何时候发生),所以这种信号也是内核向父进程发的异步通知。

2、调用wait或waitpid的进程可能会发生什么?

(1)如果其所有子进程都还在运行,则阻塞。

(2)如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。

(3)如果它没有任何子进程,则立即出错返回。

3、如果进程由于接收到SIGCHLD信号而调用wait,我们期望wait会立即返回。如果在随机时间点调用wait,则进程可能会阻塞。

4、wait函数和waitpid函数

#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
两个函数返回值:若成功,返回进程ID;若出错,返回0


(1)在一个进程终止前,wait使其调用者阻塞,waitpid有一选项,可使调用者不阻塞。

(2)waitpid并不等待在其调用之后的第一个终止子进程,它由若干个选项,可以控制它所等待的进程。

(3)这两个函数的参数statloc如果不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内。

若不关心终止状态,则可将参数指定为空指针。

(4)waitpid函数中pid参数的作用如下:

pid == -1                           等待任一进程。此种情况下,waitpid和wait等效。
pid > 0                             等待进程ID与pid相等的子进程。
pid == 0                            等待组ID等于调用进程组ID的任一子进程。
pid < -1                            等待组ID等于pid绝对值的任一子进程。


(5)waitpid函数中的options常量

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

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

WUNTRACED                          若某实现支持作用控制,而由pid指定的任一子进程已处于停止状态,
并且其状态子停止以来还未报告过,则返回其状态。WIFSTOPPED宏确定返
回值是否对应于一个停止的进程。


5、有4个互斥的宏取得进程终止的原因:



6、实例:函数pr_exit使用上图的宏打印进程终止状态的说明。

#include "apue.h"
#include <sys/wait.h>
void
pr_exit(int status)
{
if (WIFEXITED(status))
printf("nomal termination, exit status %d\n",
WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("abnormal termination, signal number = %d%s\n",
WTERMSIG(status),
#ifdef  WCOREDUMP
WCOREDUMP(status) ? " (core file generated) " : "");
#else
"");
#endif
else if (WIFSTOPPED(status))
printf("child stopped, signal number = %d\n",
WSTOPSIG(status));
}


演示不同的exit值。

/*************************************************************************
> File Name: ins8_6.c
> Author: Elliot
************************************************************************/
#include "apue.h"
#include <sys/wait.h>

void
pr_exit(int status);

int
main(void)
{
pid_t   pid;
int     status;

if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0)              /*  wait for child  */
exit(7);

if (wait(&status) != pid)       /*  wait child  */
err_sys("wait error");
pr_exit(status);                /*  and print its status  */

if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0)              /*  child  */
abort();                    /*  generates SIGABRT  */

if (wait(&status) != pid)       /*  wait child  */
err_sys("wait error");
pr_exit(status);                /*  and print its status  */

if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0)              /*  child  */
status /= 0;                /*  divide by 0 generates SIGFPE  */

if (wait(&status) != pid)       /*  wait for child  */
err_sys("wait error");
pr_exit(status);                /*  and print its status  */
exit(0);
}


7、waitpid函数提供了wait函数没有提供的3个功能:

(1)waitpid可等待一个特定的进程,而wait则返回任一终止子进程的状态。

(2)waitpid提供了一个wait的非阻塞版本。

(3)waitpid通过 WUNTRACED 和 WCONTINUED 项支持作业控制。

8、如果一个进程fork一个子进程,但不要它等待子进程终止,也不希望子进程处于僵死状态直到父进程终止。实现这一要求的诀窍是调用fork两次。

/*************************************************************************
> File Name: ins8_6_2.c
> Author: Elliot
************************************************************************/

/*
* 如果一个进程fork一个子进程,但不要它等待子进程终止,也不希望进程
* 处于僵死状态直到父进程终止,实现这一要求的诀窍是调用fork两次。
*/

#include "apue.h"
#include <sys/wait.h>

int
main(void)
{
pid_t   pid;
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0)                  /*  first child  */
{
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid > 0)
exit(0);                        /*  parent from second fork == first child  */
/*
* We're the second child; our parent become init as soon
* as our real parent calls exit() in the statement above.
* Here's where we'd continue executing, knowing that when
* We're done, init will reap our status.
*/
sleep(2);
printf("second child, parent pid = %ld\n", (long)getppid());
exit(0);
}
if (waitpid(pid, NULL, 0) != pid)       /* wait for first child and don't care its ending state  */
err_sys("waitpid error");

/*
* We're the parent (the original process); we continue executing,
* knowing that we're not the parent of the second child.
*/
exit(0);
}


8.7 函数waitid

1、函数waitid类似于waitpid,但更灵活。

#include <sys/wait.h>
int waitdid(idtype_t idtype, siginfo_t *infop, int options);
返回值:若成功,返回0;若出错,返回-1。


(1)waitid允许一个进程指定要等待的进程,但它使用两个单独参数表示要等待的子进程所属的类型。

(2)id参数作用与idtype的值相关:

P_PID                               等待一特定进程,id包含要等待子进程的进程ID。
P_PGID                              等待一特定进程组中的任一子进程,id包含要等待子进程的进程组ID
P_ALL                               等待任一进程,忽略id


(3)options 参数指示调用者关注哪些状态变化。

WCONTINUED                          等待一进程,它以前曾被停止,此后又已继续,但其状态尚未报告。
WEXITED                             等待已退出的进程。
WNOHANG                             如无可用的进程退出状态,立即返回而非阻塞。
WNOWAIT                             不破坏子进程的退出状态。该子进程退出状态可由后续的wait、
waitid或waitpid调用取得
WSTOPPED                            等待一进程,它已经停止,但其状态尚未报告。


(4)infop参数是指向signof结构的指针。该结构包含了造成子进程状态改变有关信号的详细信息。

8.8 函数wait3 和 wait4

1、函数wait3 和 wait4 添加了资源参数:

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
pid_t wait3(int *statloc, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *statloc, int options, struct rusage *rusage);
两个函数返回值:若成功,返回进程ID;若出错,返回-1。


(1)资源统计信息包括用户CPU时间总量、系统CPU时间总量、缺页次数、接收到信号的次数等。

8.9 竞争条件

1、当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,我们认为发生了竞争条件(race condition)。

2、一个由子进程输出,另一个由父进程输出。因为输出依赖于内核使这两个进程运行的顺序及每个进程运行的时间长度,所以程序包含了一个竞争条件。

/*************************************************************************
> File Name: ins8_9.c
> Author: Elliot
************************************************************************/

/*
* 程序输出两个字符串:一个由子进程输出,另一个由父进程输出。
* 因为输出依赖于内核使这两个进程运行的顺序及每个进程运行的时间,
* 所以该程序包含了一个竞争条件。
*/

#include "apue.h"

static void charactatime(char *);

int
main(void)
{
pid_t   pid;
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0)
charactatime("output from child\n");
else
charactatime("output from parent\n");

exit(0);
}

static void
charactatime(char *str)
{
char    *ptr;
int     c;

setbuf(stdout, NULL);               /*  set unbuffered  */

for(ptr = str; (c = *ptr++) != 0;)
putc(c, stdout);
}


8.10 函数exec

1、当进程调用一种exec函数时,该进程的程序完全替换为新程序,而新程序则从main函数开始执行。

2、因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。

3、有7种不同的exec函数可供使用:

#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...
/* (char  *) NULL */);
int execlp(const char *file, const char *arg, ...
/* (char  *) NULL */);
int execle(const char *path, const char *arg, ...
/*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
int fexecve(int fd, char *const argv[], char *const envp[]);
7个函数返回值:若出错,返回-1;若成功,不返回。


(1)函数之间的区别,有4个函数取路径名为参数,有两个函数则取文件名作为参数,最后一个取文件描述符作为参数。

(2)当指定filename作为参数时,如果filename中包含/,则就将其视为路径名。否则就按PATH环境变量,在它所指定的各目录中搜寻可执行文件。

(3)如果execlp或execvp使用路径名前缀中的一个找到了可执行文件,但是该文件不是由链接编辑器产生的机器可执行文件,则就认为该文件是一个shell脚本,于是试着调用/bin/sh并以该filename作为shell的输入。

(4)fexecve 函数避免了寻找正确的可执行文件,而是以来调用进程来完成这项工作。调用进程可以使用文件描述符验证所需要的文件并且无竞争地执行该文件。

(5)第二个却别与参数表的传递有关(l表示列表list,v表示矢量)。函数execl、execlp和execle要求将新程序的每个命令行参数都说明为一个单独的参数。这种参数表以空指针结尾。

对于另外4个函数(execv、execvp、execve和execlp)三个函数,则应先构造一个指向各参数的指针数组,然后将该数组的地址作为这4个函数的参数。

(6)最后一个却别与向新程序传递环境表相关。以e结尾的3个函数(execle、execve和fexecve可以传递一个指向环境字符串指针数组的指针)。其他四个函数则使用调用进程中的environ变量为新程序复制现有的环境。

4、字母p表示该函数取 filename 作为参数,并且PATH环境变量寻找可执行文件。字母l表示该函数取一个参数表,它与字母v互斥。v表示该函数取一个argv[]矢量。最后,字母e表示该函数取 envp[]数组,而不适用当前环境。

5、在执行exec后,进程ID没有改变,但新程序从调用进程集成了下列属性:

(1)进程ID和父进程ID

(2)实际用户ID、实际组ID

(3)附属组ID。

(4)进程组ID。

(5)会话ID。

(7)控制终端。

(8)闹钟尚余留的时间

(9)当前工作目录

(10)根目录

(11)文件模式创建屏蔽字

(12)文件锁

(13)进程信号屏蔽

(14)未处理信号

(14)资源限制

(15)nice值

(16)tms_utime、tms_stime、tms_cutime以及tms_cstime值


6、对打开文件的处理与每个文件的执行时关闭(close-on-exec)标志值有关,进程中每个打开描述符都有一个执行时关闭标志。若设置了此标志,则在执行exec时关闭该描述符。否则,该描述符仍打开。

除非特地用fcntl清除该执行时关闭标志,否则系统的默认操作是在exec后仍保持这种描述符打开。

7、exec时关闭打开目录流。这通常是由opendir函数实现的,它调用fcntl函数为对应于打开目录的描述符设置执行时关闭标志。

8、exec前后实际用户ID和实际组ID保持不变,而有效用户ID是否改变取决于所执行程序文件的设置用户ID和设置组ID位是否设置。

9、该7个函数中只有execve是内核的系统调用。这7个函数的关系如下图所示:



10、exec函数的使用:

/*************************************************************************
> File Name: ins8_10.c
> Author: Elliot
************************************************************************/

#include "apue.h"
#include <sys/wait.h>

char *env_init[] = {
"USER=unknown",
"PATH=/tmp",
NULL
};

int
main(void)
{
pid_t   pid;
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0)              /*  specify pathname, specify environment  */
if (execle("/home/king/bin/echoall", "echoall", "myarg1",
"MY ARG2", (char *)0, env_init) < 0)
err_sys("execle error");

if (waitpid(pid, NULL, 0) < 0)
err_sys("wait error");

if ((pid = fork()) < 0)
err_sys("wait error");
else if (pid == 0)              /*  specify filename, inherit environment  */
if (execlp("echoall", "echoall", "only 1 arg", (char *)0) < 0)
err_sys("execlp error");

exit(0);

}


/*************************************************************************
> File Name: echoall.c
> Author: Elliot
************************************************************************/

#include "apue.h"

int
main(int argc, char *argv[])
{
int     i;
char    **ptr;
extern char     **environ;

for (i = 0; i < argc; i++)          /*  echo all command-line args  */
printf("argv[%d]: %s\n", i, argv[i]);

for (ptr = environ; *ptr != 0; ptr++)       /*  and all env strings  */
printf("%s\n", *ptr);

exit(0);
}


8.11 更改用户ID和更改组ID

1、可以使用setuid函数设置实际用户ID和有效用户ID。可以用setgid函数设置实际组ID和有效组ID。

#include <unistd.h>
int setiod(uid_t uid);
int setgid(gid_t gid);
两个函数返回值:若成功,返回0;若出错,返回-1。


(1)若进程具有超级用户特权,则setuid函数将实际用户ID、有效用户ID以及保存的设置用户ID(save set-user-ID)设置为uid。

(2)若进程具有超级用户特权,但是uid等于实际用户ID或保存的设置用户ID,则setuid只将有效用户ID设置为uid。不更改实际用户ID和保存的设置用户ID。

(3)如果上面两个条件都不满足,则errno设置为EPERM,并返回-1。

2、内核所维护的3个用户ID,注意以下几点:

(1)只有超级用户进程可以更改实际用户ID

(2)仅当程序文件设置了设置用户ID位时,exec函数才设置有效用户ID。如果设置用户ID位没有设置,exec函数不会改变有效用户ID位,而将维持其现有值。

(3)保存的设置用户ID是由exec复制有效用户ID而得到的。如果设置了文件用户ID位,则在exec根据文件的用户ID设置了进程的有效用户ID以后,这个副本就被保存起来了。



3、函数setreuid和setregid

#include <unistd.h>
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);


(1)一个非特权用户总能交换实际用户ID和有效用户ID,这就允许了一个设置用户ID程序交换成用户的普通权限,以后又可再次交换会设置用户ID权限。它允许一个非特权用户将其有效用户ID设置为保存的设置用户ID

4、函数seteuid和setegid,只更改有效用户ID和有效组ID:

#include <unistd.h>
int seteuid(uid_t uid);
int setegid(gid_t gid);
两个函数返回值:若成功,返回0;若出错,返回-1。


(1)一个非特权用户可将其有效用户ID设置为其实际用户ID或其保存设置用户ID对于特权用户则可将有效用户ID设置为uid

8.12 解释器文件

1、UNIX系统都支持解释器文件(interpreter file)。这种文件是文本文件,其起始行的形式是:

#! pathname [optional-argument]


(1)pathname通常是绝对路径名,对它不进行什么特殊的处理(不适用PATH进行路径搜索),对这种文件的识别是由内核作为exec系统调用处理的一部分来完成的。

2、实例:执行一个解释器文件:

/*************************************************************************
> File Name: ins8_12.c
> Author: Elliot
************************************************************************/

#include "apue.h"
#include <sys/wait.h>

int
main(void)
{
pid_t   pid;

if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0)
if (execl("/home/king/bin/testinterp",
"testinterp", "myarg1", "MY ARG2", (char *)0) < 0)
err_sys("execl error");

if (waitpid(pid, NULL, 0) < 0)          /*  parent  */
err_sys("waitpid error");
exit(0);
}


8.13 函数system

1、函数system:

#include <stdlib.h>
int system(const char *cmdstring);


(1)cmdstring是一个空指针,则仅当命令处理程序可用时,system返回非0值。

(2)因为system在实现中调用了fork、exec和waitpid,因此有3种返回值。

(3)fork失败或者waitpid返回除EINTR之外的出错,则system返回-1,并且设置errno以指示错误类型。

(4)如果exec失败(表示不能执行shell),则其返回值如同shell执行了exit(127)。

(5)否则所有3个函数(fork、exec和waitpid)都成功,那么system的返回值是shell的终止状态。

2、system函数(没有对信号进行处理)实例:

int
system(const char *cmdstring)           /*  version without signal handling  */
{
pid_t   pid;
int     status;

if (cmdstring == NULL)
return(1);                      /*  always a command processor with UNIX  */

if ((pid = fork()) < 0)
{
status = -1;                    /*  probably out of processes  */
}
else if (pid == 0)
{
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127);                     /*  execl error  */
}
else
{
while (waitpid(pid, &status, 0) < 0)
if (errno != EINTR)
{
status = -1;                /*  error other than EINTR from waitpid()  */
break;
}
}
return(status);

}


/*************************************************************************
> File Name: ins8_13.c
> Author: Elliot
************************************************************************/

#include "apue.h"
#include <sys/wait.h>

int
main(void)
{
int     status;

if ((status = system("date")) < 0)
err_sys("system() error");

pr_exit(status);

if ((status = system("nosuchcommand")) < 0)
err_sys("system() error");

pr_exit(status);

if ((status = system("who; exit 44")) < 0)
err_sys("system() error");

pr_exit(status);

exit(0);
}


3、设置用户ID或设置组ID程序决不应调用system函数。

8.14 进程会计

1、大多数UNIX系统提供了一个选项以进行进程会计(process accounting)处理。启动该选项后,每当进程结束时内核就写一个会计记录。典型的会计记录包含总量较小的二进制数据,一般包括命令名、所使用的CPU时间总量、用户ID和组ID、启动时间等。

2、我们不能获取永远不终止的进程的会计记录。像init这样的进程在系统生命周期一直在运行,并不会产生会计记录。内核守护进程它们通常不会终止。

3、会计文件中记录顺序对应于进程终止顺序,而不是它们启动的顺序。

4、会计记录对应于进程而不是程序。在fork之后,内核为子进程初始化一个记录,而不是在一个新程序被执行时初始化。虽然exec并不创建一个新的会计记录,但相应记录中的命令名改变了,AFORK标志则被清楚。

8.15 用户标识

1、用getlogin函数可以获取此登录名

#include <unistd.h>
char *getlogin(void);
返回值:若成功,返回指向登录名字的字符串指针;若出错,返回NULL。


(1)如果调用此函数的进程灭有连接到用户登录时所用的终端,则函数会失败。通常这些进程为守护进程(daemon)。

8.16 进程调度

1、调度策略和调度优先级是内核确定的。进程可以通过调整nice值选择以更低优先级运行(通过nice值降低它对CPU的战友,因此该进程是“友好的”)。只有特权进程允许提高调度权限。

2、Single UNIX Specification 中 nice值的范围在0~(2*NZERO)- 1 之间,有些实现支持 0~2*NZERO。nice值越小,优先级越高。NZERO是系统默认的nice值。

3、Linux 3.2.0 可以通过非标准的sysconf参数(_SC_NZERO)来访问NZERO的值。

4、进程可以通过nice函数获取或更改它的nice值。使用这个函数,进程只能影响自己的nice值,不能影响任何其他进程的nice值。

#include <unistd.h>
int nice(int incr);
返回值:若成功,返回新的nice值NZERO。若出错,返回-1。


(1)incr参数被增加到调用进程的nice值上。如果incr太大,系统直接把它降到最大合法值。如果incr太小,系统也会把它提高到最小合法值。

(2)由于-1是合法的成功返回值,在调用nice函数之前需要清楚errno,在nice函数返回-1时,需要检查它的值。如果nice调用成功,并返回值为-1,那么errno仍然为0。如果errno不为0,说明nice调用失败

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

#include <sys/resource.h>
int getpriority(int which, id_t who);
返回值:若成功,返回-NZERO~NZERO-1之间的nice值;若出粗,返回-1。


(1)which参数可以取以下三个值之一:

PRIO_PROCESS 表示进程。
PRIO_PGRP  表示进程组
PRIO_USER  表示用户ID。


(2)who参数选择感兴趣的一个或多个进程,如果who参数为0,表示调用进程、进程组或者用户(取决于which参数的值)

(3)当which设为PRIO_USER并且who为0时,使用调用进程的实际用户ID。

(4)如果which参数作用于多个进程,则返回所有作用进程中优先级最高的(最小的nice值)。

6、setpriority函数可用于为进程、进程组和属于特定用户ID的所有进程设置优先级。

#include <sys/resource.h>
int setpriority(int which, id_t who, int value);
返回值:若成功,返回0;若出错,返回-1。


(1)参数which和who与getpriority函数中相同。value增加到NZERO上,然后变为新的nice值

7、程序度量了nice值的效果。

/*************************************************************************
> File Name: ins8_16.c
> Author: Elliot
************************************************************************/

/*
* 进程度量了调用进程nice值的效果。两个进程并行运行,各自增加自己的计数器。
* 父进程使用了默认的nice值,子进程以可选命令参数指定的调整后的nice值运行。
* 运行10s后,两个进程都打印各自的计数值并中止。通过比较不同nice值的进程计数值的差异,
* 我们可以了解nice值是如何影响进程调度的。
*/

#include "apue.h"
#include <errno.h>
#include <sys/time.h>

#if     defined(MACOS)
#include <sys/syslimits.h>
#elif   defined(SOLARIS)
#include <limits.h>
#elif   defined(BSD)
#include <sys/param.h>
#endif

unsigned long long count;
struct  timeval end;

void
checktime(char *str)
{
struct  timeval tv;

gettimeofday(&tv, NULL);

if (tv.tv_sec >= end.tv_sec && tv.tv_usec >= end.tv_usec)
{
printf("%s count = %lld\n", str, count);
exit(0);
}
}

int
main(int argc, char *argv[])
{
pid_t   pid;
char    *s;
int     nzero, ret;
int     adj = 0;

setbuf(stdout, NULL);           /*  关闭缓冲  */

#if     defined(NZERO)
nzero = NZERO;
#elif   defined(_SC_NZERO)
nzero = sysconf(_SC_NZERO);
#else
#error  NZERO   undefined
#endif
printf("NZERO = %d\n", nzero);
if (argc == 2)
adj = strtol(argv[1], NULL, 10);
gettimeofday(&end, NULL);
end.tv_sec += 10;                       /*  run for 10 seconds  */

if ((pid = fork()) < 0)
err_sys("fork failed");
else if (pid == 0)                      /*  child  */
{
s = "child";
printf("current nice value in child is %d, adjusting by %d\n",
nice(0)+nzero, adj);
errno = 0;
if ((ret = nice(adj)) == -1 && errno != 0)
err_sys("child set scheduling priorty");
printf("now child nice value is %d\n", ret + nzero);
}
else                /*  parent  */
{
s = "parent";
printf("current nice value in parent is %d\n", nice(0) + nzero);
}
for (;;)
{
if (++count == 0)
err_quit("%s counter wrap", s);
checktime(s);
}

}


8.17 进程时间

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

#include <sys/times.h>
clock_t times(struct tms *buf);
返回值:若成功,返回流逝的墙上时钟时间(以时钟滴答数为单位);若出错,返回-1。


(1)此函数填写由buf指向的tms结构,该结构定义如下:

struct tms {
clock_t tms_utime;              /*  user CPU time  */
clock_t tms_stime;              /*  system CPU time  */
clock_t tms_cutime;             /*  user CPU time, terminated children  */
clock_t tms_cstime;             /*  system CPU time, terminated children  */
};


(2)次结构没有包含墙上时间,times函数返回墙上时钟时间作为其函数值。此值是相对于过去某一时刻度量的,所以不能用其绝对值而必须使用其相对值。

2、程序将每个命令行参数作为shell命令串执行,对每个命令计时,并打印从tms结构取得的值。

/*************************************************************************
> File Name: ins8_17.c
> Author: Elliot
************************************************************************/
#include "apue.h"
#include <sys/times.h>

static void pr_times(clock_t, struct tms *, struct tms *);
static void do_cmd(char *);

int
main(int argc, char *argv[])
{
int     i;
setbuf(stdout, NULL);               /*  关闭缓冲  */

for (i = 1; i < argc; i++)
do_cmd(argv[i]);                /*  once for each command-line arg  */
exit(0);
}

static void
do_cmd(char *cmd)               /*  execute and time the "cmd"  */
{
struct tms  tmsstart, tmsend;
clock_t     start, end;
int     status;

printf("\ncommand: %s\n", cmd);

if ((start = times(&tmsstart)) == -1)           /*  starting values  */
err_sys("times error");

if ((status = system(cmd)) < 0)                 /*  execute command  */
err_sys("times error");

if ((end = times(&tmsend)) == -1)               /*  ending values  */
err_sys("system() error");

pr_times(end - start, &tmsstart, &tmsend);
pr_exit(status);
}

static void
pr_times(clock_t real, struct tms *tmsstart, struct tms *tmsend)
{
static long     clktck = 0;

if (clktck == 0)                /*  fetch clock ticks per second first time  */
if ((clktck = sysconf(_SC_CLK_TCK)) < 0)
err_sys("sysconf error");

printf("  real:  %7.2f\n", real / (double) clktck);
printf("  user:  %7.2f\n",
(tmsend->tms_utime - tmsstart->tms_utime) / (double) clktck);
printf("  sys  %7.2f\n",
(tmsend->tms_stime - tmsstart->tms_stime) / (double) clktck);
printf("  child user:  %7.2f\n",
(tmsend->tms_cutime - tmsstart->tms_cutime) / (double) clktck);
printf("  child sys:  %7.2f\n",
(tmsend->tms_cstime - tmsstart->tms_cstime) / (double) clktck);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  unix 编程