从整体上理解进程创建、可执行文件的加载和进程执行进程切换,重点理解分析fork、execve和进程切换
学号后三位<168>
原创作品转载请注明出处https://github.com/mengning/linuxkernel/
1.分析fork函数对应的内核处理过程sys_clone,理解创建一个新进程如何创建和修改task_struct数据结构
Linux中创建进程一共有三个函数
- fork,创建子进程
- vfork,与fork类似,但是父子进程共享地址空间,而且子进程先于父进程运行。
- clone,主要用于创建线程
进程创建过程:
YSCALL_DEFINE0(fork)
{
return do_fork(SIGCHLD, , , NULL, NULL);
}
#endif SYSCALL_DEFINE0(vfork)
{
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, ,
, NULL, NULL);
} SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
int __user *, parent_tidptr,
int __user *, child_tidptr,
int, tls_val)
{
return do_fork(clone_flags, newsp, , parent_tidptr, child_tidptr);
}
do_fork 代码分析:
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
{
struct task_struct *p;
int trace = ;
long nr; // ... // 复制进程描述符,返回创建的task_struct的指针
p = copy_process(clone_flags, stack_start, stack_size,
child_tidptr, NULL, trace); if (!IS_ERR(p)) {
struct completion vfork;
struct pid *pid; trace_sched_process_fork(current, p); // 取出task结构体内的pid
pid = get_task_pid(p, PIDTYPE_PID);
nr = pid_vnr(pid); if (clone_flags & CLONE_PARENT_SETTID)
put_user(nr, parent_tidptr); // 如果使用的是vfork,那么必须采用某种完成机制,确保父进程后运行
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
get_task_struct(p);
} // 将子进程添加到调度器的队列,使得子进程有机会获得CPU
wake_up_new_task(p); // ... // 如果设置了 CLONE_VFORK 则将父进程插入等待队列,并挂起父进程直到子进程释放自己的内存空间
// 保证子进程优先于父进程运行
if (clone_flags & CLONE_VFORK) {
if (!wait_for_vfork_done(p, &vfork))
ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
} put_pid(pid);
} else {
nr = PTR_ERR(p);
}
return nr;
}
do_fork处理了以下内容:
- 调用copy_process,将当期进程复制一份出来为子进程,并且为子进程设置相应地上下文信息。
- 初始化vfork的完成处理信息(如果是vfork调用)
- 调用wake_up_new_task,将子进程放入调度器的队列中,此时的子进程就可以被调度进程选中,得以运行。
- 如果是vfork调用,需要阻塞父进程,知道子进程执行exec。
2.使用gdb跟踪分析一个fork系统调用内核处理函数do_fork ,验证您对Linux系统创建一个新进程的理解






ret_from_fork;决定了新进程的第一条指令地址。
在ret_from_fork之前,也就是在copy_thread()函数中childregs = current_pt_regs();该句将父进程的regs参数赋值到子进程的内核堆栈,
*childregs的类型为pt_regs,里面存放了SAVE ALL中压入栈的参数
故在之后的RESTORE ALL中能顺利执行下去

