(2023-2024-1)20232830《Linux内核原理分析与设计》第七周作业
1. 阅读理解 task_struct 数据结构
task_struct 是 Linux 内核中表示进程的重要数据结构。它包含了有关进程状态、调度信息、文件系统信息等的详细信息。以下是对 task_struct 数据结构的一些主要字段:
- state(进程状态):表示进程的当前状态,可能是运行、睡眠、停止等。状态的改变反映了进程的活动。示例:long state;
- pid(进程标识符):进程的唯一标识符。通过这个字段,内核可以追踪和管理进程。示例:pid_t pid;
- parent(父进程指针):parent 字段指向父进程的 task_struct 结构。这是用于构建进程树的关键字段。示例:struct task_struct __rcu *parent;
- children(子进程链表):children 是一个链表,包含指向当前进程的所有子进程的指针。这有助于实现进程之间的父子关系。示例:struct list_head children;
- sibling(兄弟进程链表):sibling 是一个链表,包含指向与当前进程具有相同父进程的其他进程的指针。这是同一进程组的进程之间的链接。示例:struct list_head sibling;
- comm(进程名):comm 字段包含了进程的名称。它是一个简短的字符串,用于标识进程。示例:char comm[TASK_COMM_LEN];
- mm(内存管理):mm 字段指向描述进程内存布局和管理的结构。这包括进程的地址空间布局、页表等信息。示例:struct mm_struct *mm;
- active_mm(当前地址空间):active_mm 字段指向当前进程正在使用的地址空间。在进程切换时,这个字段可能会改变。示例:struct mm_struct *active_mm;
- group_leader(线程组领导):group_leader 字段指向线程组的领导进程。在多线程环境中,这有助于管理线程组。示例:struct task_struct __rcu *group_leader;
- fs(文件系统信息):fs 字段包含有关进程打开的文件的信息。它指向 fs_struct 结构,其中包含有关文件系统的信息。示例:struct fs_struct *fs;
- files(文件表):files 字段指向文件描述符表,它包含了进程打开的文件和其他 I/O 相关信息。示例:struct files_struct *files;
- signal(信号处理):signal 字段包含有关进程信号处理的信息。信号是用于与进程通信和控制其行为的机制。示例:struct signal_struct *signal;
- cred(安全凭证):cred 字段包含有关进程权限和身份的信息。这是与安全相关的关键信息。示例:struct cred __rcu *cred;
- thread_info(线程信息):thread_info 字段包含了有关线程的低级信息,如堆栈指针和处理器状态。这对于线程管理是必要的。示例:struct thread_info thread_info;
2. 分析Linux内核创建一个新进程的过程
2.1 启动MenuOS
cd LinuxKernel
rm -rf menu
git clone https://github.com/mengning/menu.git
cd menu
mv test_fork.c test.c
make rootfs
测试fork功能;
2.2 调试MenuOS
通过增加-s -S启动参数打开调试模式;
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
打开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
执行fork命令,停在了断点SyS_clone处,单步执行,定在了断点do_fork处;之后就是依次设置的断点。
3. 总结
创建一个新进程在内核中的执行过程大致如下:
-
使用系统调用Sys_clone(或fork,vfork)系统调用创建一个新进程。这些系统调用会通过调用do_fork函数来实现进程的创建。
-
Linux通过复制父进程的进程控制块(PCB)中的task_struct结构来创建一个新进程,并为新进程分配一个新的内核堆栈。
-
需要修改复制过来的进程数据,例如进程ID(pid)、进程链表等。这一步通过执行copy_process和copy_thread函数来完成。
-
设置新进程的内核栈顶,即p->thread.sp = (unsigned long) childregs。这个值表示当调度到子进程时,使用的内核栈的顶部位置。
-
设置子进程的第一条指令地址,即p->thread.ip = (unsigned long) ret_from_fork。这个地址表示当调度到子进程时,执行的第一条指令。