apue2 阅读笔记--第八章

1. 关于0号和1号进程

There are some special processes, but the details differ from implementation to implementation. Process ID 0 is usually the scheduler process and is often known as the swapper. No program on disk corresponds to this process, which is part of the kernel and is known as a system process. Process ID 1 is usually the init process and is invoked by the kernel at the end of the bootstrap procedure. The program file for this process was /etc/init in older versions of the UNIX System and is /sbin/init in newer versions. This process is responsible for bringing up a UNIX system after the kernel has been bootstrapped. init usually reads the system-dependent initialization filesthe /etc/rc* files or /etc/inittab and the files in /etc/init.dand brings the system to a certain state, such as multiuser. The init process never dies. It is a normal user process, not a system process within the kernel, like the swapper, although it does run with superuser privileges. Later in this chapter, we'll see how initbecomes the parent process of any orphaned child process.

注意0号进程是内核进程,它在结束前调用用户态的init进程,通常是/sbin/init。

几个关于id的函数

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

Returns: process ID of calling process

pid_t getppid(void);

Returns: parent process ID of calling process

uid_t getuid(void);

Returns: real user ID of calling process

uid_t geteuid(void);

Returns: effective user ID of calling process

gid_t getgid(void);

Returns: real group ID of calling process

gid_t getegid(void);

Returns: effective group ID of calling process

2. 创建进程 pid_t fork (void)

(1)Both the child and the parent continue executing with the instruction that follows the call to fork. The child is a copy of the parent. For example, the child gets a copy of the parent's data space, heap, and stack. Note that this is a copy for the child; the parent and the child do not share these portions of memory. The parent and the child share the text segment

注意只有代码段是子进程和父进程共享的,其他的,包括数据段的全局变量,子进程的都是父进程的拷贝。

(2)来个小例子说明缓存I/O和fork的相互关系

#include "apue.h"

int     glob = 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 */
        glob++;                 /* modify variables */
        var++;
    } else {
        sleep(2);               /* parent */
    }

    printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
    exit(0);
}
对于printf (“before fork\n”); 一句,当直接编译运行该例子时,输出一行,将本程序的输出重定向时,有两行输出,因为终端文件是行缓存的,而普通文件是全缓存的,子进程获得了父进程打开文件缓冲区的拷贝。

(3)关于文件共享

image

  • Real user ID, real group ID, effective user ID, effective group ID

  • Supplementary group IDs

  • Process group ID

  • Session ID

  • Controlling terminal

  • The set-user-ID and set-group-ID flags

  • Current working directory

  • Root directory

  • File mode creation mask

  • Signal mask and dispositions

  • The close-on-exec flag for any open file descriptors

  • Environment

  • Attached shared memory segments

  • Memory mappings

  • Resource limits

The differences between the parent and child are

  • The return value from fork

  • The process IDs are different

  • The two processes have different parent process IDs: the parent process ID of the child is the parent; the parent process ID of the parent doesn't change

  • The child's tms_utime, tms_stime, tms_cutime, and tms_cstime values are set to 0

  • File locks set by the parent are not inherited by the child

  • Pending alarms are cleared for the child

  • The set of pending signals for the child is set to the empty set

(4)vfork函数, 在接着调用exec函数的时候使用

3. exit 函数

As we described in Section 7.3, a process can terminate normally in five ways:

  1. Executing a return from the main function. As we saw in Section 7.3, this is equivalent to calling exit.

  2. Calling the exit function. This function is defined by ISO C and includes the calling of all exit handlers that have been registered by calling atexit and closing all standard I/O streams. Because ISO C does not deal with file descriptors, multiple processes (parents and children), and job control, the definition of this function is incomplete for a UNIX system.

  3. Calling the _exit or _Exit function. ISO C defines _Exit to provide a way for a process to terminate without running exit handlers or signal handlers. Whether or not standard I/O streams are flushed depends on the implementation. On UNIX systems, _Exit and _exit are synonymous and do not flush standard I/O streams. The _exit function is called by exit and handles the UNIX system-specific details; _exitis specified by POSIX.1.

    In most UNIX system implementations, exit(3) is a function in the standard C library, whereas _exit(2) is a system call.

     

  4. Executing a return from the start routine of the last thread in the process. The return value of the thread is not used as the return value of the process, however. When the last thread returns from its start routine, the process exits with a termination status of 0.

  5. Calling the pthread_exit function from the last thread in the process. As with the previous case, the exit status of the process in this situation is always 0, regardless of the argument passed to pthread_exit. We'll say more about pthread_exit in Section 11.5.

The three forms of abnormal termination are as follows:

  1. Calling abort. This is a special case of the next item, as it generates the SIGABRT signal.

  2. When the process receives certain signals. (We describe signals in more detail in Chapter 10). The signal can be generated by the process itselffor example, by calling the abort functionby some other process, or by the kernel. Examples of signals generated by the kernel include the process referencing a memory location not within its address space or trying to divide by 0.

  3. The last thread responds to a cancellation request. By default, cancellation occurs in a deferred manner: one thread requests that another be canceled, and sometime later, the target thread terminates. We discuss cancellation requests in detail in Sections 11.5 and 12.7.

