在 UNIX 系统中,用户创建一个新进程的唯一方法就是调用系统调用 fork。调 用 fork 的进程称为父进程,而新创建的进程叫做子进程。系统 调用的语法格式:
pid = fork();
在从系统调用 fork 中返回时,两个进程除了返回值 pid 不同外,具有 完全一样的用户级上下文。在子进程中,pid 的值为零。在系统启动时由核心内 部地创建的进程0是唯一不通过系统调用 fork 而创建的进程。
核心为系统调用 fork 完成下列操作:
1. 为新进程在进程表中分配一个空项。
2. 为子进程赋一个唯一的进程标识号 (PID)。
3. 做一个父进程上下文的逻辑副本。由于进程的某些部分,如正文区,可能被几个 进程所共享,所以核心有时只要增加某个区的引用数即可,而不是真的将该区拷贝到一个 新的内存物理区。
4. 增加与该进程相关联的文件表和索引节点表的引用数。
5. 对父进程返回子进程的进程号,对子进程返回零。
理解系统调用 fork 的实现是十分重要的,因为子进程就象从天而降一样地开始 它的执行序列。
下面是系统调用 fork 的算法。核心首先确信有足够的资源来成功完成 fork。 如果资源不满足要求,则系统调用 fork 失败。如果资源满足要求,核心在进程 表中找一个空项,并开始构造子进程的上下文。
算法:fork
输入:无
输出:对父进程是子进程的 PID
对子进程是0
{
检查可用的核心资源
取一个空闲的进程表项和唯一的 PID 号
检查用户没有过多的运行进程
将子进程的状态设置为“创建”状态
将父进程的进程表中的数据拷贝到子进程表中
当前目录的索引节点和改变的根目录(如果可以)的引用数加1
文件表中的打开文件的引用数加1
在内存中作父进程上下文的拷贝
在子进程的系统级上下文中压入虚设系统级上下文层
/* 虚设上下文层中含有使子进程能
* 识别自己的数据,并使子进程被调度时
* 从这里开始运行
*/
if (正在执行的进程是父进程) {
将子进程的状态设置为“就绪”状态
return (子进程的 PID) // 从系统到用户
}
else {
初始化计时区
return 0;
}
}
我们来看看下面的例子。该程序说明的是经过系统调用 fork 之后,对文件的 共享存取。用户调用该程序时应有两个参数,一个是已经有的文件名,另外一个是要 创建的新文件名。该进程打开已有的文件,创建一个新文件,然后,假定没有遇见过 错误,它调用 fork 来创建一个子进程。子进程可以通过使用相同的文件描述 符而继承地存取父进程的文件(即父进程已经打开和创建的文件)。
当然,父进程和子进程要分别独立地调用 rdwrt 函数,并执行一个循环,即从 源文件中读一个字节,然后写一个字节到目标文件中区。当系统调用 read 遇见 文件尾时,函数 rdwrt 立即返回。
#include <fcntl.h>
int fdrd, fdwt;
char c;
main(int argc, char *argv[])
{
if (argc != 3) {
exit(1);
}
if ((fdrd = open(argv[1], O_RDONLY)) == -1) {
exit(1);
}
if ((fdwt = creat(argv[2], 0666)) == -1) {
exit(1);
}
fork();
// 两个进程执行同样的代码
rdwrt();
exit(0);
}
rdwrt()
{
for (;;) {
if (read(fdrd, &c, 1) != 1) {
return ;
}
write(fdwt, &c, 1);
}
}
在这个例子中,两个进程的文件描述符都指向相同的文件表项。这两个进程永远 不会读或写到相同的文件偏移量,因为核心在每次 read 和 write 调用 之后,都要增加文件的偏移量。尽管两个进程似乎是将源文件拷贝了两次,但因为 他们分担了工作任务,因此,目标文件的内容依赖于核心调度两个进程的次序。如果 核心这样调度两个进程:使他们交替地执行他们的系统调用,或甚至使他们交替地 执行每对 read 和 write 调用,则目标文件的内容和源文件的内容完全一致。但考虑 这样的情况:两个进程正要读源文件中的两个连续的字符 "ab"。假定父进程读了字 符 "a",这时,核心在父进程写之前,做了上下文切换来执行子进程。如果子进程 读到字符 "b",并在父进程被调度前,将它写到目标文件,那么目标文件将不再含有 字符串 "ab",而是含有 "ba"了。核心并不保证进程执行的相对速率。
再来看看另外一个例子:
#include <string.h>
char string[] = "Hello, world";
main()
{
int count, i;
int to_par[2], to_chil[2]; // 到父、子进程的管道
char buf[256];
pipe(to_par);
pipe(to_chil);
if (fork() == 0) {
// 子进程在此执行
close(0); // 关闭老的标准输入
dup(to_child[0]); // 将管道的读复制到标准输入
close(1); // 关闭老的标准输出
dup(to_par[1]); // 将管道的写复制到标准输出
close(to_par[1]); // 关闭不必要的管道描述符
close(to_chil[0]);
close(to_par[0]);
close(to_chil[1]);
for (;;) {
if ((count = read(0, buf, sizeof(buf)) == 0)
exit();
write(1, buf, count);
}
}
// 父进程在此执行
close(1); // 重新设置标准输入、输出
dup(to_chil[1]);
close(0);
dup(to_par[0]);
close(to_chil[1]);
close(to_par[0]);
close(to_chil[0]);
close(to_par[1]);
for (i = 0; i < 15; i++) {
write(1, string, strlen(string));
read(0, buf, sizeof(buf));
}
}
子进程从父进程继承了文件描述符0和1(标准输入和标准输出)。两次执行系统调用 pipe 分别在数组 to_par 和 to_chil 中分配了两个文件描述符。然后该进程 执行系统调用 fork,并复制进程上下文:象前一个例子一样,每个进程存取 自己的私有数据。父进程关闭他的标准输出文件(文件描述符1),并复制(dup)从管道 线 to_chil 返回的写文件描述符。因为在父进程文件描述符表中的第一个空槽是刚刚 由关闭腾出来的,所以核心将管道线写文件描述符复制到了文件描述符表中的第一 项中,这样,标准输出文件描述符变成了管道线 to_chil 的写文件描述符。 父进程以类似的操作将标准输入文件描述符替换为管道线 to_par 的读文件 描述符。与此类似,子进程关闭他的标准输入文件(文件描述符0),然后复制 (dup) 管道 线 to_chil 的读文件描述符。由于文件描述符表的第一个空项是原先的标准 输入项,所以子进程的标准输入变成了管道线 to_chil 的读文件描述符。 子进程做一组类似的操作使他的标准输出变成管道线 to_par 的写文件描述 符。然后两个进程关闭从 pipe 返回的文件描述符。上述操作的结果是:当 父进程向标准输出写东西的时候,他实际上是写向 to_chil--向子进程发送 数据,而子进程则从他的标准输入读管道线。当子进程向他的标准输出写的时候, 他实际上是写入 to_par--向父进程发送数据,而父进程则从他的标准输入 接收来自管道线的数据。两个进程通过两条管道线交换消息。
无论两个进程执行的顺序如何,这个程序执行的结果是不变的。他们可能去执行睡眠 和唤醒来等待对方。父进程在15次循环后退出。然后子进程因管道线没有写进程而读 到“文件尾”标志,并退出。
转自:http://hi.baidu.com/skyeye_zeus/blog/item/8aa740800d9f4ad29023d9e2.html
分享到:
相关推荐
在“易语言监视新进程创建”的主题中,我们主要探讨如何使用易语言来实现对计算机中新创建的进程进行实时监控。 首先,我们需要理解进程的基本概念。在操作系统中,进程是程序的一次执行实例,它包含了程序运行时所...
### Linux进程创建与进程通信知识点解析 #### 实验背景与目的 在本次实验中,学生通过实际操作加深了对Linux环境下进程创建与进程间通信的理解。实验的主要目标是熟悉Linux系统调用,并学习如何在Linux中创建进程。...
进程创建、控制与撤消 一、目的: 通过进程的创建和控制的设计来达到如下目的: 1、加深对进程概念的理解,明确进程和程序的区别; 2、进一步认识并发执行的概念,区别顺序执行和并发执行; 3、分析进程争用临界资源...
### 进程创建与控制详解 #### 进程的基本概念 在计算机系统中,进程是程序的一次执行过程,它是操作系统进行资源分配和调度的基本单位。一个程序在执行时会创建一个或多个进程,每个进程都有自己的地址空间、状态...
进程创建与撤销 进程创建与撤销是操作系统中最基本的概念之一。了解进程创建和撤销的机理,对理解操作系统的基本原理至关重要。在本实验中,我们将通过模拟实现进程创建和撤销功能,来深入了解进程的概念和管理机理...
在IT领域,监控进程创建是一项重要的系统管理任务,它能够帮助我们了解系统中正在运行的程序,检测潜在的恶意活动,以及优化系统性能。本文将深入探讨如何通过源码实现进程监控,特别是针对Windows XP和Win7 32位...
本实验"进程创建模拟试验"旨在帮助学生深入理解进程创建的理论基础,掌握具体的创建方法,并熟悉与进程相关的数据结构。以下是关于这些知识点的详细说明: 1. **进程创建相关理论**: - **进程的概念**:进程是...
实验二“进程创建及进程间通信1”主要涵盖了操作系统中的核心概念,如进程创建、进程同步、进程互斥以及管道通信。在这个实验中,你将深入理解Linux操作系统中进程的生命周期,以及如何通过编程实现进程间的通信。 ...
在计算机科学中,"父进程创建子进程"是操作系统中多任务并发执行的一个核心概念。在C++编程语言中,我们可以利用操作系统提供的API(应用程序接口)来实现这一功能。本实验旨在让学生理解进程的创建机制,以及如何在...
APIHOOK拦截指定进程创建新进程。@按键精灵。
### 操作系统 进程创建实验报告 #### 实验背景 在现代计算机系统中,操作系统作为用户和硬件之间的桥梁,负责管理和协调计算机的各种资源。进程是操作系统中的一个重要概念,它是程序的一次执行过程,也是系统进行...
在Windows操作系统中,监视进程创建是一项重要的系统级编程任务,常用于系统安全、性能分析或调试目的。本主题将深入探讨如何通过API拦截技术,特别是hook `CreateProcess`函数,来实现在Windows XP系统上监控进程...
监视新进程创建是系统监控中的一个重要功能,它可以帮助开发者、安全研究人员或系统管理员了解系统上运行的进程动态,及时发现异常行为。在这个场景下,我们有一个名为“WMIProcessCreateMonitor”的压缩包文件,...
在Unix/Linux系统中,最常用的进程创建函数是`fork()`. 它会创建一个与父进程几乎完全相同的子进程,包括其内存映像、打开的文件等。实验中提到的“进程树”是指由一个父进程创建多个子进程,这些子进程又可以创建...
操作系统课程设计中的“进程创建系统”是一个重要的学习实践环节,旨在深入理解操作系统的内核机制,特别是进程管理方面的概念和技术。在这个项目中,学生需要模拟一个简化版的操作系统,实现进程的创建、调度和销毁...
实验1 进程创建模拟实验 实验内容:分析进程创建函数createpc程序模块 输入实验提供的代码后,可以输入createpc命令创建进程,输入showdetail显示每个进程及其子进程的信息,测试命令解释如下: createpc 创建进程...
以普通程序员身份认识高级语言VC++/Java/C#的进程创建与撤销工具。
### 进程创建、终止、阻塞、调度、唤醒原语 #### 一、进程创建原语 在操作系统中,进程的创建是一项重要的任务。进程创建原语是用于创建一个新进程的操作。新进程通常继承父进程的部分属性,并获得一个唯一的进程...
漫谈兼容内核之十:Windows的进程创建和映像装入》这篇文档中,作者毛德操详细探讨了Windows操作系统中进程创建与映像装载的过程。这篇文章主要基于《Microsoft Windows Internals 4e》一书的第六章内容,并结合作者...
利用fork()函数编写一个程序,要求父进程创建两个子进程,父进程、子进程并发执行,输出并发执行的消息,如: 父进程正在执行….. 子进程1正在执行….. 子进程2正在执行….. 一段时间后,父进程杀死子进程,然后...