6.fork + execve:一个进程的诞生

前两讲我们介绍了fork()系统调用以及execve()内核函数(注:所有的库函数exec*都是execve的封装例程)。

简单回顾一下fork()系统调用(传送门:fork()到底干了啥?):

进程调用fork()创建一个新的进程,新进程复制了父进程的task_struct(PCB,process control block,进程控制块),以及task_struct中的各个子模块,比如内核堆栈等,然后对各个子模块做了修改。系统调用通过eax寄存器保存返回值,fork()系统调用结束后从内核态返回两次,一次是父进程返回,一次是子进程返回,区分父子进程的方法就是看返回值是否为0,若为0,说明返回的是新进程,不为0返回的是父进程。

简单回顾一下execve()内核函数的作用(传送门:execve()到底干了啥?):

解析ELF文件,把ELF文件装入内存,修改进程的数据段代码段,修改进程的用户态堆栈(主要是把命令行参数和shell上下文加入到用户态堆栈)。修改进程内核堆栈(特别是内核堆栈的ip指针),进程从execve返回到用户态后ip指向ELF文件的main函数地址,用户态堆栈中包含了命令行参数和shell上下文环境

我们惊奇的发现进程的创建是通过fork()函数完成的,进程的执行代码是execve()加载的,要是把fork()和execve()组和在一起~~~没错,就是一个完整的进程启动故事!

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char * argv[])
{
    int pid;
    /* fork another process */
    pid = fork();
    if (pid<0) // pid都是大于等于0的
    {
        /* error occurred */
        fprintf(stderr,"Fork Failed!");
        exit(-1);
    }
    else if (pid==0) // 子进程的返回值(eax寄存器保存)是0,所以子进程进入else if条件分支
    {
        /*  child process   */
        execlp("/bin/ls","ls",NULL);// 在子进程中加载指定的可执行文件
    }
    else  // 父进程的返回值(eax寄存器保存)> 0,所以父进程进入else条件分之
    {
        /*    parent process  */
        /* parent will wait for the child to complete*/
        wait(NULL);
        printf("Child Complete!");
        exit(0);
    }
}
时间: 2024-08-30 03:56:40

6.fork + execve:一个进程的诞生的相关文章

一个进程的诞生与死亡

执行一个程序,必然就产生一个进程.最直接的程序执行方式就是在shell中以鼠标双击某一个可执行文件图标,执行起来的App进程起始是shell调用CreateProcess激活的. 1.shell调用CreateProcess激活App.exe 2.产生一个进程核心对象,计数值为1 3.系统为此进程建立一个4GB地址空间 4.加载器将必要的代码加载到上述地址空间中,包括App.exe的程序.数据,以及所需要的动态链接函数库DLL.加载器如何知道要加载那些DLLs呢?它们被记录在可执行文件PE文件的

从库函数fork()起步,探究新进程的诞生

本周在线学习了孟宁老师的<Linux内核分析>,本周的主要内容是进程的描述和创建,针对本次课程的实验现记录于本博文. 我们学习过操作系统这么课程,知道PCB是进程在内核中的唯一标识,PCB结构中包括本进程的全部信息.具体到Linux操作系统,这个PCB结构就是Linux内核中的task_struct结构体,该结构体非常庞大,包含了进程的很多基本信息.当我们使用fork()函数创建新进程的时候,理所当然的会涉及到task_struct函数,我们下面就从本结构体开始分析: 1235struct t

从整理上理解进程创建、可执行文件的加载和进程执行进程切换,重点理解分析fork、execve和进程切换

一.首先我们来看看进程控制块PCB也就是task_struct,(源码) 选出task_struct中几个关键的参数进行分析 struct task_struct {volatile long state; //进程状态 /* -1 unrunnable, 0 runnable, >0 stopped */ void *stack; //进程内核堆栈 atomic_t usage; unsigned int flags; //进程标识符 /* per process flags, defined

UNIX网络编程卷1 服务器程序设计范式1 并发服务器,为每个客户请求fork一个进程

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.传统并发服务器调用 fork 派生一个子进程来处理每个客户 2.传统并发服务器的问题在于为每个客户现场 fork 一个子进程比较耗费 CPU 时间. /* include serv01 */ #include "unp.h" int main(int argc, char **argv) { int listenfd, connfd; pid_t childpid; void

UNIX网络编程卷1 server程序设计范式1 并发server,为每一个客户请求fork一个进程

本文为senlie原创.转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.传统并发server调用 fork 派生一个子进程来处理每一个客户 2.传统并发server的问题在于为每一个客户现场 fork 一个子进程比較耗费 CPU 时间. /* include serv01 */ #include "unp.h" int main(int argc, char **argv) { int listenfd, connfd; pid_t childp

通过fork函数创建进程的跟踪,分析linux内核进程的创建

作者:吴乐 山东师范大学 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.实验过程 1.打开gdb,设置断点 2.跟踪到do_fork处 3.跟踪到copy_process断点处. 4.跟踪到ret_from_fork子进程创建完成. 二.代码部分分析 Fork的系统调用代码在linux/arch/i386/kernel/process.c中:       asmlinkage int sys_fork(s

windows 一个进程可以允许最大的线程数

默认情况下,一个线程的栈要预留1M的内存空间 而一个进程中可用的内存空间只有2G,所以理论上一个进程中最多可以开2048个线程 但是内存当然不可能完全拿来作线程的栈,所以实际数目要比这个值要小. 你也可以通过连接时修改默认栈大小,将其改的比较小,这样就可以多开一些线程. 如将默认栈的大小改成512K,这样理论上最多就可以开4096个线程. 即使物理内存再大,一个进程中可以起的线程总要受到2GB这个内存空间的限制. 比方说你的机器装了64GB物理内存,但每个进程的内存空间还是4GB,其中用户态可用

利用fork循环创建进程

我们知道,fork可以创建子进程,那么如果循环调用fork,进程之间会有什么关系呢? 得到结果: 从结果上分析,一共有4个进程,6132,6133,6134,6135,它们的关系是怎样的呢? 按道理来讲,刚开始i=0,只有一个进程6132,fork后,产生子进程6133.6132执行完printf后,i++.在fork后,6132作为父进程,产生子进程6134.而6133作为i=0时的子进程,执行完printf后,i++,此时6133作为父进程,产生子进程6135. 结果却和所想有些出入,为什么

fork()、僵死进程和孤儿进程

孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程.孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作. 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中.这种进程称之为僵死进程. #include <stdio.h> #include <iostream> #include "unistd.