Regardless of how a process terminates, the same code in the kernel is eventually executed. This kernel code closes all the open descriptors for the process, releases the memory that it was using, and the like.

进程退出的几种方式。

what happens when a process that has been inherited by init terminates? Does it become a zombie? The answer is "no," because init is written so that whenever one of its children terminates, init calls one of the wait functions to fetch the termination status. By doing this, init prevents the system from being clogged by zombies. When we say "one of init's children," we mean either a process that init generates directly (such as getty, which we describe in Section 9.2) or a process whose parent has terminated and has been subsequently inherited by init.

init的子进程退出,一定会被回收。

4. wait 和 waitpid

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

Both return: process ID if OK, 0 (see later), or 1 on error

The interpretation of the pid argument for waitpiddepends on its value:

pid ==1

Waits for any child process. In this respect, waitpid is equivalent to waitpid > 0 Waits for the child whose process ID equals pid. pid ==0

Waits for any child whose process group ID equals that of the calling process. (We discuss process groups in Section 9.4.)

pid <1 Waits for any child whose process group ID equals the absolute value of pid.

来段代码,通过两次fork来防止僵尸进程的产生,不用wait 和 waitpid函数。

#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 becomes 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 = %d\n", getppid());
        exit(0);
    }
   
    if (waitpid(pid, NULL, 0) != pid)  /* wait for first child */
        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);
}

5.

(1) 竞态

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

(2) exec函数族

用f o r k函数创建子进程后,子进程往往要调用一种e x e c函数以执行另一个程序。
当进程调用一种e x e c函数时,该进程完全由新程序代换,而新程序则从其 m a i n函数开始执行。

#include <unistd.h>
int execl(const char * p a t h n a m e, const char *a rg 0, ... /* (char *) 0 */);
int execv(const char * p a t h n a m e, char *const a rgv [] );
int execle(const char * p a t h n a m e, const char *a rg 0, .../* (char *)0, char *const e n v p [] */);
int execve(const char * p a t h n a m e, char *const a rgv [], char *const envp [] );
int execlp(const char * f i l e n a m e, const char *a rg 0, ... /* (char *) 0 */);
int execvp(const char * f i l e n a m e, char *const a rgv [] );
六个函数返回:若出错则为-1,若成功则不返回。

如果e x e c l p和e x e c v p中的任意一个使用路径前缀中的一个找到了一个可执行文件,但是该
文件不是由连接编辑程序产生的机器可执行代码文件,则就认为该文件是一个 s h e l l脚本,于是
试着调用/ b i n / s h,并以该f i l e n a m e作为s h e l l的输入。
第二个区别与参数表的传递有关( l表示表( l i s t ),v表示矢量( v e c t o r ) )。函数e x e c l、e x e c l p和
e x e c l e要求将新程序的每个命令行参数都说明为一个单独的参数。这种参数表以空指针结尾。
对于另外三个函数( e x e c v, e x e c v p和e x e c v e ),则应先构造一个指向各参数的指针数组,然后将该
数组地址作为这三个函数的参数。

以 e 结尾的两个函数( e x e c l e和e x e c v e)可以传递一个指向环境字符串指针数组的指针。

其他四个函数则使用调用进程中的e n v i r o n变量为新程序复制现存的环境。

这六个e x e c函数的参数很难记忆。函数名中的字符会给我们一些帮助。字母 p表示该函数
取f i l e n a m e作为参数,并且用PAT H环境变量寻找可执行文件。字母l表示该函数取一个参数表,
它与字母v互斥。v表示该函数取一个a rg v[ ]。最后,字母e表示该函数取e n v p[ ] 数组,而不使
用当前环境。

在很多U N I X实现中,这六个函数中只有一个e x e c v e是内核的系统调用。另外五个只是库函
数,它们最终都要调用系统调用。这六个函数之间的关系示于图8 - 2中。在这种安排中,库函数
execlp 和execvp 使用PAT H环境变量查找第一个包含名为f i l e n a m e的可执行文件的路径名前缀。

(3) system 函数

#include <stdlib.h>
int system(const char * c m d s t r i n g) ;

如果c m d s t r i n g是一个空指针,则仅当命令处理程序可用时, s y s t e m返回非0值,这一特征可以
决定在一个给定的操作系统上是否支持s y s t e m函数。在U N I X中,s y s t e m总是可用的。

因为s y s t e m在其实现中调用了f o r k、e x e c和w a i t p i d,因此有三种返回值:
1) 如果f o r k失败或者w a i t p i d返回除E I N T R之外的出错,则s y s t e m返回-1,而且e r r n o中设
置了错误类型。
2) 如果e x e c失败(表示不能执行s h e l l ),则其返回值如同s h e l l执行了e x i t ( 1 2 7 )一样。
3) 否则所有三个函数( f o r k , e x e c和w a i t p i d )都成功,并且s y s t e m的返回值是s h e l l的终止状态,
其格式已在w a i t p i d中说明。

使用s y s t e m而不是直接使用f o r k和e x e c的优点是:s y s t e m进行了所需的各种出错处理,以

及各种信号处理(在1 0 . 1 8节中的下一个版本s y s t e m函数中)。

posted @ 2011-11-25 18:12  jialejiahi  阅读(271)  评论(0编辑  收藏  举报