APUE(8)---进程控制(1)

1、进程标识app

  每一个进程都有一个非负整型标识的惟一进程ID。由于进程ID标识符老是惟一的,常将其用作其余标识符的一部分以保证其惟一性。进程ID虽然是惟一的, 可是倒是能够复用的。ID为0的进程一般是调度进程,经常被称为交换进程(swapper)。该进程是内核的一部分,它并不执行任何磁盘上的程序,所以也被称为系统进程。进程ID为1一般是init进程,在自举过程结束时由内核调用。此进程负责在自举内核后启动一个UNIX系统,init一般读取与系统有关的初始化文件,并将系统引导一个状态。init进程毫不会终止。它是一个普通的用户进程,可是它以超级用户特权运行。进程ID为2是页守护进程,此进程负责支持虚拟存储器系统的分页操做。异步

#include <unistd.h>
pid_t getpid(void); //返回进程的进程ID
pid_t getppid(void); //返回进程的父进程ID
pid_t getuid(void); //返回进程的实际用户ID
pid_t geteuid(void); //返回调用进程的有效用户ID
pid_t getgid(void); //返回进程的实际组ID
pid_t getepid(void); //返回进程的有效组ID

2、函数fork函数

#include <unistd.h>
pid_t fork(void);

  由fork建立的新进程被称为子进程(child process)。fork函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新建子进程的进程ID。缘由:由于一个进程的子进程能够有不少个,而且没有一个函数使一个进程能够得到其全部子进程的进程ID;一个进程只会有一个父进程,因此子进程老是能够调用getppid以得到其父进程的进程ID。子进程是父进程的副本,子进程得到父进程数据空间、堆和栈的副本,父进程和子进程不共享这些存储空间部分。父进程和子进程只共享正文段。因为在fork以后常常跟随着exec,因此如今的不少实现并不执行一个父进程数据段和栈和堆的彻底副本,做为替代,使用了写时复制(Copy-On-Write)技术。ui

#include "apue.h"
int globvar = 0;
char buff[] = "a write to stdout\n";

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

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

    printf("before fork\n");

    if((pid = fork()) < 0)
    {
        err_sys(" fork error");
    }
    else if(pid == 0)
    {
        globvar ++;
        var ++;
    }
    else
    {
        sleep(2);
    }

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

8-1:fork函数实例spa

  fork的一个特性是父进程的全部打开文件描述符都被复制到子进程中。咱们说赋值是由于对每一个文件描述符来讲,就好像执行了dup函数,父进程和子进程每一个相同的打开描述符共享一个文件表项。重要的一点是:父进程和子进程共享同一个文件偏移量。code

8-2: fork以后父进程和子进程之间对打开文件的共享orm

  除了打开文件以外,父进程的不少其余属性也由子进程继承:实际用户ID、实际组ID、有效用户ID、有效组ID;附属组ID;进程组ID;会话ID;控制终端;设置用户ID标志和设置组ID标志;当前工做目录;根目录;文件模式建立屏蔽字;信号屏蔽和安排;对任一打开文件描述符的执行时关闭标志;环境;链接的共享存储段;存储映像;资源限制。父进程和子进程之间的区别在于:fork的返回值不一样;进程ID不一样;两个进程的父进程ID不一样;本身称tms_utime、tms_stime、tms_ustime的值设置为0;子进程不继承父进程设置的文件所;子进程的未处理闹钟被清除;子进程的未处理信号集设置为空集。blog

  fork失败的两个主要缘由:系统中已经有了太多的进程;该实际用户ID的进程总数超过了系统限制。fork有如下两个用法:一个父进程但愿复制本身,使父进程和子进程执行不一样的代码段;一个进程要执行一个不一样的程序。继承

3、函数vfork进程

  vfork与fork同样都建立一个子进程,可是它并不将父进程的地址空间彻底复制到子进程中,由于子进程会当即调用exec,预示也就不会引用该地址空间;vfork和fork之间的另外一个区别是:vfork保证子进程先运行,在它调用exec或exit以后父进程才可能被调度运行,当子进程调用这两个函数中的任意一个时,父进程会恢复运行。

#include "apue.h"

