⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 进程的创建.htm

📁 windows网络编程技术文章
💻 HTM
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0046)http://www.chinalinuxpub.com/doc/pro/fork.html -->
<HTML><HEAD><TITLE>进程的创建</TITLE>
<META http-equiv=Content-Type content="text/html; charset=gb2312">
<META content="linux, programming, c, c++, fork" name=keywords>
<META content="MSHTML 6.00.2800.1400" name=GENERATOR></HEAD>
<BODY text=#000000 bgColor=#ffffff>
<DIV align=center>
<CENTER>
<TABLE height=213 width=750 border=0>
  <TBODY>
  <TR>
    <TD width=689 height=213>
      <P align=center><FONT color=#ff9900 size=6>进程的创建</FONT></P>
      <P align=right><FONT color=#ff9900>作者:<A 
      href="mailto:rezaie@softhome.net">Wilbur Lang</A></FONT></P>
      <P><FONT color=#0066ff>在 UNIX 系统中,用户创建一个新进程的唯一方法就是调用系统调用 <B>fork</B>。调 用 
      <B>fork</B> 的进程称为<B>父进程</B>,而新创建的进程叫做<B>子进程</B>。系统 调用的语法格式:</FONT> <PRE><FONT color=#0066ff>
	pid = fork();
</FONT></PRE>
      <P><FONT color=#0066ff>在从系统调用 <B>fork</B> 中返回时,两个进程除了返回值 <B>pid</B> 不同外,具有 
      完全一样的用户级上下文。在子进程中,<B>pid</B> 的值为零。在系统启动时由核心内 部地创建的进程0是唯一不通过系统调用 
      <B>fork</B> 而创建的进程。</FONT> 
      <P><FONT color=#0066ff>核心为系统调用 <B>fork</B> 完成下列操作:</FONT> 
      <OL>
        <LI><FONT color=#0066ff>为新进程在进程表中分配一个空项。</FONT> 
        <LI><FONT color=#0066ff>为子进程赋一个唯一的进程标识号 (<B>PID</B>)。</FONT> 
        <LI><FONT color=#0066ff>做一个父进程上下文的逻辑副本。由于进程的某些部分,如正文区,可能被几个 
        进程所共享,所以核心有时只要增加某个区的引用数即可,而不是真的将该区拷贝到一个 新的内存物理区。</FONT> 
        <LI><FONT color=#0066ff>增加与该进程相关联的文件表和索引节点表的引用数。</FONT> 
        <LI><FONT color=#0066ff>对父进程返回子进程的进程号,对子进程返回零。</FONT> </LI></OL><FONT 
      color=#0066ff>理解系统调用 <B>fork</B> 的实现是十分重要的,因为子进程就象从天而降一样地开始 它的执行序列。</FONT> 

      <P><FONT color=#0066ff>下面是系统调用 <B>fork</B> 的算法。核心首先确信有足够的资源来成功完成 
      <B>fork</B>。 如果资源不满足要求,则系统调用 <B>fork</B> 失败。如果资源满足要求,核心在进程 
      表中找一个空项,并开始构造子进程的上下文。</FONT> <PRE><FONT color=#0066ff>
<B>算法</B>:fork
输入:无
输出:对父进程是子进程的 PID
	  对子进程是0
{
	检查可用的核心资源
	取一个空闲的进程表项和唯一的 PID 号
	检查用户没有过多的运行进程
	将子进程的状态设置为“创建”状态
	将父进程的进程表中的数据拷贝到子进程表中
	当前目录的索引节点和改变的根目录(如果可以)的引用数加1
	文件表中的打开文件的引用数加1
	在内存中作父进程上下文的拷贝
	在子进程的系统级上下文中压入虚设系统级上下文层
		/* 虚设上下文层中含有使子进程能
		 * 识别自己的数据,并使子进程被调度时
		 * 从这里开始运行
		 */
	<B>if</B> (正在执行的进程是父进程) {
		将子进程的状态设置为“就绪”状态
		return (子进程的 PID)			// 从系统到用户
	}
	<B>else</B> {
		初始化计时区
		return 0;
	}
}
</FONT></PRE><FONT color=#0066ff>我们来看看下面的例子。该程序说明的是经过系统调用 <B>fork</B> 
      之后,对文件的 共享存取。用户调用该程序时应有两个参数,一个是已经有的文件名,另外一个是要 
      创建的新文件名。该进程打开已有的文件,创建一个新文件,然后,假定没有遇见过 错误,它调用 <B>fork</B> 
      来创建一个子进程。子进程可以通过使用相同的文件描述 符而继承地存取父进程的文件(即父进程已经打开和创建的文件)。</FONT> 
      <P><FONT color=#0066ff>当然,父进程和子进程要分别独立地调用 <B>rdwrt</B> 函数,并执行一个循环,即从 
      源文件中读一个字节,然后写一个字节到目标文件中区。当系统调用 <B>read</B> 遇见 文件尾时,函数 <B>rdwrt</B> 
      立即返回。</FONT> <PRE><FONT color=#0066ff>
