apue 第8章 进程控制

引言

  • 创建新进程,执行程序和进程终止
  • 进程属性的ID:实际、有效和保存的用户ID和组ID以及它们如何受到控制原语控制的
  • 解释器文件和system函数
  • 进程会计

进程标识

  • 每个进程都有一个非负整型表示的唯一进程ID。因为进程ID标识符总是唯一的,常将其用作其他标识符的一部分以保证其唯一性。
  • 进程ID可复用:当一个进程终止后,其进程ID就称为复用的候选者。大多数UNIX系统实现延迟复用算法,新进程的ID不同于最近终止进程的ID。

专用进程

  • ID为0的进程通常是调度进程,常常被称为交换进程(swapper)。该进程是内核的一部分,它并不执行任何磁盘上的程序,因此也被称为系统进程。
  • 进程ID 1通常是init进程。在自举过程结束时由内核调用。inti进程绝不会终止,它是一个普通的用户进程。
  • 进程ID 2是页守护进程。负责支持虚拟存储系统的分页操作

获取进程的一些函数

#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

函数fork

  • 一个现有的进程可以调用fork函数创建一个新进程
#include <unistd.h>
pid_t fork(void);
//返回值:子进程返回0;父进程返回进程ID;若出错,返回-1
  • fork创建一个子进程。
  • fork函数调用一次,返回两次。子进程的返回值是0,父进程的返回值则是新建子进程的进程ID。

将子进程ID返回给父进程的理由是:因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的进程ID。
fork使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总可以调用getppid以获得其父进程的进程ID。

子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如,子进程获得父进程数据空间、堆和栈的副本。注意,这是子进程所有用的副本。父进程和子进程并不共享这些存储空间部分。父进程和子进程共享正文段

由于在fork之后经常跟随着exec,所以现在的很多实现并不执行一个父进程数据段、栈和堆的完全副本。作为代替,使用了写时复制(Copy-On-Write, cow)技术。这些区域由父进程和子进程共享,而且内核将它们的访问权限改变为只读。

fork函数实例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int globvar = 6;
char buf[] = "a write to stdout\n";

int main()
{
    int var;
    pid_t pid;

    var = 88;
    if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
    {
        printf("write error!\n");
        exit(1);
    }
    printf("Befor fork\n");

    if ((pid = fork()) < 0)
    {
        printf("Fork error\n");
        exit(1);
    }
    else if (pid == 0)
    {
        //子进程
        globvar++;
        var ++;
    }
    else
        sleep(2);
    printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var);

    exit(0);
}
yanke@vm:~/Code/apue/ch8ForkContral$ ./a.out 
a write to stdout
Befor fork
pid = 3473, glob = 7, var = 89
pid = 3472, glob = 6, var = 88
yanke@vm:~/Code/apue/ch8ForkContral$ ./a.out  > tmp.out 
yanke@vm:~/Code/apue/ch8ForkContral$ cat tmp.out 
a write to stdout
Befor fork
pid = 3478, glob = 7, var = 89
Befor fork
pid = 3477, glob = 6, var = 88
  • 由于write是不带缓冲的,所以数据直接写到标准输出
  • printf是带缓冲的,如果是terminal是行缓冲,如果是重定向文件,则是全缓冲,因此fork时会连缓冲区一起fork过去,在进程结束时输出缓冲区。
  • 在fork之后是父进程先执行还是子进程先执行是不确定的,这取决于内核所使用的调度算法

文件共享

  • 父进程和子进程每个相同的打开描述符共享一个文件表项
  • 父进程和子进程共享同一个文件偏移量

apue 第8章 进程控制_第1张图片

在fork之后处理文件描述符有以下两种常见的情况。

  1. 父进程等待子进程完成。在这种情况下,父进程无需对其描述符做任何处理。当子进程终止后,它层进行过读、写操作的任一共享描述符的文件偏移量已做了相应更新。
  2. 父进程和子进程各自执行不同的程序段。在这种情况下,在fork之后,父进程和子进程各自关闭它们不需使用的文件描述符,这样就不会干扰对方使用的文件描述符。这种方法是网络服务进程经常使用的。

