1. 如何描述一个进程:进程描述符的数据结构;
  2. 如何创建一个进程:内核是如何执行的,以及新创建的进程从哪里开始执行;
  3. 使用gdb跟踪新进程的创建过程。
  4. 进程的描述

操作系统三大功能:

  • 进程管理(最核心最基础)
  • 内存管理
  • 文件系统

进程描述符task_struct数据结构

  • task _ struct:为了管理进程,内核必须对每个进程进行清晰的描述,进程描述符提供了内核所需了解的进程信息。struct task_struct数据结构很庞大。

  • 进程的状态:Linux进程的状态(就绪态、运行态、阻塞态)与操作系统原理中的描述的进程状态有所不同,比如就绪状态和运行状态都是TASK_RUNNING,当一个进程处于TASK_RUNNING的时候是可运行的,至于有没有运行其中的区别就在于有没有获得CPU的使用权。

  • 进程的标示pid:用来标示进程

  • 进程描述符task_struct数据结构

    1. struct task_struct {
    2. volatile long state; /* 进程的运行状态-1 unrunnable, 0 runnable, >0 stopped */
    3. void *stack; /*指定了进程的内核堆栈*/
    4. atomic_t usage;
    5. unsigned int flags; /* 每一个进程的标识符 */
    6. unsigned int ptrace;
    7. int on_rq;/运行队列和进程调度相关/
  • 进程调度的链表

    所有进程链表struct list_head tasks;

    1. struct list_head{
    2. struct list_head *next,*prev;
    3. };

    内核的双向循环链表的实现方法:一个更简略的双向循环链表。

  • 进程的地址空间内存管理相关

    1. struct mm_struct *mm, *active_mm;
  • 进程标示(pid)

    1. pid_t pid;
    2. pid_t tgid;
  • 进程父子关系

    程序创建的进程具有父子关系,在编程时往往需要引用这样的父子关系。进程描述符中有几个域用来表示这样的关系

  1. struct task_struct __rcu *real_parent; /* real parent process */
  2. struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
  3. struct list_head children; /* list of my children */
  4. struct list_head sibling; /* linkage in my parent's children list */
  5. struct task_struct *group_leader; /* threadgroup leader */
  • 调试

    1. struct list_head ptraced;
    2. struct list_head ptrace_entry;
  • 当前任务相关的CPU状态

    1. struct thread_struct thread;

    Linux为每个进程分配一个8KB大小的内存区域,用于存放该进程两个不同的数据结构:Thread_info和进程的内核堆栈

进程的创建

进程的创建

start _ kernel代码中的rest _ init创建两个内核线程,kernel _ init和kthreadd。kernel _ init将用户态进程init启动,是所有用户态进程的祖先,kthreadd是所有内核线程的祖先。

在命令行下创建进程和启动内核的原理是大致相同的,复制一份0号进程描述符,然后根据进程需要将pid等数据结构修改,就完成了进程的创建。

fork一个进程的用户态代码

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int main(int argc, char * argv[])
  5. {
  6. int pid;
  7. /* fork another process */
  8. pid = fork();//fork是在用户态用于创建一个子进程的系统调用
  9. if (pid < 0)
  10. {
  11. /* error occurred */
  12. fprintf(stderr,"Fork Failed!");
  13. exit(-1);
  14. }
  15. else if (pid == 0)
  16. {
  17. /* child process */
  18. printf("This is Child Process!\n");
  19. }
  20. else
  21. {
  22. /* parent process */
  23. printf("This is Parent Process!\n");
  24. /* parent will wait for the child to complete*/
  25. wait(NULL);
  26. printf("Child Complete!\n");
  27. }
  28. }

fork系统调用在父进程和子进程各返回一次,在子进程中返回的pid是0,父进程中的返回值子进程的pid,所以程序中的else if(pid==0)和else都会被执行,它的背后有两个进程,可以利用这一点在用户态创建子进程。

创建一个新进程在内核中的执行过程

系统调用再回顾:见第四节博客http://www.cnblogs.com/July0207/p/5277774.html

fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建

Linux通过复制父进程来创建一个新进程,大部分信息都是相同的,少数信息需要修改,否则就会发生混乱。例如:pid、进程链表、内核堆栈、进程执行上下文thread等,并设置好fork返回的下一条指令(esp和eip)。由此得出进程创建的一个大致框架:

  • 复制一个PCB——task_struct

    1. p = dup_task_struct(current);//复制进程的PCB
    2. int __weak arch_dup_task_struct(struct task_struct *dst,struct task_struct *src)
    3. {
    4. *dst = *src;//通过赋值实现复制
    5. return 0;
    6. }
  • 给新进程分配一个新的内核堆栈

    1. ti = alloc_thread_info_node(tsk, node);
    2. tsk->stack = ti;
    3. setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈
  • 修改复制过来的进程数据,比如pid、进程链表等(见copy_process内部)。

    1. /*copy_thread in copy_process*/
    2. /*拷贝内核堆栈数据和指定新进程的第一条指令地址*/
    3. *childregs = *current_pt_regs(); //复制内核堆栈,只复制了SAVE_ALL相关的部分
    4. childregs->ax = 0; //子进程的fork返回0的原因
    5. p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
    6. p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址

创建的新进程开始启动的位置

int指令和SAVE_ALL压到内核栈的内容:系统调用压栈的相关数据结构,系统调用号,寄存器参数。

当子进程获得CPU的控制权开始运行的时候,ret _ form _ fork可以将后面的堆栈出栈,从iret返回到用户态,从而切换到子进程的用户空间。

实验——使用gdb跟踪创建新进程的过程

  1. 将menu目录删除,利用git命令克隆一个新的menu目录。

    1. rm menu -rf
    2. git clone https://github.com/mengning/menu.git
  2. 用test_fork.c将test.c覆盖,然后重新编译footfs

    1. mv test_fork.c test.c
    2. make rootfs

    可以看到fork函数的运行结果:

  1. gdb准备调试,加载符号表并设置端口。

    1. file linux-3.18.6/vmlinux
    2. target remote:1234
  2. 设置断点:

  1. 单步执行:

(由于这里报错原因不明,没有办法进行单步调试,后续的实验在实验楼环境下完成。)

(1)使用命令c继续执行,可看到执行到do_fork处

(2)单步执行到copy_process

(3)之后进入dup _ task _ struct

(4)继续执行可到copy_thread

(5)之后可跟踪到ret _ form _ fork,不能继续跟踪执行,结束调试。

总结:通过使用gdb跟踪创建新进程的过程,可以看出创建一个新进程的过程如下:

  • 首先调用do_fork函数(函数功能是fork一个新的进程);
  • do _ fork中的copy _ process函数复制父进程的相关信息
  • dup _ task _ struct()复制进程的PCB
  • thread相关代码给新进程分配一个新的内核堆栈,copy _ thread复制内核堆栈,只复制SAVE _ ALL相关的部分
  • 设置sp调度到子进程时的内核栈顶,ip转到子进程时的第一条指令地址
  • 当子进程获得CPU的控制权开始运行的时候,ret _ form _ fork可以将后面的堆栈出栈,从iret返回到用户态,从而切换到子进程的用户空间,完成新进程的创建。

Linux内核第六节 20135332武西垚的更多相关文章

  1. Linux内核第四节 20135332武西垚

    实验目的: 使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用 实验过程: 查看系统调用列表 get pid 函数 #include <stdio.h> #include & ...

  2. Linux内核第五节 20135332武西垚

    20135332武西垚 在MenuOS中通过添加代码增加自定义的系统调用命令 使用gdb跟踪调试内核 简单分析system_call代码了解系统调用在内核代码中的处理过程 由于本周实验是在Kali虚拟 ...

  3. Linux内核第七节 20135332武西垚

    预处理.编译.链接和目标文件的格式 可执行程序是怎么得来的 以C语言为例,c代码经过编译器的预处理,编译成汇编代码,由汇编器编译成目标代码,再链接成可执行文件,由操作系统加载到cpu里来执行. (截图 ...

  4. Linux内核总结博客 20135332武西垚

    http://www.cnblogs.com/wuxiyao/p/5220677.htmlhttp://www.cnblogs.com/wuxiyao/p/5247571.htmlhttp://www ...

  5. Linux内核第三节 20135332武西垚

    总结部分: Linux内核源代码: Arch 支持不同cpu的源代码:主要关注x86 Init   内核启动的相关代码:主要关注main.c,整个Linux内核启动代码start_kernel函数 K ...

  6. Linux内核第八节 20135332武西垚

    第一种分类: I/O-bound:频繁进行I/O,并且需要花费很多时间等待I/O完成 CPU-bound:计算密集,需要大量的CPU时间进行运算 第二种分类: 批处理进程:不必与用户交互,常在后台进行 ...

  7. (笔记)Linux内核学习(六)之并发和同步概念

    一 临界区和竞争条件 临界区:访问和操作共享数据的代码段. 竞争条件:多个执行线程处于同一个临界区中. 处于竞争条件:造成访问的数据或者资源不一致状态: 对资源i的访问:ProcessA和B访问后得到 ...

  8. Linux内核第二节

    作者:武西垚 深入理解函数调用堆栈 堆栈是C语言程序运行时必须的一个记录调用路径和参数的空间 堆栈的作用 函数调用框架 传递参数 保存返回地址 提供局部变量空间 堆栈相关的寄存器 esp,堆栈指针,指 ...

  9. 《Linux内核分析》 第六节 进程的描述和进程的创建

    <Linux内核分析> 第六节 进程的描述和进程的创建 20135307 张嘉琪 原创作品转载请注明出处 +<Linux内核分析>MOOC课程http://mooc.study ...

随机推荐

  1. VMWare:vSphere6 企业版参考序列号

    HV4WC-01087-1ZJ48-031XP-9A843 NF0F3-402E3-MZR80-083QP-3CKM2 4F6FX-2W197-8ZKZ9-Y31ZM-1C3LZ JZ2E9-6D2D ...

  2. Vue、Vuex+Cookie 实现自动登陆 。

    概述 1.自动登陆实现思路. 2.vuex + cookie 多标签页状态保持. 自动登陆的需求: 1.登陆时勾选自动登陆,退出登陆或登陆到期后再次登陆后自动填写表单(记住密码)或访问登陆页自动登陆. ...

  3. 个人博客作业Week3(微软必应词典客户端的案例分析)

    软件缺陷常常又被叫做Bug,即为计算机软件或程序中存在的某种破坏正常运行能力的问题.错误,或者隐藏的功能缺陷.缺陷的存在会导致软件产品在某种程度上不能满足用户的需要.IEEE729-1983对缺陷有一 ...

  4. 5、爬虫系列之scrapy框架

    一 scrapy框架简介 1 介绍 (1) 什么是Scrapy? Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,非常出名,非常强悍.所谓的框架就是一个已经被集成了各种功能(高性能 ...

  5. oracle+st_geometry

    最近因为性能的原因开始关注通过oracle和st_geometry直接操作数据库来解决实际业务问题.主要还是用到了“使用 SQL 处理 ST_Geometry”.对此,ESRI给出的帮助文档中的解释如 ...

  6. ARDUINO 中断

    设置中断函数   attachInterrupt() attachInterrupt(interrupt, function, mode) 描述: 当发生外部中断时,调用一个指定函数.当中断发生时,该 ...

  7. 2017-2018-2 20155314《网络对抗技术》Exp1 PC平台逆向破解(5)M

    2017-2018-2 20155314<网络对抗技术>Exp1 PC平台逆向破解(5)M 目录 实验要求 实验内容 预备知识 实验步骤 0 准备工作:macOS下Parallels De ...

  8. luogu-1563玩具谜题

    题目描述 小南有一套可爱的玩具小人, 它们各有不同的职业. 有一天, 这些玩具小人把小南的眼镜藏了起来. 小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的面朝圈外.如下图: 这时singersi ...

  9. CVE-2018-14634 - Linux create_elf_tables()中的整型溢出 - 翻译

    原文:https://seclists.org/oss-sec/2018/q3/274 摘要 Qualys研究实验室的安全团队发现一个位于Linux内核函数create_elf_tables()中的整 ...

  10. BZOJ1767/Gym207383I CEOI2009 Harbingers 斜率优化、可持久化单调栈、二分

    传送门--BZOJCH 传送门--VJ 注:本题在BZOJ上是权限题,在Gym里面也不能直接看,所以只能在VJ上交了-- 不难考虑到这是一个\(dp\). 设\(dep_x\)表示\(x\)在树上的带 ...