在执行过程中,一个进程可能创建新的过程。 前面提到过,创建进程称为父进程,而新进程被称为该进程的子进程。每一个新的进程可以反过来创建其他进程,形成进程树。

很多操作系统(包括UNIX,Linux和Windows)都通过唯一的进程标识符(PID)来标识进程,这个符号是一个int数字。

PID为系统中的每个进程提供了一个独特的值,它可以作为一个索引来访问内核中的进程的各种属性。

图3.8展示了Linux操作系统的典型的进程树,它显示了每个进程的名称和pid。(在Linux中通常使用任务来描述进程)。init进程(它的PID总是1)作为所有用户进程的根父进程。系统启动后,init进程可以创建多个用户进程,如web或打印服务器,一个ssh服务器,等等。在图3.8中,我们看到了init的两个子进程------- kthreadd和sshd。kthreadd进程创建用于代表内核执行任务的额外进程(在本例子中是指khelper和pdflush)。而sshd进程负责管理通过使用ssh(secure shell的缩写)连接到系统的客户端。login进程负责管理直接登录系统的客户端。在本例中,一个客户端使用bash shell登录上来,该shell被分配为pid 8416。使用bash命令行界面,该用户创建了ps进程以及emacs编辑器。

UNIX和Linux系统上,我们可以通过使用ps命令来获得进程的列表。 例如:

ps -el

该命令将会列出系统中当前活动的所有进程的完整信息。通过递归地跟踪父进程一直到init进程,构建类似于图3.8所示的流程树是很容易的。

通常,当进程创建一个子进程时,子进程需要某些资源(CPU时间,内存,文件,I/O设备)来完成它的任务。

子进程可以直接从操作系统那里获取这些资源,或者被限制为父进程资源的一个子集(即从父进程获取资源)。父类可能必须在其子进程之间分配资源,或者可以在几个子进程中共享一些资源(比如内存或文件)。将子进程限制为父进程资源的子集可以防止任何进程通过创建过多的子进程来超载系统。

除了提供各种物理和逻辑资源,父进程可能会将初始化数据(输入)传递给子进程。比如,考虑一个进程,它的功能是在终端屏幕上展示文件的内容-----image.jpg的内容。当一个进程被创建时,它将从它的父进程中获取文件image.jpg的名称。使用这个文件名,它可以打开文件,并将其内容写出去。它可还可以获取输出设备的名称。另外,一些操作系统将资源传递给子进程。在这样一个系统上,新的进程可能会得到两个打开的文件,image.jpg和终端设备,新进程只需要简单地在这两者之间传输数据。

当一个进程创建一个新进程时,存在两种执行可能性:

  • 父进程与其子进程并发的执行
  • 父进程等待,直到它的某些或者所有的子进程都终止为止。

新程序还有两个地址空间可能性:

  • 子进程是父进程的副本(它具有与父进程相同的程序和数据)。

  • 子进程有一个加载给它的新程序。

为了说明这些差异,让我们首先考虑UNIX操作系统。在UNIX中,正如我们所看到的,每个进程都由其进程标识符标识,它是一个惟一的整数。一个新的进程由fork()系统调用创建。新进程由原始进程的地址空间的副本组成。该机制允许父进程轻松地与其子进程通信。两个进程(父进程和子进程)在fork()之后继续执行,有一个区别

  • 对于子进程,fork()的返回码是0,
  • 对于父进程,子进程的(非零)进程标识符返回给父进程。

在fork()系统调用之后,其中一个进程通常会使用exec()系统调用,用新程序来替换进程的内存空间。exec()系统调用会加载二进制文件进内存(消毁包含exec()系统调用的程序的内存映像)并启动执行。如此,这两个进程可以进行通信,并可以按照自己的方式执行。

父进程可以在之后继续创建更多的子进程,或者,当子进程运行时候,父进程没有事做的话,那么他可以采用wait()系统调用将自己移除就绪队列,直到子进程终止为止。

因为对exec()的调用通过一个新程序覆盖进程的地址空间,对exec()的调用不会返回控制权,除非发生错误。

