【python】os.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出来的子进程会复制一份父进程的内存页表作为自己的内存页表,因为页表项中虚拟内存页指向的实际物理内存页还是原来的,所以进程间内存也是共享的。只有当其中某个进程触发写操作时,操作系统会给这个触发写的进程申请复制一个相关内存页的副本,修改内存页中所指向的实际物理内存页后(此处保证了内存的物理隔离)并在页表中替换原页表项。

这里还有一个容易混淆的点就是,对于不同的进程来说,虚拟内存地址是可以重复的,因为虚拟内存地址的主要作用就是:页目录索引定位页表物理地址 -> 页表索引定位页目录项物理地址 -> 页偏移量定位页目录项中物理页的页内偏移量。在页目录项映射实际物理页这一步,不同进程中有各自独立的映射。虚拟内存更像是充当一个标识符的作用。

虚拟内存地址寻址实际物理内存地址的过程参考:虚拟内存地址寻址实际物理内存地址。页目录属于操作系统级别,页表属于进程级别,可以把页目录理解成为一本书,书中的每一页是一个页表,属于一个进程。页表中每一行记录了一个实际的物理内存页号。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值