Linux | 您所在的位置:网站首页 › 经度纬度的符号表示 › Linux |
进程创建
fork
#include
pid_t fork(void);
操作系统做了什么?
调用fork之后,内核的工作: 分配新的内存块和内核数据结构给子进程将父进程部分数据结构内容拷贝至子进程添加子进程到系统进程列表当中fork返回,开始调度器调度进程 = 内核数据结构 + 代码和数据 创建子进程的过程就是:创建子进程的内核数据结构,即task_struct + mm_struct + paper_table等等,继承父进程的代码,同时拷贝数据。 fork之后,操作系统会create一个新的process,copy_process,copy_mem,把父进程的所有资源(包括代码段,数据段、堆栈数据等),以及寄存器的值继承给子进程。之后再以写时拷贝的形式保证进程的数据独立性。 do_fork int sys_fork(struct pt_regs *regs) { return do_fork(SIGCHLD, regs->sp, regs, 0, NULL, NULL); } asmlinkage int sparc_do_fork(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size) { unsigned long parent_tid_ptr, child_tid_ptr; unsigned long orig_i1 = regs->u_regs[UREG_I1]; long ret; parent_tid_ptr = regs->u_regs[UREG_I2]; child_tid_ptr = regs->u_regs[UREG_I4]; ret = do_fork(clone_flags, stack_start, regs, stack_size, (int __user *) parent_tid_ptr, (int __user *) child_tid_ptr); /* If we get an error and potentially restart the system * call, we're screwed because copy_thread() clobbered * the parent's %o1. So detect that case and restore it * here. */ if ((unsigned long)ret >= -ERESTART_RESTARTBLOCK) regs->u_regs[UREG_I1] = orig_i1; return ret; }fork返回之后,两个进程的PC指针都指向fork函数之后的代码,但实际上是父子进程共享了整个代码。子进程运行的时候是从pc指针中取值得到下一次运行的代码的地址。 当然,子进程完全可以通过goto等方法让执行流回到fork之前的地方。 一个有趣的现象 #include #include #include int main() { printf("我是父进程,我要来执行fork了\n"); pid_t id = fork(); again: printf("这是fork之后的代码,子进程跳转后才能访问到的\n"); if(id == 0) { printf("我是子进程 pid = %d, ppid = %d\n",getpid(), getppid()); for(int i = 0; i sleep(1); } break; } return 0; }
进程退出的场景:运行完毕,结果正确;运行完毕,结果不正确;运行没完毕,被终止。 echo $?命令可以查询最近一次进程执行完毕时对应进程的退出码,退出码是一个8位无符号数。 虽然status是int,但是操作系统会把int转换成uint,因为退出码的范围是0-255。所以exit(-1)时,echo $? 结果是255。 异常终止如收到信号。 进程等待 为什么要等待?如果子进程退出,父进程不回收就会变成僵尸进程,操作系统是无法杀死的,可能内存泄漏,除非父进程也结束了,子进程被操作系统领养。因此父进程应该等待子进程,回收子进程的资源,获取其退出信息,例如退出码。 如何进程等待? #include #include pid_t wait(int* status); pid_t waitpid(pid_t pid, int* status, int options);调用这两个函数,可以从task_struct中拿到进程的退出状态,退出状态是进程退出后仍留在数据结构中的。 wait wait的参数是输出型参数,等待任意一个子进程,并输出退出信息。 waitpid pid:-1表示任意进程,大于0的数表示要等待的进程的pid。status:该进程的退出状态。options:0表示阻塞等待、WNOHANG表示非阻塞等待。返回值:大于0表示等待成功,并且进程已退出;等于0表示等待成功,但是进程还没有退出。 进程阻塞调用scanf和cin的时候,没有输入就会一直等待,task_struct的R状态变为S状态,从运行队列转移到等待队列。 非阻塞等待等待的时候如果事件没就绪就直接返回,执行别的东西,待会再来检查。多次调用费阻塞等待接口的检测行为称为轮询检测。 等待的结果 退出码的组成status的低八位是进程的退出信号,高8位是进程的退出码。当进程被杀死了还会有一个core_dump标志位。 printf("退出信号 = %d, 退出码 = %d\n", (status & 0x7F), (status >> 8) & 0x7F);例如:当用kill杀死进程的时候,比如kill -9 进程, 进程的低八位就会收到9号信号。 如果一个进程收到信号,就说明进程出现异常,这时候退出码就没用了,因为不是正常退出,所以只关心收到的信号。 进程替换 什么是进程替换?之前创建进程后,父子进程共享代码。如果想让子进程执行其他逻辑呢? 进程替换不会创建新进程,因为进程替换只是将该进程的数据替换为指定的可执行程序。而进程PCB没有改变,所以不是新的进程,pid不变。进程替换后,如果替换成功则开始执行新程序,原替换函数之后的代码不会执行,因为进程替换是覆盖式的替换,替换成功后进程原来的代码就消失了。如果进程替换失败则会执行原替换函数后的代码。 原理将磁盘中的别的程序加载到内存中,通过写时拷贝,重新建立页表的映射,将原子进程中数据段和代码段的映射改变为到别的程序的映射,这样父子进程的代码和数据就完全分离了。 进程替换的接口 #include 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[]); execl path:用来替换的程序路径:/usr/bin/ls,也可以是相对路径。要执行一个程序,系统需要知道如何执行这个程序,就要带参数:ls -a -larg:表示可变参数,用于传入选项。以NULL为终止符表示参数传递完毕。返回值不用判断,因为替换成功不会返回,替换失败将会执行原文件后续代码。调用方式:execl ("/usr/bin/ls", "ls", "-a", "-l", NULL); execlp(p代表环境变量path) file:要替换的程序名,可以是相对路径,也可以在环境变量中搜索。arg:可变参数,跟上面一样。返回值:不用判断调用方式:execlp(“./hello”,“./hello” ,NULL); execv和execvp跟execl的区别是可变参数变成字符串数组。 char* commands[] = {"./hello", NULL}; std::cout |
今日新闻 |
推荐新闻 |
专题文章 |
CopyRight 2018-2019 实验室设备网 版权所有 |