【python】os.fork进程创建
文章目录
- 基本使用
- ps.从fork理解虚拟内存管理与写时复制技术
基本使用
API:https://docs.python.org/zh-cn/3.9/library/os.html#os.fork
fork,叉子、分叉的意思。python的os模块中,fork用来从主进程中复制出来一个子进程副本,复制出来的子进程和主进程基本完全相同,除一些进程特有的标识外(如进程id、父进程id),其余的像环境变量、文件句柄、内部对象的虚拟内存地址等都一样,仅适用类unix系统平台(对于开发来说基本可以认为两个进程相同)。
在fork调用后,父进程和子进程会在同一个地方继续执行下去。类似于:
如果fork调用成功,在父进程中,fork将返回新创建的子进程的进程ID;而在子进程中,fork将返回0,也就是一些资料中说的“一次调用,两次返回”,可以通过这个返回的值来区分子进程与父进程。如果调用失败,会抛出OSError异常。
注意这个返回的值跟os.getpid()返回的pid含义不同,os.getpid()返回的是当前进程的进程号,而fork调用返回的值更像是一个进程内部的标识变量,只不过这个变量的值在父进程中定义为子进程的进程号(便于控制子进程)。
可以通过下面代码验证:
import os
if __name__ == '__main__':
print(f'fork前 进程号:{os.getpid()} 父进程号:{os.getppid()}')
try:
pid = os.fork()
except OSError as e:
print(f'fork error, msg:{e}')
else:
if pid == 0: # 子进程执行
print(f'fork后 子进程执行 进程号:{os.getpid()} 父进程号:{os.getppid()}')
elif pid > 0: # 父进程执行
print(f'fork后 父进程执行 进程号:{os.getpid()} 父进程号:{os.getppid()}')
else:
raise ValueError('pid is not 0 or an integer greater than 0')
ps.从fork理解虚拟内存管理与写时复制技术
从下面这个demo开始了解:
import os
if __name__ == '__main__':
data = list(range(10))
try:
pid = os.fork()
except OSError as e:
print(f'fork error, msg:{e}')
else:
if pid == 0:
print(f'子进程输出 data:{data} id:{id(data)}')
data.append(10)
print(f'子进程输出 data:{data} id:{id(data)}')
data = [1, 2, 3]
print(f'子进程输出 data:{data} id:{id(data)}')
elif pid > 0:
os.waitpid(pid, 0) # 等待子进程执行完毕
print(f'父进程输出 data:{data} id:{id(data)}')
else:
raise ValueError('pid is not 0 or an integer greater than 0')
上面代码的执行顺序:主进程创建data对象 -> 主进程fork一个子进程 -> 子进程执行完毕 -> 主进程执行。
开始的不理解在于,fork出来的子进程data指向的内存地址和父进程相同,但子进程对该对象修改后对父进程的data对象又没有影响?或者说,不同进程有各自的内存空间,为什么父子进程中data对象的内存地址相同?
整个问题可以把虚拟内存管理和写时复制技术结合来看:
在类unix系统中,fork系统调用一般采用了写时复制(Copy on Write)技术,fork出来的子进程会复制一份父进程的内存页表作为自己的内存页表,因为页表项中虚拟内存页指向的实际物理内存页还是原来的,所以进程间内存也是共享的。只有当其中某个进程触发写操作时,操作系统会给这个触发写的进程申请复制一个相关内存页的副本,修改内存页中所指向的实际物理内存页后(此处保证了内存的物理隔离)并在页表中替换原页表项。
这里还有一个容易混淆的点就是,对于不同的进程来说,虚拟内存地址是可以重复的,因为虚拟内存地址的主要作用就是:页目录索引定位页表物理地址 -> 页表索引定位页目录项物理地址 -> 页偏移量定位页目录项中物理页的页内偏移量。在页目录项映射实际物理页这一步,不同进程中有各自独立的映射。虚拟内存更像是充当一个标识符的作用。
虚拟内存地址寻址实际物理内存地址的过程参考:虚拟内存地址寻址实际物理内存地址。页目录属于操作系统级别,页表属于进程级别,可以把页目录理解成为一本书,书中的每一页是一个页表,属于一个进程。页表中每一行记录了一个实际的物理内存页号。