学号后三位<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和进程切换的更多相关文章

  1. [MISSAJJ原创]cell内 通过SDWebImage自定义创建动态菊花加载指示器

    最后更新已经放到了github上了 MISSAJJ自己写的一个基于SDWebImage自定义的管理网络图片加载的工具类(普通图片加载,渐现Alpha图片加载,菊花Indicator动画加载) 经常在项 ...

  2. Java 反射理解(二)-- 动态加载类

    Java 反射理解(二)-- 动态加载类 概念 在获得类类型中,有一种方法是 Class.forName("类的全称"),有以下要点: 不仅表示了类的类类型,还代表了动态加载类 编 ...

  3. iscroll.js实现上拉刷新,下拉加载更多,应用技巧项目实战

    上拉刷新,下拉加载更多...仿原生的效果----iscroll是一款做滚动效果的插件,具体介绍我就不废话,看官方文档,我只写下我项目开发的一些用到的用法: (如果不好使,调试你的css,想必是个很蛋疼 ...

  4. PullToRefreshGridView上拉刷新,下拉加载

    PullToRefreshGridView上拉刷新,下拉加载 布局: <?xml version="1.0" encoding="utf-8"?> ...

  5. 深入理解 Laravel 中 config 配置加载原理

    Laravel的配置加载其实就是加载config目录下所有文件配置.如何过使用php artisan config:cache则会把加载的配置合并到一个配置文件中,下次请求就不会再去加载config目 ...

  6. vux (scroller)上拉刷新、下拉加载更多

    1)比较关键的地方是要在 scroller 组件上里加一个 ref 属性 <scroller :lockX=true height="-170" :pulldown-conf ...

  7. iOS--MJRefresh的使用 上拉刷新和下拉加载

    1.一般使用MJRefresh 来实现上拉刷新和下拉加载功能 2.MJRefresh 下载地址:https://github.com/CoderMJLee/MJRefresh 3. MJRefresh ...

  8. 【转】vux (scroller)上拉刷新、下拉加载更多

    1)比较关键的地方是要在 scroller 组件上里加一个 ref 属性 <scroller :lockX="true" height="-170" :p ...

  9. 在Unity中创建可远程加载的.unity3d包

    在一个Unity项目中,发布包本身不一定要包括所有的Asset(译为资产或组件),其它的部分可以单独发布为.unity3d,再由程序从本地/远程加载执行,这部分不在本文讨论范围.虽然Unity并没有直 ...

随机推荐

  1. ubuntu系统下,下载Android源码

    博主电脑的Windows的  是用的vmware装的ubuntu kylin.vmware随便找都能找到尽量安装最新版本号的,而unbuntu在官方站点下载即可了,安装vmware的时候建议关闭安全类 ...

  2. 嵌入式开发之函数解析---ip地址2进制转字符inet_ntoa 调用中只生效一次

    inet_addr()   简述:将一个点间隔地址转换成一个in_addr. #include <winsock.h> unsigned long PASCAL FAR inet_addr ...

  3. libjpeg交叉编译

    下载libjpeg http://libjpeg.sourceforge.net/ 解压tar -zxf jpegsrc.v6b.tar.gz cd jpeg-6b cp /usr/bin/libto ...

  4. 写一个简单的Makefile

    all: osx .PHONY: osx linux run osx: kale.dylib linux : kale.so run: kale.bin CC = gcc OBJECTS = $(pa ...

  5. 怎么查询数据库中第30到40条记录呢? 通过ID,查询当前第30-40条记录 注意,ID不是顺序的

    http://blog.csdn.net/lee576/article/details/5812347 http://bbs.csdn.net/topics/190070614 http://www. ...

  6. Java 基础 —— 注解

    注解(annotation)不是注释(comment): 注解,是一种元数据(metadata),可为我们在代码中添加信息提供了一种形式化的方法.注解在一定程度上实现了元数据和源代码文件的结合,而不是 ...

  7. 洛谷 P2038 无线网络发射器选址 —— 二维树状数组

    题目:https://www.luogu.org/problemnew/show/P2038 大水题暴露出我的愚蠢. 用二维树状数组,然而居然忘了它应该那样写,调了一个小时: 正方形可以超出外面,只要 ...

  8. Java IO 字节流与字符流 (二)

    1. 什么是流 Java中的流是对字节序列的抽象,我们可以想象有一个水管,只不过现在流动在水管中的不再是水,而是字节序列.和水流一样,Java中的流也具有一个“流动的方向”,通常可以从中读入一个字节序 ...

  9. 解决 jquery dialog 弹框destroy销毁方法不能把弹出元素设置成初始状态

    在使用jquery ui中的dialog弹出窗口的时候遇到一个问题,就是页面弹出窗口关闭后希望表单元素能回到初始状态 例如文本框输入内容后关闭dialog后里面的内容清除,使用了destroy方法也不 ...

  10. bzoj 1579: [Usaco2009 Feb]Revamping Trails 道路升级【分层图+spfa】

    至死不用dijskstra系列2333,洛谷上T了一个点,开了O2才过 基本想法是建立分层图,就是建k+1层原图,然后相邻两层之间把原图的边在上一层的起点与下一层的终点连起来,边权为0,表示免了这条边 ...