以C程序代码解释:代码可以下载,该文件名字是newproc-posix.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main()
{
pid_t pid;

    /* fork a child process */
    pid = fork();

    if (pid < 0) { /* error occurred */
        fprintf(stderr, "Fork Failed\n");
        return 1;
    }
    else if (pid == 0) { /* child process */
        printf("I am the child %d\n",pid);
        execlp("/bin/ls","ls",NULL);
    }
    else { /* parent process */
        /* parent will wait for the child to complete */
        printf("I am the parent %d\n",pid);
        wait(NULL);

        printf("Child Complete\n");
    }

    return 0;
}

在Linux上需要编译:

gcc newproc-posix.c -o newproc-posix.out

执行:

./newproc-posix.out

输出:

[root@localhost temp]# gcc newproc-posix.c -o newproc-posix.out
[root@localhost temp]# ./newproc-posix.out 
I am the parent 24405
I am the child 0
newproc-posix.c  newproc-posix.out  unix_pipe.c  unix_pipe.o  unix_pipe.out
Child Complete

fork()系统调用会创建一个子进程,父子进程的区别是子进程的PID是0,而父进程的PID大于0,上述的打印已经验证了。

父子进程都运行相同的程序的副本,子进程继承父进程的特权和调度属性,还有一些资源,比如打开的文件。

然后,子进程通过调用execlp()(它是exec()的一个版本)系统调用,传递UNIX命令/bin/ls来覆盖其地址空间。

父进程会从wait()处等待子进程结束(通过隐式或显式地调用exit()),父进程从wait()调用中恢复,使用exit()系统调用完成。

当然,没有什么可以阻止子进程不调用exec(),而是继续作为父进程的副本执行。在这个场景中,父进程和子进程是运行相同代码的并发进程。因为子进程是父进程的副本,每个进程都有任务数据的自己的副本。

在Window中,使用createProcess()系统调用来创建子进程,它与fork()作用类似。但是,fork()系统调用中,子进程继承父进程的地址空间,而CreateProcess()系统调用在进程创建过程中,需要将一个特殊的程序加载到子进程的地址空间中,而且,fork()没有传递参数,CreateProcess()至少需要10个参数:

Windows下C程序代码:

#include <windows.h>
#include <stdio.h>

int main( VOID )
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );

    // Start the child process.
    if( !CreateProcess( NULL,   // No module name (use command line).
        "C:\\WINDOWS\\system32\\mspaint.exe", // Command line.
        NULL,             // Process handle not inheritable.
        NULL,             // Thread handle not inheritable.
        FALSE,            // Set handle inheritance to FALSE.
        0,                // No creation flags.
        NULL,             // Use parent's environment block.
        NULL,             // Use parent's starting directory.
        &si,              // Pointer to STARTUPINFO structure.
        &pi )             // Pointer to PROCESS_INFORMATION structure.
    )
    {
        printf( "CreateProcess failed (%d).\n", GetLastError() );
        return -1;
    }

    // Wait until child process exits.
    WaitForSingleObject( pi.hProcess, INFINITE );

    // Close process and thread handles.
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );
}

代码中,C程序加载了“C:\WINDOWS\system32\mspaint.exe”程序,并传递 一些列参数,创建出一个进程。

样例中传递给CreateProcess() 的两个参数是STARTUPINFO和PROCESS_INFORMATION结构体。

STARTUPINFO:指定新进程 一些属性值,比如窗口大小,标准输入和输出 句柄。

PROCESS_INFORMATION:包含一个句柄以及新生成的进程和线程的标识。

在调用CreateProcess() 之前,调用ZeroMemory()函数来为每个结构分配内存。

传递给CreateProcess()的前两个参数是应用的名字以及命令行参数。如果应用名字是NULL,那么由命令行参数加载指定的应用。在C程序中,我们即加载的 mspaint.exe程序。

除了这两个初始参数之外,我们还使用了继承进程和线程句柄的默认参数,并指定不会有创建标志。

我们还使用父进程的现有环境块和启动目录。

最后,我们提供了两个指向在程序开始时创建的STARTUPINFO和PROCESS -INFORMATION结构体的指针。在图3.9中,父进程通过调用wait()系统调用来等待子进程完成。在Windows中等效的是WaitForSingleObject(),它通过传递子进程的句柄-------- pi.hProcess——等待这个进程完成结束。一旦子进程结束,控制从WaitForSingleObject()函数返回到父进程中。

results matching ""

    No results matching ""