#include &lt;fcntl.h&gt;

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, &amp;c, 1) != 1) {
			return ;
		}
		write(fdwt, &amp;c, 1);
	}
}
</FONT></PRE><FONT color=#0066ff>在这个例子中,两个进程的文件描述符都指向相同的文件表项。这两个进程永远 
      不会读或写到相同的文件偏移量,因为核心在每次 <B>read</B> 和 <B>write</B> 调用 
      之后,都要增加文件的偏移量。尽管两个进程似乎是将源文件拷贝了两次,但因为 他们分担了工作任务,因此,目标文件的内容依赖于核心调度两个进程的次序。如果 
      核心这样调度两个进程:使他们交替地执行他们的系统调用,或甚至使他们交替地 执行每对 read 和 write 
      调用,则目标文件的内容和源文件的内容完全一致。但考虑 这样的情况:两个进程正要读源文件中的两个连续的字符 "ab"。假定父进程读了字 符 
      "a",这时,核心在父进程写之前,做了上下文切换来执行子进程。如果子进程 读到字符 
      "b",并在父进程被调度前,将它写到目标文件,那么目标文件将不再含有 字符串 "ab",而是含有 
      "ba"了。核心并不保证进程执行的相对速率。</FONT> 
      <P><FONT color=#0066ff>再来看看另外一个例子:</FONT> <PRE><FONT color=#0066ff>
#include &lt;string.h&gt;

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 &lt; 15; i++) {
		write(1, string, strlen(string));
		read(0, buf, sizeof(buf));
	}
}
</FONT></PRE><FONT color=#0066ff>子进程从父进程继承了文件描述符0和1(标准输入和标准输出)。两次执行系统调用 
      <B>pipe</B> 分别在数组 to_par 和 to_chil 中分配了两个文件描述符。然后该进程 执行系统调用 
      <B>fork</B>,并复制进程上下文:象前一个例子一样,每个进程存取 
      自己的私有数据。父进程关闭他的标准输出文件(文件描述符1),并复制(dup)从管道 线 to_chil 
      返回的写文件描述符。因为在父进程文件描述符表中的第一个空槽是刚刚 由关闭腾出来的,所以核心将管道线写文件描述符复制到了文件描述符表中的第一 
      项中,这样,标准输出文件描述符变成了管道线 <B>to_chil</B> 的写文件描述符。 父进程以类似的操作将标准输入文件描述符替换为管道线 
      <B>to_par</B> 的读文件 描述符。与此类似,子进程关闭他的标准输入文件(文件描述符0),然后复制 (dup) 管道 线 
      <B>to_chil</B> 的读文件描述符。由于文件描述符表的第一个空项是原先的标准 输入项,所以子进程的标准输入变成了管道线 
      <B>to_chil</B> 的读文件描述符。 子进程做一组类似的操作使他的标准输出变成管道线 <B>to_par</B> 的写文件描述 
      符。然后两个进程关闭从 <B>pipe</B> 返回的文件描述符。上述操作的结果是:当 父进程向标准输出写东西的时候,他实际上是写向 
      <B>to_chil</B>--向子进程发送 数据,而子进程则从他的标准输入读管道线。当子进程向他的标准输出写的时候, 他实际上是写入 
      <B>to_par</B>--向父进程发送数据,而父进程则从他的标准输入 接收来自管道线的数据。两个进程通过两条管道线交换消息。</FONT> 
      <P><FONT color=#0066ff>无论两个进程执行的顺序如何,这个程序执行的结果是不变的。他们可能去执行睡眠 
      和唤醒来等待对方。父进程在15次循环后退出。然后子进程因管道线没有写进程而读 到“文件尾”标志,并退出。</FONT> 
      <HR>

      <P>
      <BLOCKQUOTE><I><FONT color=#0066ff>如果您对本文有任何意见和建议,请给 <A 
        href="mailto:rezaie@softhome.net">Wilbur Lang</A> 
        写信。如果您自己有什么文章要发表,也请写信。任何反馈意见都非常欢迎。</FONT></I></BLOCKQUOTE>
      <P> </P></TD></TR></TBODY></TABLE></CENTER></DIV></BODY></HTML>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -