fork函数和子进程
- 格式:doc
- 大小:96.50 KB
- 文档页数:3
fork 子进程不继承父进程打开的文件描述符1.引言1.1 概述概述在操作系统中,进程是指正在执行的程序的实例。
当一个进程创建子进程时,子进程会继承父进程的一些属性和资源,以便能够继续执行相同的操作。
然而,在某些情况下,子进程并不会继承父进程的所有属性和资源,其中一个重要的例子就是父进程打开的文件描述符。
文件描述符是用来标识一个文件或者文件流的抽象概念。
当一个进程需要对文件进行操作时,它必须首先打开该文件,并获得一个文件描述符。
父进程通过打开文件来获取文件描述符,而子进程在创建时则不会继承这些父进程已打开的文件描述符。
这种设计有其合理性和必要性。
首先,父进程打开的文件描述符可能会包含一些敏感信息,比如数据库连接信息、加密密钥等。
如果这些信息被子进程继承,有可能会导致安全风险。
其次,大多数情况下,子进程并不需要继承父进程的文件描述符。
子进程是作为独立的实体运行的,通常具有自己独立的文件操作需求。
虽然子进程不继承父进程的文件描述符,但是子进程可以通过其他方式来获取和打开文件。
举例来说,子进程可以使用文件路径来打开需要的文件,或者通过网络传输文件描述符等方式来获取父进程已打开的文件描述符。
因此,对于子进程来说,并不存在无法获取文件描述符的问题。
总之,子进程不继承父进程打开的文件描述符是有意而为的设计。
这一设计使得父子进程之间的资源隔离得以实现,同时也增强了系统的安全性。
在实际应用中,开发人员需要考虑到这一点,并正确处理子进程的文件操作需求。
1.2文章结构文章结构部分的内容可以描述文章的整体框架和组织结构,包括各个章节的主题和内容概要。
可以按照以下方式编写文章结构部分的内容。
文章结构部分:本文主要围绕着fork子进程不继承父进程打开的文件描述符展开讨论,旨在探讨子进程不继承父进程文件描述符的原因以及对应的影响和应用。
在引言部分,我们将通过概述对fork子进程和文件描述符的概念进行简要介绍,并给出本文的目的。
进程的创建实验报告进程的创建实验报告引言:在计算机科学领域中,进程是一个非常重要的概念。
进程是计算机程序的执行实例,它具有独立的内存空间和执行环境。
进程的创建是操作系统中一个关键的操作,本实验旨在通过编写一个简单的程序来演示进程的创建过程。
实验目的:通过实验,我们的目标是深入理解进程的创建过程,并了解操作系统是如何管理进程的。
实验步骤:1. 引入必要的头文件:在开始编写代码之前,我们需要引入一些必要的头文件。
这些头文件包括<sys/types.h>、<sys/wait.h>和<unistd.h>。
这些头文件提供了创建进程所需的函数和数据类型。
2. 创建一个子进程:在主程序中,我们使用fork()函数来创建一个子进程。
fork()函数会在当前进程的基础上创建一个新的进程,这个新进程称为子进程。
子进程和父进程几乎完全相同,只有在返回值上有所区别。
如果fork()函数返回0,表示当前进程是子进程;如果返回一个正整数,表示当前进程是父进程。
3. 子进程的执行:在子进程中,我们可以编写任意的代码来执行特定的任务。
子进程可以使用exec()函数来执行其他程序,或者执行一系列的操作。
在本实验中,我们简单地输出一条信息,以展示子进程的执行过程。
4. 父进程的执行:在父进程中,我们可以编写代码来执行其他任务,或者等待子进程的结束。
在本实验中,我们使用wait()函数来等待子进程的结束。
wait()函数会暂停父进程的执行,直到子进程结束为止。
5. 编译和运行程序:在完成代码编写后,我们需要将程序编译成可执行文件,并运行它。
我们可以使用gcc编译器来编译程序,然后运行生成的可执行文件。
实验结果:在运行程序后,我们可以观察到以下结果:子进程开始执行。
父进程等待子进程结束。
子进程结束。
父进程继续执行。
结论:通过本实验,我们成功地演示了进程的创建过程。
我们了解了操作系统是如何管理进程,并且掌握了使用fork()函数来创建子进程的方法。
exec族函数详解及循环创建⼦进程 前⾔:之前也知道exec族函数,但没有完全掌握,昨天⼜重新学习了⼀遍,基本完全掌握了,还有⼀些⽗⼦进程和循环创建⼦进程的问题,还要介绍⼀下环境变量,今天分享⼀下。
⼀、环境变量 先介绍下环境的概念和特性,再举例⼦吧。
环境变量,是指在中⽤来指定操作系统运⾏环境的⼀些参数。
通常具备以下特征: ①字符串(本质) ②有统⼀的格式:名=值[:值] ③值⽤来描述进程环境信息。
存储形式:与命令⾏参数类似。
char *[]数组,数组名environ,内部存储字符串,NULL作为哨兵结尾。
使⽤形式:与命令⾏参数类似。
引⼊环境变量表:须声明环境变量。
extern char ** environ; 环境变量跟很多东西有关系,例如接下来的exec族函数,这也是为什么要先介绍下环境变量的原因,对理解exec族函数很有帮助;例如,Linux是什么样的系统?多⽤户多任务开源系统,每个⽤户的登录信息环境变量都会记录。
举例⼀下常⽤的环境变量:PATH 可执⾏⽂件的搜索路径。
ls命令也是⼀个程序,执⾏它不需要提供完整的路径名/bin/ls,然⽽通常我们执⾏当前⽬录下的程序a.out却需要提供完整的路径名./a.out,这是因为PATH环境变量的值⾥⾯包含了ls命令所在的⽬录/bin,却不包含a.out所在的⽬录。
PATH环境变量的值可以包含多个⽬录,⽤:号隔开。
在Shell中⽤echo命令可以查看这个环境变量的值: $ echo $PATHSHELL 当前Shell,它的值通常是/bin/bash。
TERM 当前终端类型,在图形界⾯终端下它的值通常是xterm,终端类型决定了⼀些程序的输出显⽰⽅式,⽐如图形界⾯终端可以显⽰汉字,⽽字符终端⼀般不⾏。
LANG 语⾔和locale,决定了字符编码以及时间、货币等信息的显⽰格式。
HOME 当前⽤户主⽬录的路径,很多程序需要在主⽬录下保存配置⽂件,使得每个⽤户在运⾏该程序时都有⾃⼰的⼀套配置 介绍跟环境变量相关的函数: char *getenv(const char *name); //获取环境变量 int setenv(const char *name, const char *value, int overwrite); //添加或改变环境变量 int unsetenv(const char *name); //删除 ⼆、fork函数及循环创建⼦进程 先说⼀个问题,学会fork并写程序时,可能都会遇到⼀个问题如下: ./a.out的输出跑到终端上了,想过为什么?接下来我会解释这个问题。
3. 进程控制上一页第30 章进程下一页3. 进程控制3.1. fork函数#include <sys/types.h>#include <unistd.h>pid_t fork(void);fork调用失败则返回-1,调用成功的返回值见下面的解释。
我们通过一个例子来理解fork是怎样创建新进程的。
例30.3. fork#include <sys/types.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>int main(void){pid_t pid;char *message;int n;pid = fork();if (pid < 0) {perror("fork failed");exit(1);}if (pid == 0) {message = "This is the child\n"; n = 6;} else {message = "This is the parent\n"; n = 3;}for(; n > 0; n--) {printf(message);sleep(1);}return 0;}$ ./a.outThis is the childThis is the parentThis is the childThis is the parentThis is the childThis is the parentThis is the child$ This is the childThis is the child这个程序的运行过程如下图所示。
图30.4. fork父进程初始化。
父进程调用fork,这是一个系统调用,因此进入内核。
内核根据父进程复制出一个子进程,父进程和子进程的PCB信息相同,用户态代码和数据也相同。
Shell脚本编写的高级技巧使用多进程提高并发处理能力Shell脚本是一种用于自动化任务和批处理的脚本语言。
随着计算机处理能力的提升和数据量的增加,对脚本的并发处理能力的要求也越来越高。
在本文中,我们将介绍一些Shell脚本编写的高级技巧,可以利用多进程来提高并发处理能力。
一、并发处理的概念在计算机领域,当多个任务能够同时进行时,就称为并发处理。
而在Shell脚本中,利用多进程技术可以实现并发处理,提高任务的执行效率。
二、使用fork()函数创建子进程在Linux环境下,通过在Shell脚本中使用fork()函数,可以创建子进程来实现并发处理。
fork()函数会复制当前进程,使得原有进程变为父进程,而复制出的子进程可以独立执行其他的任务。
示例代码:```shell#!/bin/bashfor i in {1..5}do# 创建子进程{# 子进程执行的任务echo "子进程 $i 正在执行任务..."sleep 5} &done# 等待所有子进程执行完毕waitecho "所有任务执行完毕。
"```在上述示例中,首先通过for循环创建了5个子进程,并且每个子进程都执行了一个简单的任务。
通过在任务代码的末尾加上`&`符号,可以使得该任务在后台执行,不会阻塞其他子进程的执行。
最后通过wait命令等待所有子进程执行完毕,并输出“所有任务执行完毕”。
三、控制并发处理的数量在实际应用中,我们可能需要控制并发处理的数量,以避免资源浪费和系统负荷过大。
可以通过设置信号量来控制同时执行的子进程数量。
示例代码:#!/bin/bash# 定义并发处理的数量MAX_PROCESSES=3SEMAPHORE=0for i in {1..5}do(# 子进程执行的任务echo "子进程 $i 正在执行任务..."sleep 5# 任务执行完毕时释放信号量((SEMAPHORE--))) &# 控制并发处理的数量((SEMAPHORE++))if [ $SEMAPHORE -ge $MAX_PROCESSES ]; then waitfi# 等待剩余子进程执行完毕waitecho "所有任务执行完毕。
进程控制之fork函数⼀个现有进程可以调⽤fork函数创建⼀个新进程。
#include <unistd.h>pid_t fork( void );返回值:⼦进程中返回0,⽗进程中返回⼦进程ID,出错返回-1由fork创建的新进程被称为⼦进程(child process)。
fork函数被调⽤⼀次,但返回两次。
两次返回的唯⼀区别是⼦进程的返回值是0,⽽⽗进程的返回值则是新⼦进程的进程ID。
将⼦进程ID返回给⽗进程的理由是:因为⼀个进程的⼦进程可以有多个,并且没有⼀个函数使⼀个进程可以获得其所有⼦进程的进程ID。
fork使⼦进程得到返回值0的理由是:⼀个进程只会有⼀个⽗进程,所以⼦进程总是可以调⽤getppid以获得其⽗进程的进程ID(进程ID 0总是由内核交换进程使⽤,所以⼀个⼦进程的进程ID不可能为0)。
⼦进程和⽗进程继续执⾏fork调⽤之后的指令。
⼦进程是⽗进程的副本。
例如,⼦进程获得⽗进程的数据空间、堆和栈的副本。
注意,这是⼦进程所拥有的副本。
⽗、⼦进程并不共享这些存储空间部分。
⽗、⼦进程共享正⽂段(text,代码段)。
由于在fork之后经常跟随着exec,所以现在的很多实现并不执⾏⼀个⽗进程数据段、栈和堆的完全复制。
作为替代,使⽤了写时复制(Copy-On-Write,COW)技术。
这些区域由⽗、⼦进程共享,⽽且内核将它们的访问权限改变为只读的。
如果⽗、⼦进程中的任⼀个试图修改这些区域,则内核只为修改区域的那块内存制作⼀个副本,通常是虚拟存储器系统中的⼀“页”。
Linux 2.4.22提供了另⼀种新进程创建函数——clone(2)系统调⽤。
这是⼀种fork的泛型,它允许调⽤者控制哪些部分由⽗、⼦进程共享。
程序清单8-1中的程序演⽰了fork函数,从中可以看到⼦进程对变量所作的改变并不影响⽗进程中该变量的值。
程序清单8-1 fork函数⽰例[root@localhost apue]# cat prog8-1.c#include "apue.h"int glob = 6; /* external variable in initialized data */char buf[] = "a write to stdout\n";intmain(void){int var; /* automatic variable on the stack */pid_t pid;var = 88;if(write(STDOUT_FILENO, buf, sizeof(buf) - 1) != sizeof(buf) -1)err_sys("write error");printf("before fork\n"); /* we don't flush stdout */if((pid = fork()) < 0){err_sys("fork error");}else if(pid == 0) /* child */{glob++; /* modify variables */var++;}else{sleep(2); /* parent */}printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);exit(0);}如果执⾏此程序则得到:[root@localhost apue]# ./prog8-1a write to stdoutbefore forkpid = 13367, glob = 7, var = 89⼦进程的变量值改变了pid = 13366, glob = 6, var = 88⽗进程的变量值没有改变[root@localhost apue]# ./prog8-1 > tmp.out[root@localhost apue]# cat tmp.outa write to stdoutbefore forkpid = 13369, glob = 7, var = 89before forkpid = 13368, glob = 6, var = 88⼀般来说,在fork之后是⽗进程先执⾏还是⼦进程先执⾏是不确定的。
对fork函数的理解前言:对于刚刚接触Unix/Linux操作系统,在Linux下编写多进程的人来说,fork 是最难理解的概念之一:它执行一次却返回两个值。
因此,本文着重从以下几个方面来使初学者加深对fork函数的理解和应用:fork函数的机制与特性、fork 函数的两次返回和父子进程的执行顺序介绍、关键字:fork函数、返回值、父进程、子进程正文:一、fork函数的机制与特性1 #include<stdio.h>2 #include<unistd.h>3 #include<stdlib.h>45 int main(void)6 {7 pid_t pid;8 if ((pid = fork()) == 00) {9 getchar();10 exit(0);11 }12 getchar ();13}14父进程成功的调用fork(8行)后将会产生一个子进程。
此时会有两个问题:1、子进程的代码从哪里来?2、子进程首次被OS调用时,执行的第一条代码是哪条代码?子进程的代码是父进程代码的一个完全相同拷贝。
事实上不仅仅是text 段,子进程中的全部进程空间都是(包括:text/data/bss/heap/commandline/envir onment)父进程空间的一个完全拷贝。
下一个问题是谁为子进程分配了内存空间?谁复制了父进程空间的内容到子空间?fork当仁不让。
事实上,fork 实现的源代码,由四部分组成:首先,为子进程分配内存空间;然后,将父进程空间的全部内容复制到分配给子进程的内存空间;接着在内核数据结构中创建并正确初始化子进程的PCB (包括两个重要信息:子进程pid,PC 的值=善后代码的第一条指令地址);最后是一段善后代码。
由于子进程的PCB已经产生,因此子进程可以被OS调度子进程首次被OS调度时,执行的第一条代码在fork 内部,不过从引用程序的角度来看,子进程首次被OS调度时,执行的第一条代码是从fork返回。
首先看下fork的基本知识:函数原型:pid_t fork( void);返回值:若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1一个现有进程可以调用fork函数创建一个新进程。
由fork创建的新进程被称为子进程(child process)。
fork函数被调用一次但返回两次。
两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。
注意要点:1、子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。
此处先简要介绍下COW(Copy-on-write)机制,大致原理如下:在复制一个对象的时候并不是真正的把原先的对象复制到内存的另外一个位置上,而是在新对象的内存映射表中设置一个指针,指向源对象的位置,并把那块内存的Copy-On-Write位设置为1.这样,在对新的对象执行读操作的时候,内存数据不发生任何变动,直接执行读操作;而在对新的对象执行写操作时,将真正的对象复制到新的内存地址中,并修改新对象的内存映射表指向这个新的位置,并在新的内存位置上执行写操作。
linux内核下fork使用COW机制工作原理:进程0(父进程)创建进程1(子进程)后,进程0和进程1同时使用着共享代码区内相同的代码和数据内存页面, 只是执行代码不在一处,因此他们也同时使用着相同的用户堆栈区。
在为进程1(子进程)复制其父进程(进程0)的页目录和页表项时,进程0的640KB页表项的属性没有改动过(仍然可读写),但是进程1的640KB对应的页表项却被设置成只读。
因此当进程1(子进程)开始执行时,对用户堆栈的入栈操作将导致页面写保护异常,从而使得内核的内存管理程序为进程1在主内存区中分配一内存页面,并把进程0中的页面内容复制到新的页面上。
从此时开始,进程1开始有自己独立的内存页面,由于此时的内存页面在主内存区,因此进程1中继续创建新的子进程时也可以采用COW技术。
内核调度进程运行时次序是随机的,进程0创建进程1后,可能先于进程1修改共享区,进程0是可读写的,在未分开前,进程1是只读的,由于两个进程共享内存空间,为了不出现冲突问题,就必须要求进程0在进程1执行堆栈操作(进程1的堆栈操作会导致页面保护异常,从而使得进程1在主内存区得到新的用户页面区,此时进程1和进程0才算是真正独立,如前面所述)之前禁止使用用户堆栈区。
Fork函数
函数pid_t fork(void)
正确返回:在父进程中返回子进程的进程号,在子进程中返回0
错误返回:-1
子进程是父进程的一个拷贝。
即,子进程从父进程得到了数据段和堆栈段的拷贝,这些需要分配新的内存;而对于只读的代码段,通常使用共享内存的方式访问。
fork返回后,子进程和父进程都从调用fork函数的下一条语句开始执行。
父进程与子进程的不同之处在于:fork的返回值不同——父进程中的返回值为子进程的进程号,而子进程为0。
以下是fork的两个示例程序:
//fork.c
#include <stdio.h>
#include <unistd.h>
void main ()
{
int pid;
//printf("Process [%d] begin",getpid()); //print twice
printf("Process [%d] begin\n",getpid()); //print once
//由于fork时pc等值的拷贝,子进程只会从fork处开始执行
pid = fork();
if (pid < 0)
printf("error in fork!");
else if (pid == 0)
printf("I'm child process, my pid is %d\n", getpid());
else
printf("I'm parent process, my pid is %d\n", getpid());
printf("Process [%d] end\n",getpid());
return;
}
输出结果:
使用printf("Process [%d] begin\n",getpid())时
Process [11155] begin
I'm parent process, my pid is 11155
Process [11155] end
I'm child process, my pid is 11156
Process [11156] end
使用printf("Process [%d] begin",getpid())时
Process [11054] beginI'm parent process, my pid is 11054
Process [11054] end
Process [11054] beginI'm child process, my pid is 11055
Process [11055] end
不同的输出结果是因为Printf的缓冲机制。
printf某些内容时,操作系统仅仅是把该内容放到stdout的缓冲队列里,并没有实际写到屏幕上。
但是,只要看到有"\n"则会立即刷新stdout,因此就马上能够打印了。
运行了printf("string") 后,string 仅仅被放到了缓冲里,再运行到fork时,缓冲里的string 被子进程继承了,因此在子进程度stdout缓冲里面就也有了string。
而运行printf("string\n")后,string 被立即打印到了屏幕上,之后fork到的子进程里的stdout缓冲里不会有string 内容。
实际上,fork语句之前的指令在子进程中不会再执行。
//son_2_father.c
#include <stdio.h>
#include <unistd.h>
int main()
{
int i;
printf("Process\t[%d] begin\n",getpid());
for( i = 0; i < 3; i++)
{
int pid = fork();
if(pid == 0)
printf("son\t[%d] create [%d]\n", getpid(), pid);
else
printf("father\t[%d] create [%d]\n", getpid(), pid);
}
printf("Process\t[%d] finish\n",getpid());
return 0;
}
输出结果如下:
Process [10026] begin
father [10026] create [10027]
son [10027] create [0]
father [10026] create [10028]
father [10027] create [10029]
father [10026] create [10030]
Process [10026] finish
father [10027] create [10031]
Process [10027] finish
son [10028] create [0]
father [10028] create [10032]
Process [10028] finish
son [10032] create [0]
Process [10032] finish
son [10030] create [0]
Process [10030] finish
son [10031] create [0]
son [10029] create [0]
Process [10031] finish
father [10029] create [10033]
Process [10029] finish
son [10033] create [0]
Process [10033] finish
我们可以将此进程的执行过程表示为上图,子进程fork返回0,但是fork之后便成为了父进程,再次fork就可以得到子进程。
最后,需要注意,派生子进程的进程,即父进程,其pid不变,fork之后父子进程除非采用了同步手段,否则不能确定谁先运行,也不能确定谁先结束。
参考文献:
/guichen83/article/details/4160697
/blog/static/18056918120113134516506/。