Linux内核分析 笔记六 进程的描述和进程的创建 ——by王玥

一、知识点总结

(一)进程的描述

1.操作系统内核里有三大功能:

  • 进程管理
  • 内存管理
  • 文件系统

2.进程描述符:task_struct

2.进程描述符——struct task_struct

1. pid_t pid又叫进程标识符,唯一地标识进程
2.双向循环链表链接起了所有的进程,也表示了父子、兄弟等进程关系
3. struct mm_struct 指的是进程地址空间,涉及到内存管理(对于X86而言,一共有4G的地址空间)
4. thread_struct thread 与CPU相关的状态结构体 
5. struct *file表示打开的文件链表
6. Linux为每个进程分配一个8KB大小的内存区域,用于存放该进程两个不同的数据结构:Thread_info和进程的内核堆栈

3.进程状态转换图

数据结构分析:

struct task_struct {
1236	volatile long state;	//运行状态
1237	void *stack; //进程的内核堆栈
1238	atomic_t usage; 
1239	unsigned int flags;//每个进程的标识符

1240 unsigned int
#ifdef CONFIG_SMP //这部分是条件编译
1243	struct llist_node wake_entry;
1244	int on_cpu;
1245	struct task_struct *last_wakee;
1246	unsigned long wakee_flips;
1247	unsigned long wakee_flip_decay_ts;
.....
	struct list_head tasks; //这部分很关键,是进程的列表
1296#ifdef CONFIG_SMP
1297	struct plist_node pushable_tasks;
1298	struct rb_node pushable_dl_tasks;
1299#endif
1300
1301	struct mm_struct *mm, *active_mm;//和地址的内存空间,内存管理有关的,每个地址有独立的地址空间。
1302#ifdef CONFIG_COMPAT_BRK
1303	unsigned brk_randomized:1;
1304#endif
1305	/* per-thread vma caching */
1306	u32 vmacache_seqnum;
1307	struct vm_area_struct *vmacache[VMACACHE_SIZE];
1308#if defined(SPLIT_RSS_COUNTING)
1309	struct task_rss_stat	rss_stat;
1310#endif

链表的数据结构如下:
它是一个双向链表,参见include/linux/list.h

1330	pid_t pid;//进程的pid,来标识某一个进程
1331	pid_t tgid;
....
	/*
1338	 * pointers to (original) parent process, youngest child, younger sibling,//进程的父子关系
1339	 * older sibling, respectively.  (p->father can be replaced with
1340	 * p->real_parent->pid)
1341	 */
1342	struct task_struct __rcu *real_parent; /* real parent process */
1343	struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
1344	/*
1345	 * children/sibling forms the list of my natural children
1346	 */
1347	struct list_head children;	/* list of my children */
1348	struct list_head sibling;	/* linkage in my parent's children list */
1349	struct task_struct *group_leader;	/* threadgroup leader */
1350
...1411/* CPU-specific state of this task */
1412	struct thread_struct thread;//当前CPU相关的状态,在进程切换时起关键作用。

(二)进程的创建

  • 1.进程的状态以及fork一个进程的用户态代码
  • 2.fork系统调用在父进程和子进程各返回一次
  • 3.TASK_RUNNING具体是就绪还是执行,要看系统当前的资源分配情况
  • 4. TASK_ZOMBIE也叫僵尸进程

fork一个子进程的代码

  1. 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();
    9.if (pid < 0) 
    10.{ 
    11./* error occurred */
    12.fprintf(stderr,"Fork Failed!");
    13.exit(-1);
    14.} 
    15.else if (pid == 0) //pid == 0和下面的else都会被执行到(一个是在父进程中即pid ==0的情况,一个是在子进程中,即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.}
    • iret与int 0x80指令对应,一个是弹出寄存器值,一个是压入寄存器的值
    • 如果将系统调用类比于fork();那么就相当于系统调用创建了一个子进程,然后子进程返回之后将在内核态运行,而返回到父进程后仍然在用户态运行
    • 回顾:系统调用的进程创建过程

 

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

1.一个新创建的子进程,(当它获得CPU之后)是从哪一行代码进程执行的?

  • 与之前写过的my_kernel相比较,kernel中是可以指定新进程开始的位置(也就是通过eip寄存器指定代码行)。fork中也有相似的机制
  • 这就涉及子进程的内核堆栈数据状态和task_struct中thread记录的sp和ip的一致性问题,这是在哪里设定的?copy_thread in copy_process

  • fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建;
  • Linux通过复制父进程来创建一个新进程,那么这就给我们理解这一个过程提供一个想象的框架:
    • 复制一个PCB——task_struct

      1. err = arch_dup_task_struct(tsk, orig);
    • 要给新进程分配一个新的内核堆栈

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

  • 从用户态的代码看fork();函数返回了两次,即在父子进程中各返回一次,父进程从系统调用中返回比较容易理解,子进程从系统调用中返回,那它在系统调用处理过程中的哪里开始执行的呢?这就涉及子进程的内核堆栈数据状态和task_struct中thread记录的sp和ip的一致性问题,这是在哪里设定的?copy_thread in copy_process

    1. *childregs = *current_pt_regs(); //复制内核堆栈
    2. childregs->ax = 0; //为什么子进程的fork返回0,这里就是原因!
    3. p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
    4. p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址
      • dup_thread复制父进程的PCB
      • copy_process修改复制的PCB以适应子进程的特点,也就是子进程的初始化
      • 分配一个新的内核堆栈(用于存放子进程数据)

        • 内核堆栈的一部分也要从父进程中拷贝

        • 根据拷贝的内核堆栈情况设置eip,esp寄存器的值

 

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

  1. 更新menu内核,然后删除test_fork.c以及test.c(以减少对之后实验的影响)

  1. 编译内核,可以看到fork命令

  1. 启动gdb调试,并对主要的函数设置断点

  1. 在MenuOS中执行fork,就会发现fork函数停在了父进程中
  2. 继续执行之后,停在了do_fork的位置。然后n单步执行,依次进入copy_process、dup_task_struct。按s进入该函数,可以看到dst =src(也就是复制父进程的struct)

 

     3.在copy_thread中,可以看到把task_pg_regs(p)也就是内核堆栈特定的地址找到并初始化

     4.到了159、160行的代码就是把压入的代码再放到子进程中:

  • *children = *current_pt_regs();
    childregs->ax = 0;
  • 164行,是确定返回地址

    p->thread.ip = (unsigned long) ret_from_fork;
  • 最后,可以输入finish使得进程运行完。
posted on 2016-03-29 14:49  20135232  阅读(243)  评论(0编辑  收藏  举报