进程的描述和进程的创建
进程的描述
1、操作系统内核实现操作系统的三大管理功能:
-
进程管理
-
内存管理
-
文件系统
其中最核心的功能是进程管理。
2、对进程的描述:在操作系统原理中,通过进程控制块PCB描述进程。在Linux内核中通过一个数据结构struct task_struct来描述进程,称其为进程描述符。
3、对进程状态的描述:
-
在操作系统原理中,进程有就绪态、运行态和阻塞态3种基本状态;
-
在Linux内核中,当时用fork()系统调用来创建一个进程时,新进程的状态是TASK_RUNNING(就绪态但没有运行)。就绪态和运行态在Linux内核中都是TASK_RUNNING状态,此状态下进程是可运行的,也就是就绪态,是否在运行取决于它有没有获得CPU的控制权,即这个进程有没有在CPU中实际执行。如果在CPU中实际执行了,进程状态就是运行态;如果被内核调度出去了,在等待队列里就是就绪态。
-
对于一个正在运行的进程,调用用户态库函数exit()会陷入内核执行该内核函数do_exit(),进程会进入TASK_ZOMBIE状态,即进程的终止状态。TASK_ZOMBIE状态的进程一般叫做僵尸进程,Linux内核会在适当的时候把僵尸进程处理掉,然后释放进程描述符。
-
一个正在运行的进程在等待特定事件或资源时会进入阻塞态,阻塞态分为两种:
-
TASK_INTERRUPTIBLE
-
TASK_UNINTERRUPTIBLE
-
前者可以被信号和wake_up()唤醒,后者只能被wake_up()唤醒。
进程的创建
1、0号进程的初始化:
init_task为第一个进程(0号进程)的进程描述符结构体变量,它的初始化是通过硬编码方式固定下来的,除此之外的其它进程的初始化都是通过do_fork复制父进程的方式初始化的。
2、fork、vfork、clone这3个系统调用和kernel_thread内核函数都可以创建一个新进程,而且都是通过do_fork函数来创建进程的,只不过传递的参数不同。
3、进程的创建过程:
利用fork()系统调用来创建新进程,把当前进程的描述符等相关进程资源复制一份,从而产生一个子进程,并根据子进程的需要对复制的进程描述符做一些修改,然后把创建好的子进程放入运行队列。在进程调度时,新创建的子进程处于就绪状态有机会被调度执行。
4、进程创建过程中的重要函数或数据结构:
- do_fork():主要完成调用copy_process()复制父进程信息、获得pid、调用wake_up_new_task将子进程加入调度器队列等待获得分配CPU资源运行、通过clone_flags标志做一些辅助工作。
- copy_process():主要完成调用dup_task_struct复制当前进程(父进程)描述符task_struct、信息检查、初始化、把进程状态设置为TASK_RUNNING(就绪态)、采用写时复制技术逐一复制所有其他进程资源、调用copy_thread初始化子进程内核栈、设置子进程pid等。
- dup_task_struct():复制当前进程(父进程)描述符task_struct和copy_thread初始化子进程内核栈。
- thread_info:小型的进程描述符,占据连续的两个页框,通过task指针指向进程描述符。内核栈有高地址到低地址增长,thread_info结构有低地址到高地址增长。
- copy_thread():完成内核栈关键信息的初始化。如果创建的是内核线程,则子进程开始执行的起点是ret_from_kernel_thread;如果创建的是用户态进程,则子进程开始执行的起点是ret_from_fork。
分析Linux内核创建一个新进程的过程
1、增加fork系统调用
cd LinuxKernel
rm -rf menu
git clone https://github.com/mengning/menu.git
cd menu
make rootfs
2、启动内核
3、gdb跟踪
启动gdb,将内核加载进来,建立连接,并在sys_clone、do_fork、dup_task_struct、copy_process、copy_thread、ret_from_fork处各设置断点。
总结
进程的创建过程大致是复制进程描述符、一一复制其它进程资源(采用写时复制技术)、分配子进程的内核堆栈并对内核堆栈关键信息进行初始化。
问题是fork、vfork、clone这三种系统调用的不同,通过查阅资料发现:
- fork():子进程拷贝父进程的数据段,代码段,父子进程的执行次序不确定;
- vfork():创建的子进程与父进程共享数据段,而且子进程将先于父进程运行;
- clone():是则可以将父进程资源有选择地复制给子进程,而没有复制的数据结构则通过指针的复制让子进程共享。