首页 > 技术文章 > 《Linux/UNIX系统编程手册》第28章 详述进程创建和程序执行

arnoldlu 2020-12-19 00:00 原文

关键词:acct()、clone()、fork()/vfork()等等。

 

内核对进程记账会使系统在每个进程结束后记录一条账单信息。

Linux通过fork()、vfork()、clone()创建进程,其中clone()提供了更为精细的控制。

接着比较了fork()、vfork()、clone()、fork()+exec()、vfork()+exec()创建进程熟读和虚拟内存消耗总量。

exec()执行新程序替换当前进程,然后详细比较了exec()和fork()两者对创建进程属性的影响。

1. 进程记账

内核对进程记账的信息包括终止状态以及进程消耗的CPU时间。

特权进程可利用acct()来打开和关闭进程记账功能。

#define _BSD_SOURCE
#include <unistd.h>
int acct(const char *acctfile);
    Returns 0 on success, or –1 on error

2. 系统调用clone()

类似于fork()和vfork(),clone()也能创建一个新进程,clone()对进程创建步骤控制更为精准。

#define _GNU_SOURCE
#include <sched.h>
int clone(int (*func) (void *), void *child_stack, int flags, void *func_arg, ...
/* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );
    Returns process ID of child on success, or –1 on error

clone()创建的新锦成几近于父进程的翻版,但是子进程继续运行时不以调用处为起点,转而去调用参数func所指定的函数。

 调用者必须分配一块大小适中的内存空间供子进程的栈使用,同时将这块内存的指针置于参数child_stack中。child_stack应当指向所分配内存块的高端。

flags低字节中存放着子进程的中职信号,剩余字节存放掩码,用于控制clone()操作。

 剩余参数ptkd、tls、ctid与线程的实现相关。

#define _GNU_SOURCE
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sched.h>
#include "tlpi_hdr.h"

#ifndef CHILD_SIG
#define CHILD_SIG SIGUSR1       /* Signal to be generated on termination
                                   of cloned child */
#endif

static int                      /* Startup function for cloned child */
childFunc(void *arg)
{
    if (close(*((int *) arg)) == -1)
        errExit("close");

    return 0;                           /* Child terminates now */
}

int
main(int argc, char *argv[])
{
    const int STACK_SIZE = 65536;       /* Stack size for cloned child */
    char *stack;                        /* Start of stack buffer */
    char *stackTop;                     /* End of stack buffer */
    int s, fd, flags;

    fd = open("/dev/null", O_RDWR);     /* Child will close this fd */
    if (fd == -1)
        errExit("open");

    /* If argc > 1, child shares file descriptor table with parent */

    flags = (argc > 1) ? CLONE_FILES : 0;-----------------------------------使用CLONE_FILES之后,父子进程共享同一个文件描述符表。

    /* Allocate stack for child */

    stack = malloc(STACK_SIZE);
    if (stack == NULL)
        errExit("malloc");
    stackTop = stack + STACK_SIZE;      /* Assume stack grows downward */---分配一个栈供子进程使用,并将高地址作为栈开始。

    /* Ignore CHILD_SIG, in case it is a signal whose default is to
       terminate the process; but don't ignore SIGCHLD (which is ignored
       by default), since that would prevent the creation of a zombie. */

    if (CHILD_SIG != 0 && CHILD_SIG != SIGCHLD)
        if (signal(CHILD_SIG, SIG_IGN) == SIG_ERR)          errExit("signal");

    /* Create child; child commences execution in childFunc() */

    if (clone(childFunc, stackTop, flags | CHILD_SIG, (void *) &fd) == -1)--创建子进程。
        errExit("clone");

    /* Parent falls through to here. Wait for child; __WCLONE is
       needed for child notifying with signal other than SIGCHLD. */

    if (waitpid(-1, NULL, (CHILD_SIG != SIGCHLD) ? __WCLONE : 0) == -1)------等待子进程终止。
        errExit("waitpid");
    printf("child has terminated\n");

    /* Did close() of file descriptor in child affect parent? */

    s = write(fd, "x", 1);---------------------------------------------------调用write()检查文件描述符是否仍处于打开状态。
    if (s == -1 && errno == EBADF)
        printf("file descriptor %d has been closed\n", fd);
    else if (s == -1)
        printf("write() on file descriptor %d failed "
                "unexpectedly (%s)\n", fd, strerror(errno));
    else
        printf("write() on file descriptor %d succeeded\n", fd);

    exit(EXIT_SUCCESS);
}

 Linux Threads线程实现使用clone()来创建线程:CLONE_VM|CLONE_FILES|CLONE_FS|CLONE_SIGHAND。

3. 进程的创建速度

x86 32系统,内核2.6.27测试数据:

 

 

第1行进程所占内存越大,fork()所需时间越长。额外时间花在了为子进程复制页表、将数据段、堆栈页表标记为只读的工作上。

第2行尽管进程的虚拟内存总量在增加,但所使用的时间保持不变。因为vfork()并未复制页表或页,调用进程的虚拟内存总大小并未造成影响。

第3行clone()使用CLONE_VM|CLONE_VFORK|CLONE_FS|CLONE_SIGHAND|CLONE_FILES,前两个模拟vfork(),剩余则要求父子进程共享文件系统属性、信号处置表、打开文件描述符表。和vfork()之间的差值表示了这些额外工作的开销。

第4行和第2行的差值,说明执行新程序的开销。

第5行和第3行的差值,说明执行新程序的开销。

4. exec()和fork()对进程属性的影响

fork()、vfork()、clone()使用do_fork()系统调用,和exec()有较大差异。

下表详细描述了exec()和fork()对进程属性的影响:

 

 

 

 

 

 

 

 

 

 

 

推荐阅读