3.理解编译链接的过程和ELF可执行文件格式
编译过程
编译->汇编->链接
)预处理
gcc -E -o hello.cpp hello.c -m32 )编译为汇编代码
gcc -x cpp-output -S -o hello.s hello.cpp -m32 )汇编代码编译为目标代码
gcc -x assembler -c hello.s -o hello.o -m32 )链接
gcc -o hello hello.o -m32
使用共享库的编译,libc, printf )
静态编译(所依赖的都放在hello.static内部)
gcc -o hello.static hello.o -m32 -static
文件格式:
a.out
COFF
PE
ELF(EXECUTABLE AND LINKABLE FORMAT)
三种目标文件:
- 可重定位文件 .o文件
- 可执行文件
- 共享目标文件 .so文件
用来被两个链接器链接:链接编辑器,可以和其他可重定位和共享文件object来创建其他object(静态链接); 动态链接器,联合一个可执行文件和其他共享object文件来创建一个进程映像 .
ELF文件加载内存(静态链接,所有代码放在一个段),形成进程,默认是加载到以0x8048000开始处
gcc -shared shlibexample.c -o libshlibexample.so -m32
gcc -shared dllibexample.c -o libdllibexample.so -m32 gcc main.c -o main -L$PWD -lshlibexample -ldl -m32 //-ldl动态加载库
export LD_LIBRARY_PATH=$PWD //当前目录加入到库搜索路径
4.编程使用exec*库函数加载一个可执行文件可执行文件的装载
- 执行一个程序的shell环境,直接使用execve系统调用
- shell不限制命令行个数,取决于命令本身
- shell调用execve将命令行参数和环境参数传递给可执行程序的main函数
- int execve(const char filename, char const argv[], char * const envp[])
- 库函数exec*是系统调用execve的封装例程
- sys_execve会解析可执行文件格式
- do_execve -> do_execve_common -> exec_binprm
- search_binary_handler符合寻找文件格式对应的解析模块(根据文件头部信息寻找对应的文件格式处理模块
list_for_each_entry(fmt, &formats, lh) {
if (!try_module_get(fmt->module))
continue;
read_unlock(&binfmt_lock);
bprm->recursion_depth++;
retval = fmt->load_binary(bprm);//解析elf文件格式的执行位置
read_lock(&binfmt_lock);
- 对于ELF格式的可执行文件fmt->load_binary(bprm);执行的应该是load_elf_binary其内部是和ELF文件格式解析的部分需要和ELF文件格式标准结合起来阅读
- Linux内核是如何支持多种不同的可执行文件格式的?
static struct linux_binfmt elf_format = {
.module = THIS_MODULE,
.load_binary = load_elf_binary,
.load_shlib = load_elf_library,
.core_dump = elf_core_dump,
.min_coredump = ELF_EXEC_PAGESIZE,
};
static int __init init_elf_binfmt(void)
{
register_binfmt(&elf_format);
return ;
}
五、总结
1、进程创建
fork创建一个子进程,父进程在运行中间过程中fork,产生一个子进程,子进程不会从头开始运行代码,而会从fork的开始的后面的代码开始运行。exec的调用即是替换掉子进程,进而替换上有用的想要执行的进程,避免父子进程完全一样的浪费,若是替换成功,则不会返回。
fork和exec系统调用最终都是通过int 0x80软中断 + EAX寄存器(存储对应的系统调用号)进入内核,在内核中fork和exec对应找到sys_fork/do_fork和sys_exec/do_exec。do_fork主要的工作就是创建一个新进程,创建的方法是拷贝当前进程、分配新的进程pid、插入进程相关链表队列中等。do_exec的工作较为复杂,它的主要目标是将一个可执行程序加载到当前进程中来,返回到用户态时EIP指向可执行程序的入口位置(即0x08048000)。
2、可执行程序的加载过程
可执行程序的加载过程可以分为两种情况:一种是加载静态编译的ELF文件,只需要将代码段加载到0x08048000的位置,其他的数据也根据规则加载即可;另一种情况更常见需要动态链接。
用共享库来动态链接的过程,共享库就是为了解决这一问题,共享库是一个目标模块,在运行时可被加载到任意的存储器地址,并和一个在存储器中的程序链接起来。这个过程叫做动态链接,是由动态链接器的程序来完成的。
大致步骤:
(1)源程序文件和头文件等被翻译器生成可重定位的目标文件;
(2)链接器把可重定位目标文件和共享库的重定位和符号表的信息经过链接生成部分链接的可执行目标文件;
(3)加载时,由动态链接器把部分链接的可执行文件和共享库的代码、数据完全链接成完全可执行文件。
从整体上理解进程创建、可执行文件的加载和进程执行进程切换,重点理解分析fork、execve和进程切换的更多相关文章
- [MISSAJJ原创]cell内 通过SDWebImage自定义创建动态菊花加载指示器
最后更新已经放到了github上了 MISSAJJ自己写的一个基于SDWebImage自定义的管理网络图片加载的工具类(普通图片加载,渐现Alpha图片加载,菊花Indicator动画加载) 经常在项 ...
- Java 反射理解(二)-- 动态加载类
Java 反射理解(二)-- 动态加载类 概念 在获得类类型中,有一种方法是 Class.forName("类的全称"),有以下要点: 不仅表示了类的类类型,还代表了动态加载类 编 ...
- iscroll.js实现上拉刷新,下拉加载更多,应用技巧项目实战
上拉刷新,下拉加载更多...仿原生的效果----iscroll是一款做滚动效果的插件,具体介绍我就不废话,看官方文档,我只写下我项目开发的一些用到的用法: (如果不好使,调试你的css,想必是个很蛋疼 ...
- PullToRefreshGridView上拉刷新,下拉加载
PullToRefreshGridView上拉刷新,下拉加载 布局: <?xml version="1.0" encoding="utf-8"?> ...
- 深入理解 Laravel 中 config 配置加载原理
Laravel的配置加载其实就是加载config目录下所有文件配置.如何过使用php artisan config:cache则会把加载的配置合并到一个配置文件中,下次请求就不会再去加载config目 ...
- vux (scroller)上拉刷新、下拉加载更多
1)比较关键的地方是要在 scroller 组件上里加一个 ref 属性 <scroller :lockX=true height="-170" :pulldown-conf ...
- iOS--MJRefresh的使用 上拉刷新和下拉加载
1.一般使用MJRefresh 来实现上拉刷新和下拉加载功能 2.MJRefresh 下载地址:https://github.com/CoderMJLee/MJRefresh 3. MJRefresh ...
- 【转】vux (scroller)上拉刷新、下拉加载更多
1)比较关键的地方是要在 scroller 组件上里加一个 ref 属性 <scroller :lockX="true" height="-170" :p ...
- 在Unity中创建可远程加载的.unity3d包
在一个Unity项目中,发布包本身不一定要包括所有的Asset(译为资产或组件),其它的部分可以单独发布为.unity3d,再由程序从本地/远程加载执行,这部分不在本文讨论范围.虽然Unity并没有直 ...
随机推荐
- union关键字及大小端模式
1. union 关键字 union 维护足够的空间来置放多个数据成员中的“一种”,而不是为每一个数据成员配置空间,在 union 中所有的数据成员共用一个空间,同一时间只能储存其中一个数据成员,所有 ...
- 安装ubuntu后启动黑屏
我是在windows7上的一个空暇盘上安装ubuntu 14.安装后重新启动没有ubuntu的启动项,然后用easybcd生成启动项,重新启动发现果然有,可是选择之后黑屏. 百度半天无果.后来无意发现 ...
- python 矩阵
python的numpy库提供矩阵运算的功能,因此我们在需要矩阵运算的时候,需要导入numpy的包. 1.numpy的导入和使用 from numpy import *;#导入numpy的库函数 im ...
- ios20--xib2
故事板控制器: // // ViewController.m // 03-通过xib自定义商品的View #import "ViewController.h" #import &q ...
- ubuntu-10.10嵌入式开发环境搭建【转】
本文转载自:http://blog.csdn.net/zjhsucceed_329/article/details/8036781 版权声明:本文为博主原创文章,未经博主允许不得转载. ubuntu- ...
- C的结构体函数
#include<stdio.h> #include<string.h> struct Test { int age; ]; double score; }std1; //结构 ...
- clc和clear命令的使用
clc命令是用来清除命令窗口的内容,这点不用多说.不管开启多少个应用程序,命令窗口只有一个,所以clc无论是在脚本m文件或者函数m文件调用时,clc命令都会清除命令窗口的内容.clear命令可以用来清 ...
- KeepAlived的介绍
KeepAlived介绍 keepalived keepalived是一个类似于layer3, 4 & 7交换机制的软件,也就是我们平时说的第3层.第4层和第7层交换. Keepalived的 ...
- DNS域名记录
DNS域名记录 DNS数据库 在DNS的解析过程中用到域名的解析资源的记录,这个解析记录在DNS当中称为DNS数据库. 这个数据库又分为正解和反解,正解就是从主机名到ip的过程,反解就是从ip反响解析 ...
- Rails5 layout 和 template
layout是布局,比如页面的头(head), 脚(foot), 内容(body) template是布局的一部分的内容 这两货实在太像了,写了这些我也是一脸懵逼. 换个说法,layout和tem ...