子进程继承的属性:

  • 共享文件
  • 实际用户ID、实际组ID、有效用户ID、有效组ID
  • 附属组ID
  • 进程组ID
  • 会话ID
  • 控制终端
  • 设置用户ID标志和设置组ID标志
  • 当前工作目录
  • 根目录
  • 文件模式创建屏蔽字
  • 信号屏蔽和安排
  • 对任一打开文件描述符的执行时关闭标志
  • 环境
  • 连接的共享存储段
  • 存储映像
  • 资源限制

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

  • fork的返回值不同
  • 进程ID不同
  • 这两个进程的父进程ID不同:子进程的父进程ID是创建它的进程的ID,而父进程的父进程ID则不变。
  • 子进程的tms_utime、tms_stime、tms_cutime和tms_ustime的值设置为0
  • 子进程不继承父进程设置的文件锁
  • 子进程的未处理闹钟被清除
  • 子进程的未处理信号集设置为空集

fork失败的两个主要的原因

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

fork有以下两种用法

  1. 一个父进程希望复制自己,使父进程和子进程同时执行不同的代码段。这在网络服务器进程中是常见的——父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求。
  2. 一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec

函数vfork

  • vfork函数的调用序列和返回值与fork相同,但两者的语义不同。
  • vfork函数用于创建一个新进程,而该进程的目的是exec一个新程序。vfork不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec,它在父进程的空间中运行。
  • vfork和fork之间的另一个区别是:vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行,当子进程调用这两个函数中的任意一个时,父进程会恢复运行。

使用vfork

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int globvar = 6;

int main()
{
    int var;
    pid_t pid;

    var = 88;
    printf("Befor fork\n");
    if ((pid = vfork()) < 0)
    {
        printf("Fork error\n");
        exit(1);
    }
    else if (pid == 0)
    {
        //子进程
        globvar++;
        var ++;
        _exit(0);
    }

    printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var);
    exit(0);
}
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch8ForkContral$ ./a.out
Befor fork
pid = 5822, glob = 7, var = 89

可见,子进程改变了父进程中的值,因为子进程在父进程的空间中执行

函数exit

进程有5种正常终止:

  1. 在main函数内执行return语句,等效调用exit
  2. 调用exit函数
  3. 调用_exit或_EXIT函数
  4. 进程的最后一个线程在启动例程中执行return语句
  5. 进程的最后一个线程调用pthread_exit函数

进程3种异常终止:

  1. 调用abort。
  2. 当进程收到某些信号时
  3. 最后一个线程对“取消”请求做出响应。

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

  • 子进程终止后会返回终止状态给父进程
  • 如果父进程在子进程之前终止:父进程已经终止的所有进程,它们的父进程都改变为init进程。我们称这些进程由init进程收养

一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程被称为僵死进程。ps(1)命令将僵死进程的状态打印为Z。如果编写一个长期运行的程序,它fork了很多子进程,那么除非父进程等待取得子进程的终止状态,不然这些子进程终止后就会变成僵死进程。

函数wait和waitpid

当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号(内核保存了子进程的终止信息)。因为子进程终止是个异步事件(这可以在父进程运行的任何时候发生),所以这种信号也是内核向父进程发的异步通知。父进程可以选择忽略该信号,或者提供一个该信号发生时即被调用执行的函数(信号处理程序)

调用wait和waitpid的进程可能会发生:

  1. 如果其所有子进程都还在运行,则阻塞。
  2. 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
  3. 如果它没有任何子进程,则立即出错返回。
#include <sys/wait.h>

pid_t wait( int *statloc );
返回值:若成功则返回已终止子进程ID,若出错则返回-1

