修订历史
版本 日期 原因
V1.00 2016/12/04 创建文档
V1.10 2017/02/05 修改文档
目录
1. 适用范围 1
2. 原理介绍 1
2.1 Linux下fork函数功能浅析 1
2.2 posix_spawn原型介绍 1
3. 技术实现 2
3.1 文件描述符 2
3.2 全局变量 3
3.3 守护进程 4
4. posix_spawn源码实现框图 4
5. 参考资料 6
附录一:设置文件描述符不可复制 6
1. 适用范围
SylixOS是一款为嵌入式系统设计的硬实时系统。为了保证系统的实时性,系统创建子进程时不做页表切换(页表切换很耗时间,不利于实时性的体现),即父子进程共享同一个页表,而对于Linux下fork函数创建的父子进程是需要进行页表复制和切换的。为了在SylixOS下实现Linux的fork函数功能,本文总结了如何使用posix标准的posix_spawn函数替换fork函数。
2. 原理介绍
SylixOS的posix_spawn函数是基于posix标准的,是fork函数和exec函数的功能合二为一,它只是创建一个新进程并执行指定程序。
2.1 Linux下fork函数功能浅析
由fork函数创建的子进程获得父进程的堆栈、数据段和执行文本段的拷贝,实质上我们可以这样理解:即子进程完全复制父进程的页表(对于有写时拷贝机制的fork函数我们也可以这么理解)。
父子进程均可修改各自的栈数据、以及堆段中的变量,而不影响另一进程。程序可以通过fork函数的返回值来区别父子进程,在父进程中fork函数将返回子进程的进程ID。鉴于父进程可能需要创建,进而追踪多个子进程(通过wait函数或类似的方法)。fork函数在子进程中返回0,子进程可以通过getpid函数以获得自身ID,可以使用getppid函数获得父进程的ID。
父子进程之间文件共享,子进程获得父进程所有文件描述符号的副本(副本的创建类似于dup函数的功能),且父子进程中对应的描述符均指向相同的文件句柄(即公用一个文件结构和文件节点),共享同一个文件句柄,这样公用的文件偏移量保证了父子进程写同一个文件时不会覆盖彼此写入的内容(但是父子进程的输出会随意混杂在一起,可用进程间同步来规避)。
总的来说fork函数创建子进程,复制父进程的信息可分为主要三个方面:
1、复制父进程的文件描述符表;
2、复制父进程数据段;
3、复制父进程的环境变量。
对于SylixOS系统来说不提供fork函数,也没有提供与fork函数相同功能的函数。而SylixOS提供一个posix标准的posix_spawn函数,我们可以根据fork函数功能进行使用posix_spawn函数替换fork函数。
2.2 SylixOS的posix_spawn函数原型介绍
#include <spawn.h>
int posix_spawn(pid_t *pid,
const char *path,
constposix_spawn_file_actions_t *file_actions,
constposix_spawnattr_t *attrp,
char *const argv[],
char *const envp[]);
函数成功返回 0,失败返回-1 并设置错误码。函数参数如表2 1函数参数对比:
表2 1函数参数
项目 posix_spawn
进程号pid 新的进程号
文件操作集file_actions 新进程启动时需要处理的文件操作集, 为 0 表示不进行任何文件操作
新进程初始化属性attrp 新进程初始化属性,为 NULL 表示不设置
命令行参数字符串数组argv 命令行参数组成的字符串数组,数组以可执行文件名开始以 NULL结束,为 NULL 表示不使用命令行参数多64个
进程环境变量字符串数组envp 需要预先设置的进程环境变量字符串数组,数组以 NULL 结束,为NULL 表示不需要设置环境变量不超过64个
可执行文件路径path path 是可执行文件路径
根据posix_spawn函数原型和功能可知,针对fork函数创建子进程时复制的主要三个方面有如下的方法替代:
1. 复制文件描述符表:posix_spawn函数自身在创建子进程时也复制父进程所有的文件描述符,父子进程的文件描述符表指向同一个文件结构,这个和fork函数一样;
2. 复制数据段:posix_spawn函数可以通过第五个参数指针数组argv[ ]进行数据传递,对于对父进程的代码段的复制只能通过再次编写来解决;
3. 复制坏境变量:posix_spawn函数可以使用第六个参数数指针数组envp[ ]来传递父进程的环境参数,并且可以认为设置envp[ ]参数来设置子进程的环境参数。
注:因为在SylixOS父子进程因为是共享同一张页表的,所以对于SylixOS操作系统的父子进程天生就有共享内存(即共享同一张页表地址),不过需要注意如果父子进程通过使用同一张页表同一个地址来传递数据很危险,需谨慎使用(比如子进程到共享页表地址进行读写操作,不小心踩到父进程的堆栈空间会造成不可估量的危险)。
3. 技术实现
3.1 文件描述符
例程描述:
父进程创建子进程前已经打开一个文件,父进程将文件描述符值传递给子进程,父、子进程共同对同一文件进行写操作。
例程总结:
父进程在创建子进程时,子进程复制父进程的文件描述符表,所以只需要通过传参的方式把文件描述符告知给子进程,子进程就能够使用这个文件描述符,深入了解后发现使用posix_spawn函数创建子进程,父子进程也是公用一个文件结构和文件节点。posix_spawn函数也支持执行时关闭(close-on-exec) 标志,即文件描述符设置FD_CLOEXEC标志,在创建子进程时子进程不能复制该文件描述符 。
注:close-on-exec标志可通过fnctl函数对文件描述符设置,具体实现可参考附录一。
3.2 全局变量
例程描述:
父进程创建子进程时将全局变量值传递给子进程,随后父进程对全局变量进行加1操作,子进程对接收到的全局变量值也进行加1操作。
例程总结:
父进程可通过传递参数的方式将子进程需要的数据传递给子进程,这样父子进程都有自己的数据,且互不干扰。
3.3 守护进程
例程描述:
父进程创建子进程,子进程执行daemon函数将自己设为守护进程,父进程手动退出。
例程总结:
SylixOS提供了设置守护进程daemon函数,该函数内部包含了部分fork函数创建守护进程的相关操作。
daemon原型介绍
SylixOS提供API接口daemon函数,可以设置当前进程为守护进程。
#include <unistd.h>
int daemon(int nochdir, int noclose);
函数 daemon 原型分析:
此函数成功返回 0,失败返回-1 并设置错误码;
参数 nochdir 表示是否切换进程当前工作目录到根目录“ /”。 0 表示切换, 其他表
示不切换;
参数 noclose 表示是否重定向标准输入、标准输出、标准错误输出到“ /dev/null”
文件。 0 表示重定向, 其他表示不重定向
4. posix_spawn函数源码实现
posix_spawn函数中的流程实现
对于posix_spawn函数实现的核心步骤__processStart的实现流程:
__processStart函数的实现流程
5. 参考资料
《 SylixOS 应用开发手册》
附录一置文件描述符不可复制
1.在open函数打开时设置标志位:
iFd = open("./filetest", O_RDWR | O_CREAT | O_CLOEXEC , FILE_MODE);
加上这个标志:O_CLOEXEC
2.使用fcntl函数设置文件描述符不可复制:
fcntl(iFd, F_SETFD, 0);
注:0表示不设置标志位,1表示设置标志位