int globvar = 6;
int main(void)
{
    int var;
    pid_t pid;

    var = 88;
    pirntf("before vfork\n");
    if((pid = vfork()) < 0 )
    {
        err_sys("vfork error");
    }    
    else if(pid == 0)
    {
        globvar ++;
        var ++;
        _exit(0);
    }
    
    pirntf("pid = %ld, glob = %d, var = %d\n",(long)getpid(), globvar, var);
    exit(0);
}

8-3: vfork函数实例

4、函数exit

  在大多数UNIX系统实现中,exit是标准C库中的一个函数,而_exit则是一个系统调用。无论进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭全部打开描述符,释放它所使用的存储器等。在任意一种状况下,该终止进程的父进程都能用wait或waipid函数取得其终止状态。对于父进程已经终止的全部进程,它们的父进程都改变为init进程。咱们称这些进程由init进程手痒。在UNIX术语中,一个已经终止、可是父进程还没有对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程被称为僵死进程。

5、函数wait和waitpid

  当一个进程正常或异常终止时,内核就向父进程发送SIGCHLD信息。由于子进程终止是个异步事件,因此这种信号也是内核向父进程发的异步通知。父进程能够选择忽略该信号,或者提供一个该信号发生时即被调用执行的函数,对这种信号的系统默认动做是忽略它。

#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpidd(pid_t pid, int *statloc, int options);

  若是其全部子进程都还在运行,则阻塞;若是一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态当即返回;若是它没有任何子进程,则当即出错返回。

#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)
    {
        exit(7);
    }

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

    if(wait(&status) != pid)
    {
        err_sys("wait error");
    }
    pr_exit(status);

        if((pid = fork) < 0)
    {
        err_sys("fork error");
    }
    else if(pid == 0)
    {
        status /= 0;
    }

    if(wait(&status) != pid)
    {
        err_sys("wait error");
    }
    pr_exit(status);

    exit(0);
}

void pr_exit(int status)
{
    if(WIFEXITED(status))
    {
        printf("normal termination, exit status = %d\n", WEXITSTATUS(status));
    }
    else if(WIFSIGNALED(status))
    {
        printf("abnormal termination, signal number = %d\n", WTERMSIG(status),
        #ifdef WCORDUMP
        WCORDUMP(status)? "core file generated":"");
        #else
        "");
        #endif
    }
    else if(WIFSTOPPED(status))
    {
        printf("child stopped, signal number = &d\n", WSTOPSIG(status));
    }
}

8-6:演示不一样的exit值

  若是一个进程有几个子进程,那么只要有一个子进程终止,wait就返回。waitpid函数提供了wait函数没有提供的3个功能:一、waitpid可等待一个特定的进程,而wait则返回任一终止子进程的状态;二、waitpid提供了一个wait的非阻塞版本;三、waitpid经过WUNTRACED和WCONTINUED选项支持做业控制。

#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((pid = fork()) < 0)
        {
            err_sys("fork error");
        }
        else if(pid > 0)
        {
            exit(0);
        }

        sleep(2);
        printf("second child, parent pid = %ld\n",(long) getppid());
        exit(0);
    }

    if(waitpid(pid, NULL, 0) != pid)
    {
        err_sys("waitpid error");
    }

    exit(0);
}

8-8:fork两次以免僵死进程

6、函数exec

  当进程调用一种exec函数时,该进程执行的程序彻底替换为新程序,而新程序则从其main函数开始执行。由于调用exec并不建立新进程,因此先后的进程ID并未改变。exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。用fork能够建立新进程,用exec能够初始化执行新的程序。exit函数和wait函数处理终止和等待终止。这些是咱们须要的基本的进程控制原语。

#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *file, char *const argv[], char *const envp[]);

8-15:7个exec函数之间的关系

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

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

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

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

    if((pid == fork()) < 0)
    {
        err_sys("fork error");
    }
    else if(pid == 0)
    {
        if(execlp("echoall", "echoall", "only 1arg", (char *)0) < 0)
        {
            err_sys("execle error");
        }
    }

    exit(0);
}

8-16:exec函数实例

相关文章
相关标签/搜索