pid_t waitpid( pid_t pid, int *statloc, int options );
返回值:若成功则返回状态改变的子进程ID,若出错则返回-1,若指定了WNOHANG选项且pid指定的子进程状态没有发生改变则返回0
  • 在一个子进程终止前,wait使其调用者阻塞,而waitpid有一个选项,可使调用者不阻塞。
  • waitpid并不等待在其调用之后的第一个终止子进程,它有若干个选项,可以控制它所等待的进程。
  • 这两个函数的参数statloc是一个整型指针。如果statloc不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内。如果不关心终止状态,则可将该参数指定为空指针
  • 检查wait和waitpid所返回的终止状态的宏
    • WIFEXITED(status):若为正常终止子进程返回的状态,则为真。对于这种情况可执行WEXITSTATUS(status),取子进程传送给exit、_exit或_Exit参数的低8位
    • WIFSIGNALED(status):若为异常终止子进程返回的状态,则为真(接到一个不捕捉的信号)。对于这种情况,可执行WTERMSIG(status),取使子进程终止的信号编号。另外,有些实现定义宏WCOREDUMP(status),若已产生终止进程的core文件,则它返回真
    • WIFSTOPPED(status):若为当前暂停子进程的返回状态,则为真。对于这种情况,可执行WSTOPSIG(status),取使子进程暂停的信号编号
    • WIFCONTINUED(status):若在作业控制暂停后已经继续的子进程返回了状态,则为真。(POSIX.1的XSI扩展;仅用于waitpid。)

例子:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>

void ErrorExit(const char* m)
{
    perror(m);
    exit(EXIT_FAILURE);
}

void pr_exit(int status)
{
    if (WIFEXITED(status))
        printf("normal termination,exit status = %d\n",\
                WEXITSTATUS(status));
    else if(WIFSIGNALED(status))
        printf("abnoraml termination, signal = number = %d.\n",\
                WTERMSIG(status));
    else if (WIFSTOPPED(status))
        printf("child stopped, signal number = %d\n",\
                WSTOPSIG(status));
}

int main()
{
    pid_t pid;
    int status;

    if((pid = fork()) < 0)
        ErrorExit("fork error!");
    else if(pid == 0)
        exit(7);

    if (wait(&status) != pid)
        ErrorExit("wait error!");
    pr_exit(status);

    if ((pid = fork()) < 0)
        ErrorExit("fork error!");
    else if (pid == 0)
        abort();
    if (wait(&status) != pid)
        ErrorExit("wait error!");
    pr_exit(status);

    if ((pid = fork())<0)
        ErrorExit("wait error!");
    else if (pid == 0)
        status /= 0;
    if (wait(&status) != pid)
        ErrorExit("wait error!");
    pr_exit(status);

    exit(0);

}
normal termination,exit status = 7
abnoraml termination, signal = number = 6.
abnoraml termination, signal = number = 8.

waitpid函数中pid的作用是:

  • pid == –1:等待任一子进程。就这一方面而言,waitpid与wait等效。
  • pid > 0 :等待其进程ID与pid相等的子进程。
  • pid == 0 :等待其组ID等于调用进程组ID的任一子进程。
  • pid < –1 : 等待其组ID等于pid绝对值的任一子进程。

waitpid函数返回终止子进程的进程ID,并将该子进程的终止状态存放在由status指向的存储单元中。对于wait,其唯一的出错是调用进程没有子进程(函数调用被一个信号中断时,也可能返回另一种出错)。但是对于waitpid,如果指定的进程或进程组不存在,或者参数pid指定的进程不是调用进程的子进程则都将出错。

options参数使我们能进一步控制waitpid的操作。此参数可以是0:

  • WCONTINUED:若实现支持作业控制,那么由pid指定的任一子进程在暂停后已经继续,但其状态尚未报告,则返回其状态
  • WNOHANG:若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0
  • WUNTRACED:若某实现支持作业控制,而由pid指定的任一子进程已处于暂停状态,并且其状态自暂停以来还未报告过,则返回其状态。WIFSTOPPED宏确定返回值是否对应于一个暂停子进程

函数waitid

Single UNIX Specification的XSI扩展包括了另一个取进程终止状态的函数——waitid,此函数类似于waitpid,但提供了更多的灵活性。

#include <sys/wait.h>

