Linux笔记 ·

《Linux内核分析》之分析fork函数对应的系统调用处理过程

实验过程

 

实验过程

1、在实验楼中shell终端依次执行如下代码:

cd LinuxKernel

rm -rf menu

git clone https://github.com/mengning/menu.git

cd menu

mv test_fork.c test.c

make rootfs

可看到启动后的MenuOS已经包含了fork命令。

2、通过增加-s -S启动参数打开调试模式

①其中在之前代码基础上先通过

 cd ..

返回上一级目录

②再执行如下代码打开调试模式,若无步骤①会提示找不到相关文件

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

3、打开gdb进行远程调试

①相关配置

gdb

file linux-3.18.6/vmlinux

target remote:1234

②设置断点

b sys_clone

b do_fork

b dup_task_struct

b copy_process

b copy_thread

b ret_from_fork

代码及分析

tast_struct

xref: /linux-3.18.6/include/linux/sched.h

struct task_struct {
    volatile long state;        //说明了该进程是否可以执行,还是可中断等信息
    unsigned long flags;        //进程号,在调用fork()时给出
    int sigpending;             //进程上是否有待处理的信号
    mm_segment_t addr_limit;    //进程地址空间,区分内核进程与普通进程在内存存放的位置不同
                                //0-0xBFFFFFFF for user-thead
                                //0-0xFFFFFFFF for kernel-thread
    //调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度
    volatile long need_resched;
    int lock_depth;             //锁深度
    long nice;                  //进程的基本时间片
    //进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR, 分时进程:SCHED_OTHER
    unsigned long policy;
    struct mm_struct *mm;       //进程内存管理信息
    int processor;
    //若进程不在任何CPU上运行, cpus_runnable 的值是0,否则是1 这个值在运行队列被锁时更新
    unsigned long cpus_runnable, cpus_allowed;
    struct list_head run_list;  //指向运行队列的指针
    unsigned long sleep_time;   //进程的睡眠时间
    //用于将系统中所有的进程连成一个双向循环链表, 其根是init_task
    struct task_struct *next_task, *prev_task;
    struct mm_struct *active_mm;
    struct list_head local_pages;       //指向本地页面
    unsigned int allocation_order, nr_local_pages;
    struct linux_binfmt *binfmt;        //进程所运行的可执行文件的格式
    int exit_code, exit_signal;
    int pdeath_signal;                  //父进程终止是向子进程发送的信号
    unsigned long personality;

    int did_exec:1;
    pid_t pid;                          //进程标识符,用来代表一个进程
    pid_t pgrp;                         //进程组标识,表示进程所属的进程组
    pid_t tty_old_pgrp;                 //进程控制终端所在的组标识
    pid_t session;                      //进程的会话标识
    pid_t tgid;
    int leader;                         //表示进程是否为会话主管
    struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
    struct list_head thread_group;      //线程链表
    struct task_struct *pidhash_next;   //用于将进程链入HASH表
    struct task_struct **pidhash_pprev;
    wait_queue_head_t wait_chldexit;    //供wait4()使用
    struct completion *vfork_done;      //供vfork() 使用
    unsigned long rt_priority;          //实时优先级,用它计算实时进程调度时的weight值
    …… //后面就不看了 我们不关心
};

 

进程创建分析

fork一个子进程的代码

#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)
    {
        /* error occurred */
        fprintf(stderr,"Fork Failed!");
        exit(-1);
    }
    else if (pid == 0)
    {
        /* child process */
        printf("This is Child Process!n");
    }
    else
    {
        /* parent process  */
        printf("This is Parent Process!n");
        /* parent will wait for the child to complete*/
        wait(NULL);
        printf("Child Complete!n");
    }
}

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

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

Linux通过复制父进程来创建一个新进程,那么这就给我们理解这一个过程提供一个想象的框架:

复制一个PCB——task_struct

err = arch_dup_task_struct(tsk, orig);

要给新进程分配一个新的内核堆栈

ti = alloc_thread_info_node(tsk, node);
tsk->stack = ti;
setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈

 

参与评论