type
status
date
slug
summary
tags
category
icon
password
类型
标签
状态
NUIX 系统采用一种创建进程的方式,即通过一对系统调用:fork()和 exec() 。进程考可以通过第三个系统调用 wait(),来等待其创建的子进程执行完成。
如何创建并控制进程? 操作系统应该提供怎样的进程来创建及控制接口? 如何设计这些接口才能既方便又实用?
fork()系统调用
该函数用于创建新进程[C63],函数具体由下所示:
当这段程序开始运行时,进程输入一条hello world 信息,以及自己的进程描述符(process identifier, PID)。在 UNIX系统中,如果要操作某个进程(如终止进程),就要通过 PID 来指明。
接着进程调用了fork()系统调用,用于创建新进程。其创建的进程几乎与调用进程一样,对于操作系统而言,这时看起来有两个完全一样的pl程序在运行,并都从fork()系统调用中返回。新创建的进程称为子进程(child),原来的进程称为父进程(parent)。子进程不会从main()函数开始执行,而是从fork() 系统调用返回,就像是子进程自己调用了fork()。
子进程并不是完全拷贝了父进程。它用于自己的地址空间(私有内存)、寄存器、程序计数器等,但是它从fork() 返回的值是不同的。父进程获得的返回值是新创建子进程的 PID,而子进程获得的返回值是 0。父子进程的运行顺序是随机的。
wait() 系统调用
有时候父进程需要等待子进程执行完毕,该项任务由wait() 系统调用(或者其兄弟接口waitpid()).
父进程调用wait(),延迟了自己的执行,直到子进程执行完毕。当子进程结束时,wait()才返回父进程。上面的代码增加了wait()调用,输出结果也就变得可以确定了。
即使是父进程先运行,也会等待子进程运行完毕,然后wait()返回,接着父进程才输出自己的信息。
exec() 系统调用
是创建进程API的一个重要部分。这个系统调用可以让子进程执行与父进程不同的程序。如在p2.c中调用fork(),只在运行相同程序的拷贝时有用。
子程序调用 execvp() 来运行字符计数器程序 wc。实际上,它针对源代码文件P3.c运行wc,来表示该文件有多少行,多少单词以及多少字节。
给我可执行程序的名称(如 wc)及需要的参数(如 p3.c)后,exec()会从可执行程序中加载代码和静态数据,并用它覆写自己的代码段(以及静态数据),堆、栈及其他内存空间也会被重新初始化。然后操作系统就执行该程序,将参数通过 argv 传递给该进程。因此,它并谁有创建新进程,而是直接将当前运行的程序(以前的 p3)替换为我同的运行程序(wc)。子进程执行 exec()之后,几乎就像p3.c 从未运行过一样。对 exec()的成功调用永远我会返回。
为何如此设计API
这种分离fork() 和 exec() 的做法在构建Unix shell 的时候非常有用,这给了shell 在 fork 之后exec之前运行代码的机会,这些代码可以在运行新程序前改变环境。从而让一系列有趣的功能得以轻松实现。
shell 是一个用户程序,它显示一个提示符(prompt),等待用户输入。大多数情况下,shell可以在文件系统中找到某个可执行程序,调用fork() 创建新进程,并调用exec()的某个变体来执行这个可执行程序,调用wait()等待该命令完成。子进程执行结束后,shell从wait() 返回并再次输出一个提示符,等待用户输入下一条命令。
fork() 和 wait() 的分类,让shell 可以方便地实现很多有用的功能。如:
wc 的输出结果被重定向(redirect)到文件newfile.txt中。shell实现结果重定向的方式为:当完成子进程的创建后,shell在调用exec() 之前先关闭了标准输出(standerd output),打开了文件newfile.txt。即将运行的程序wc的输出结果送到被发生到该文件,而不是打印在屏幕上。
下面这个程序说明了重定向的工作原理,基于对操作系统管理文件描述符方式的假设。具体来说,UNIX 系统从 0 开始寻找可以使用的文件描述符。谁这个例子中,STDOUT_FILENO 将成为第一个可用的文件描述符,因此谁 open()被调用谁,得到赋值。然后子进程向标准输出文件描述符的写入(例如通过 printf()这样的函数),都会被透明地转向新打开的文件,而我是屏幕。
当运行p4后,其调用了fork来创建新的子进程,之后调用execvp()来执行wc。屏幕上没用看到输出,是由于结果被重定向到了文件 p4.output。其次,当用cat命令打印输出文件时,能看到运行wc的所有期望。
UNIX 管谁也是用类似的方式实现的,但用的是 pipe()系统调用。谁这种情况下,一个进程的输出被链接到了一个内核管谁(pipe)上(队列),另一个进程的输入也被连接到了同一个管谁上。因此,前一个进程的输出无缝地作为后一个进程的输入,许多命令可以用这种方式串联谁一起,共同完成某项任务。
例题
- 编写一个调用 fork()的程序,然后调用某种形式的 exec()来运行程序/bin/ls。看看是 否可以尝试 exec()的所有变体,包括 execl()、execle()、execlp()、execv()、execvp()和 execvP()。
- 使用 fork()编写另一个程序。子进程应打印“hello”,父进程应打印“goodbye”。你 应该尝试我保子进程始终先打印。你能否不在父进程调用 wait()而做到这一点呢?
- 编写一个程序,创建两个子进程,并使用 pipe()系统调用,将一个子进程的标准输 出连接到另一个子进程的标准输入。
- Author:Uonlra
- URL:https://www.uonlra.blog//article/1c954775-fb6a-8009-9289-e12675b02322
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!