int waitid( idtype_t idtype, id_t id, siginfo_t *infop, int options );
返回值:若成功则返回0,若出错则返回-1
  • waitid允许一个进程指定要等待的子进程。但它使用单独的参数表示要等待的子进程的类型,而不是将此与进程ID或进程组ID组合成一个参数.idtype常量
    • P_PID :等待一个特定的进程:id包含要等待子进程的进程ID
    • P_PGID:等待一个特定进程组中的任一子进程:id包含要等待子进程的进程组ID
    • P_ALL:等待任一子进程:忽略id
  • infop参数是指向siginfo结构的指针。该结构包含了有关引起子进程状态改变的生成信号的详细信息。
  • options参数是按位“或”。这些标志指示调用者关注哪些状态变化。
    • WCONTINUED:等待一个进程,它以前曾被暂停,此后又已继续,但其状态尚未报告
    • WEXITED:等待已退出的进程
    • WNOHANG:如无可用的子进程退出状态,立即返回而非阻塞
    • WNOWAIT:不破坏子进程退出状态。该子进程退出状态可由后续的wait、waitid或waitpid调用取得
    • WSTOPPED:等待一个进程,它已经暂停,但其状态尚未报告

函数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
  • 参数rusage要求内核返回由终止进程及其所有子进程使用的资源汇总。
  • 资源统计信息包括用户CPU时间总量、系统CPU时间总量、页面出错次数、接收到信号的次数等。

竞争条件

当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,则我们认为这发生了竞争条件。如果一个进程希望等待一个子进程终止,则它必须调用一种wait函数。如果一个进程要等待其父进程终止,则可使用下列形式的循环:

while ( getppid() != 1 )
    sleep( 1 );

这种形式的循环(称为轮询(polling))的问题是它浪费了CPU时间,因为调用者每隔1秒都被唤醒,然后再进行条件测试。为了避免竞争条件和轮询,在UNIX中可以使用信号机制。也可使用各种形式的进程间通信(IPC)。

exec函数

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

用fork可以创建新进程,用exec可以执行新程序。exit函数和两个wait函数处理终止和等待终止。这些是我们需要的基本的进程控制原语。

include <unistd.h>
int execl( const char *pathname, const char *arg0, ... /* (char *)0 */ );
int execv( const char *pathname, char *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 argv[] );
//6个函数返回值:若出错则返回-1,若成功则不返回值
  • 如果filename中包含了/,则将其视为路径名。否则就按PATH环境变量,在它所指定的各目录中搜寻可执行文件。
  • PATH变量包含了一张目录表(称为路径前缀),目录之间用冒号(:)分隔。例如name = value环境字符串:PATH=/bin:/usr/bin:/usr/local/bin/:.
  • 第二个区别与参数表的传递有关.execl、execlp和execle要求将新程序的每个命令行参数都说明为一个单独的参数。这种参数表以空指针结尾。对于另外三个函数(execv、execvp和execve),则应先构造一个指向各参数的指针数组,然后将该数组地址作为这三个函数的参数。指定在4个目录中进行搜索。最后的路径前缀(.)表示当前目录。
  • 字母p表示该函数取filename作为参数,并且用PATH环境变量寻找可执行文件。字母l表示该函数取一个参数表,它与字母v互斥。字母v表示该函数取一个argv[]矢量。字母e表示该函数取envp[]数组,而不使用当前环境。
  • 在很多UNIX实现中,这6个函数中只有execve是内核的系统调用。另外5个只是库函数,它们最终都要调用该系统调用。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

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

    for (i = 0; i < argc; ++i)
        printf("argv[%d]:%s\n", i, argv[i]);

    for (ptr = environ; *ptr != 0; ++ptr)
    {
        printf("%s\n", *ptr);
    }

    exit(0);
}
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

char* env_inti[] = {"USER=unknow", "PATH=/tmp", NULL};

