专业的集团网站建设哪家,网站 设计 深圳,一个网站建设需求的人员,大连装修公司哪家比较好文章目录进程创建进程等待程序替换进程终止进程创建
fork函数#xff1a; 操作系统提供的创建新进程的方法#xff0c;父进程通过调用 fork函数 创建一个子进程#xff0c;父子进程代码共享#xff0c;数据独有。 当调用 fork函数 时#xff0c;通过 写时拷贝技术 来拷贝…
文章目录进程创建进程等待程序替换进程终止进程创建
fork函数 操作系统提供的创建新进程的方法父进程通过调用 fork函数 创建一个子进程父子进程代码共享数据独有。 当调用 fork函数 时通过 写时拷贝技术 来拷贝父进程的信息。
写时拷贝技术copy on write 子进程通过复制父进程的 PCB使得父子进程指向同一块物理内存运行位置和代码也相同。但又因为进程的独立性所以当某一个进程数据发生改变的时候会重新给子进程开辟物理内存将数据拷贝过去。之所以这样使用是因为如果数据不修改的话还开辟空间拷贝数据会使效率降低这也就是数据独有的原因。
代码共享 通过页表来实现访问控制使代码段是只读的不可修改。 fork函数 的运用
#includeiostream
#includeunistd.h
#includestdlib.husing namespace std;int main()
{cout hello world getpid() endl;int id fork();if(id 0){cerr fork failed endl;}else if(id 0){cout I am child process, id getpid() endl;}else{cout I am parent process, id getpid() endl;}return 0;
}运行结果 父进程调用 fork函数 后对操作系统来说这时看起来有两个完全一样的 test程序 在运行并都从 fork() 系统调用中返回。区别在于子进程不会从 main()函数 开始执行因此 hello world 信息只输出了一次而是直接从 fork() 系统调用返回就好像是它自己调用了fork() 只是返回值和父进程不同罢了。
实际上上图的输出结果并不是唯一答案也有可能 子进程先于父进程执行完毕 。 vfork函数 创建一个子进程并且阻塞父进程直到子进程退出或者程序替换父进程才继续运行。
#include unistd.h
pid_t vfork(void);
返回值自进程中返回0父进程返回子进程id.出错返回1。vfork 创建子进程的效率比 fork 要高因为 vfork 所创建的子进程和父进程共用同一个虚拟地址空间。
但也因为这样进程之间就不具备独立性父子进程不能同时访问代码段和数据段所以当子进程运行的时候必须要阻塞父进程防止产生冲突。
虽然 vfork 效率高但是 fork 因为实现了写时拷贝技术效率提高了不少所以 vfork 已经很少使用了。 进程等待
之前讲过如果子进程退出时没有给父进程返回退出的信息父进程就会以为他并没有退出所以一直不释放他的资源使子进程进入僵死状态。
之前的解决方法是退出父进程但是那个不是一个合理的解决方法这里有更好的方法就是进程等待。
wait 阻塞等待任意一个进程退出获取退出子进程的 pid 并且释放子进程资源。
#includesys/types.h
#includesys/wait.h
pid_t wait(int*status);
返回值
成功返回被等待进程pid失败返回-1。
参数
输出型参数获取子进程退出状态不关心则可以设置为NULL阻塞为了完成某个功能发起调用如果不具备完成功能的条件则调用不返回一直等待。非阻塞为了完成某个功能发起调用如果不具备完成功能的条件则立即报错返回 wait函数 的运用
#includeiostream
#includeunistd.h
#includestdlib.h
#includesys/wait.husing namespace std;int main()
{cout hello world, My id getpid() endl;int id fork();if(id 0){cerr fork failed endl;}else if(id 0){cout I am child process, id getpid() endl;}else{int wc wait(NULL);cout I am parent process, id getpid() . My child id id . wc: wc endl;}return 0;
}运行结果 对比 fork函数 的运行实例可以看到本次运行 子进程 要先于 父进程 完成这是因为 父进程 调用了 wait() 延迟了自己的的执行阻塞直到 子进程 执行完毕wait() 才返回父进程。
但与 fork函数 的运行实例不同本实例的运行结果是唯一的
如果子进程先运行父进程调用 wait() 时子进程早已执行完毕父进程无需阻塞自己那么输出结果如上没什么可多说的。如果父进程先运行那么到调用 wait() 的时候必须等待子进程运行结束后才能返回接着输出父进程自己的信息。 waitpid 可以指定等待一个子进程的退出。
#includesys/wait.h
pid_ t waitpid(pid_t pid, int *status, int options);返回值
当正常返回的时候 waitpid 返回收集到的子进程的进程ID
如果设置了选项 WNOHANG ,而调用中 waitpid 发现没有已退出的子进程可收集,则返回0
如果调用中出错,则返回-1,这时 errno 会被设置成相应的值以指示错误所在参数
- pid
Pid-1,等待任一个子进程。与wait等效。
Pid0.等待其进程ID与pid相等的子进程。
- status:
WIFEXITED(status): 若为正常终止子进程返回的状态则为真。查看进程是否是正常退出
WEXITSTATUS(status): 若WIFEXITED非零提取子进程退出码。查看进程的退出码
- options:
WNOHANG: 若 pid 指定的子进程没有结束则 waitpid() 函数返回0不予以等待。若正常结束则返回该子进程的ID。程序替换
创建子进程必定是想让 子进程做与父进程不一样的事情 如果采用判断 pid 的方法来进行代码分流这样的程序会非常庞大所以还有更好的方法就是通过 exec()函数 来实现 程序替换 。
exec 是创建进程 API 的重要组成部分。可以让 子进程执行与父进程不同的程序 。
程序替换 exec() 加载另一个程序的代码和静态数据到内存中覆盖自己的代码段以及静态数据堆、栈及其他控件也会被重新初始化。PCB 不再调度原来的程序而调度这个新的程序。只是改变了映射关系所以原本的 PCB 和程序还在
#include unistd.h
int execl(const char *path, const char *arg, …);
int execlp(const char *file, const char *arg, …);
int execle(const char *path, const char *arg, …,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);乍一看这么多接口很不容易记其实是有规律的
l(list) : 表示参数采用列表v(vector) : 参数用数组p(path) : 有 p 自动搜索环境变量 PATHe(env) : 有 e 表示自己维护环境变量 exec()函数 的运用
#includeiostream
#includeunistd.h
#includestdlib.h
#includesys/wait.h
#includestring.husing namespace std;int main(int argc, char *argv[])
{cout hello world, My id getpid() endl;int id fork();if(id 0){cerr fork failed endl;exit(1);}else if(id 0){cout I am child process, id getpid() endl;char* s[3];s[0] strdup(wc);s[1] strdup(test);s[2] NULL;execvp(s[0], s);cout This shouldnt print out. endl;}else{int wt wait(NULL);cout I am parent process, id getpid() . My child id id . wt: wt endl;}return 0;}
运行结果
在本例中子进程调用 execvp() 来运行字符计数程序 wc 。将计数程序 wc 作为可执行文件 test 的执行参数输出该文件有多少行、多少单词、多少字节。exec() 从可执行程序 wc 中加载代码和静态数据以覆盖运来的代码段及静态数据并重新初始化堆、栈及其他内存空间然后操作系统执行该程序将参数通过 argv 传递给该进程。exec() 并不创建新进程而是直接将当前运行的程序test替换为不同的运行程序wc子程序执行 exec() 后几乎就像 test 从未运行过一样对 exec() 的成功调用永远不会返回。
fork() 和 exec() 的组合既简单又极其强大fork 使用 父进程 的各种资源迅速创建一个 子进程 在修改 子进程 的 资源/环境 而保证 父进程 能执行 原来的需求工作 再将 子进程 exec 为另一个程序从而 创建两个并行的功能不同的进程 。 shell 也可以通过 fork() 和 exec() 方便地实现很多有用的功能。比如上面的例子可以写成
prompt wc p3.c newfile.txt在上面的 shell 命令中wc 的输出结果被 重定向redirect 到文件 newfile.txt 中通过 newfile.txt 之前的大于号来指明重定向。shell 实现结果重定向的方式也很简单当完成子进程的创建后shell 在调用 exec() 之前先关闭了标准输出standard output 打开了文件 newfile.txt 。这样即将运行的程序 wc 的输出结果就被发送到该文件而不是打印在屏幕上。
用代码实现重定向
#includeiostream
#includeunistd.h
#includestdlib.h
#includesys/wait.h
#includestring.h
#includefcntl.husing namespace std;int main(int argc, char *argv[])
{int id fork();if(id 0){cerr fork failed endl;exit(1);}else if(id 0){close(STDOUT_FILENO);open(./test.output, O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);// now exec wc...char* s[3];s[0] strdup(wc);s[1] strdup(test);s[2] NULL;execvp(s[0], s);}else{int wt wait(NULL);}return 0;}上例中 重定向的工作原理 重定向是 基于对操作系统管理文件描述符方式的假设 。具体来说UNIX 系统从 0 开始寻找可以使用的文件描述符。在这个例子中STDOUT_FILENO标准输出文件描述符 将成为第一个可用的文件描述符因此在 open() 被调用时得到赋值。然后子进程向 标准输出文件描述符 的写入例如 wc 程序中 printf() 这样的函数都会被透明地转向新打开的文件而不是屏幕。
UNIX管道也是用类似的方式实现的但用的是 pipe() 系统调用。 进程终止
在 Linux 下有三种终止进程的方法。 return 只能在 main 函数中使用退出后刷新缓冲区 exit 库函数调用接口退出刷新缓冲区
#include unistd.h
void exit(int status);_exit 系统函数调用接口退出不刷新缓冲区
#include unistd.h
void _exit(int status);
参数status 定义了进程的终止状态父进程通过wait来获取该值