灯火互联
管理员
管理员
  • 注册日期2011-07-27
  • 发帖数41778
  • QQ
  • 火币41290枚
  • 粉丝1086
  • 关注100
  • 终身成就奖
  • 最爱沙发
  • 忠实会员
  • 灌水天才奖
  • 贴图大师奖
  • 原创先锋奖
  • 特殊贡献奖
  • 宣传大使奖
  • 优秀斑竹奖
  • 社区明星
阅读:2795回复:0

linux/unix进程的创建

楼主#
更多 发布于:2013-02-18 13:57

作者:fengyv
linux/unix进程的创建
 
 
 
在 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{
 
                 初始化计时区
 
                 return0;
 
         }
 
}
 
我们来看看下面的例子。该程序说明的是经过系统调用 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次循环后退出。然后子进程因管道线没有写进程而读
 到“文件尾”标志,并退出。

喜欢0 评分0
游客

返回顶部