int main()
{
    pid_t pid;
    if ((pid = fork()) < 0)
    {
        printf("fork error\n");
        exit(1);
    }
    else if(pid == 0)
    {
        if (execle("/home/ubuntu/Code/apue/ch8ForkContral/echoall", "echoall", "yan", "ke", "scut", (char*)0, env_inti) < 0)
        {
            printf("execle error\n");
            exit(1);
        }
     }

    if (waitpid(pid, NULL, 0) < 0)
    {
        printf("wait error!\n");
        exit(1);
    }
    else if (pid == 0)
    {
        if (execlp("echoall", "echoall", "only one arg", (char*)0) < 0)
        {
            printf("execlp error\n");
            exit(1);
        }
    }

    exit(0);
}
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch8ForkContral$ ./a.out 
argv[0]:echoall
argv[1]:yan
argv[2]:ke
argv[3]:scut
USER=unknow
PATH=/tmp

最后总结一下使用exec函数时需要考虑哪些参数:首先,要执行的新程序名(带不带路径);其次,命令行参数(列表形式还是指针数组形式);最后,环境表(要不要传递环境表).

更改用户ID和组ID

在UNIX系统中,特权(例如能改变当前日期的表示法以及访问控制(例如,能否读、写一特定文件))是基于用户ID和组ID的。当程序需要增加特权,或需要访问当前并不允许访问的资源时,我们需要更换自己的用户ID或组ID,使得新ID具有合适的特权或访问权限。与此类似,当程序需要降低其特权或阻止对某些资源的访问时,也需要更换用户ID或组ID,从而使新ID不具有相应特权或访问这些资源的能力。在设计应用程序时,我们总是试图使用最小特权(least privilege)模型。

有时,在从一个服务器到另一个服务器迁移服务器或应用程序时将需要更改 UID 或 GID。还有,在管理员操作失误时也需要更改 UID 或 GID。在使用 AIX High-Availability Cluster Multiprocessing (HACMP) 进行集群管理的环境中,所有集群服务器必须始终具有相同的 UID 和 GID;否则故障转移流程就无法正确工作。

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

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

规则:

  • 若进程具有超级用户特权,则setuid函数将实际用户ID、有效用户ID,以及保存的设置用户ID设置为uid。
  • 若进程没有超级用户特权,但是uid等于实际用户ID或保存的设置用户ID,则setuid只将有效用户ID设置为uid。不改变实际用户ID和保存的设置用户ID。
  • 如果上面两个条件都不满足,则将errno设置为EPERM,并返回-1。

注意:

  • 实际用户ID和实际组ID标识我们究竟是谁(执行这个程序的用户和组)
  • 有效用户ID,有效组ID以及附加组ID决定了我们的文件访问权限
  • 保存设置用户ID和保存设置组ID在执行一个程序时包含了有效用户ID和有效组ID的副本。用于exec函数保持。
  • 无论设置用户ID位是关闭还是打开,保存的设置用户ID都是从有效用户ID复制得来的

apue 第8章 进程控制_第2张图片

setreuid和setregid函数:交换实际用户ID(组ID)和有效用户ID(组ID)的值

#include <unistd.h>
int setreuid( uid_t ruid, uid_t euid );
int setregid( gid_t rgid, gid_t egid );
两个函数返回值:若成功则返回0,若出错则返回-1

seteuid和setegid函数:类似于setuid和setgid,但只更改有效用户ID和有效组ID。

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

关于用户ID所说明的一切都适用于组ID。附加组ID不受setgid、setregid或setegid函数的影响。

解释器文件

所有现今的UNIX系统都支持解释器文件(interpreter file)(也可称为解释器脚本)。这种文件是文本文件,其起始行格式是:#! pathname [optional-argument].感叹号和pathname之间的空格是可选的。最常见的解释器文件以下列行开始:

#!/bin/sh
  • pathname通常是绝对路径名
  • 内核调用exec函数的进程实际执行的并不是该解释器文件,而是该解释器文件第一行中pathname所指定的文件

system函数

#include<stdlib.h> 
int system(const char * string); 
  • 函数说明:system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令,此命>令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。
  • 返回值:

  • =-1:出现错误

  • =0:调用成功但是没有出现子进程
  • 0:成功退出的子进程的id

  • 在编写具有SUID/SGID权限的程序时请勿使用system(),system()会继承环境变量,通过环境变量可能会造成系统安全的问题

#include <stdlib.h>
#include <stdio.h>

int main()
{
    system("ls -al ./");

    exit(0);
}
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch8ForkContral$ ./a.out 
total 88
drwxrwxr-x  2 ubuntu ubuntu  4096 Nov  2 09:54 .
drwxrwxr-x 16 ubuntu ubuntu  4096 Oct 29 15:08 ..
-rwxrwxr-x  1 ubuntu ubuntu  8632 Nov  2 09:54 a.out
-rwxrwxr-x  1 ubuntu ubuntu   113 Sep 16 11:26 awktest
-rwxrwxr-x  1 ubuntu ubuntu  8715 Sep 15 10:59 echoall

进程会计

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

函数(acct)用于启用和禁用进程会计.

进程会计的结构体如下

typedef    u_short    comp_t;    /* 3-bit base 8 exponent; 13-bit fraction */

struct acct
{
    char    ac_flag;        /* flag */
    char    ac_stat;        /* termination status (signal & core flag only) */
                            /* (Solaris only) */
    uid_t    ac_uid;        /* real user ID */
    gid_t    ac_gid;        /* real group ID */
    dev_t    ac_tty;        /* controlling terminal */
    time_t   ac_btime;      /* starting calendar time */
    comp_t   ac_utime;      /* user CPU time (clock ticks) */
    comp_t   ac_stime;      /* system CPU time (clock ticks) */
    comp_t   ac_etime;      /* elapsed time (clock ticks) */
    comp_t   ac_mem;        /* average memory usage */
    comp_t   ac_io;         /* bytes transferred (by read and write) */
                            /* "blocks" on BSD system */
    comp_t   ac_rw;         /* blocks read or written */
                            /* (not present on BSD system) */
    char     ac_comm[8];    /* command name: [8] for Solaris, */
                            /* [10] for Mac OS X, [16] for FreeBSD, and */
                            /* [17] for Linux */

};

进程终止时写一个会计记录。这就意味着在会计文件中记录的顺序对应于进程终止的顺序,而不是它们起动的顺序。

用户标识

获取登录名

#include <unistd.h>
char *getlogin(void);
返回值:若成功则返回指向登录名字符串的指针,若出错则返回NULL
  • 如果调用此函数的进程没有连接到用户登录时所用的终端,则本函数会失败。通常称这些进程为守护进程(daemon)

进程调度

nice值越小,优先级越高

#include<unistd.h> 
int nice(int incr); //返回值:若成功,返回新的nice值;若出错,返回-1 
  • incr被增加到调用进程的nice值上.如果nice过大或过小,都会被系统降到最大合法值或提高到最小合法值.

和进程nice值相关的函数:

(1)getpriority函数可以像nice函数获取进程nice值,而且他还可以获取一组相关进程的nice值.

#include <sys/resource.h> 
int getpriority(int which, id_t who); //返回值:若成功,返回nice值;若失败,返回-1 
  • which:RIO_PROCESS表示进程,PRIO_PGRP表示进程组,PRIO_USER表示用户ID.
  • who为0,表示调用进程,进程组或者用户(取决与which参数的值).

(2)setpriority

#include <sys/resource.h> 
int setpriority(int which,id_t who, int value);//返回值:若成功,返回0;若失败,返回-1 
  • 参数和getpriority一样,其中value增加到nice值上.

进程时间

使用下面函数可以获得进程执行时间:

#include <unistd.h>
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 */
};
clock_t times(struct tms* buf); //返回wall clock time.但是需要通过差值来反映
  • 为了转换成为秒数,需要使用sysconf(_SC_CLK_TCK)得到每秒钟多少个滴答数。

参考

[1] https://www.ibm.com/developerworks/cn/aix/library/au-satuidgid/
[2] http://blog.csdn.net/mmshixing/article/details/51305138

你可能感兴趣的:(apue 第8章 进程控制)