目录
————————————————————————————————————————————
并发处理,涉及资源的竞争和贡献,需要更好地理解多进程。
没有用户虚拟地址空间的进程的进程叫做内核线程。
每个内核线程需要创建一套栈:
1、创建 概览
1.1 INT中断进入内核
main
代码里调用fork()
这里是多进程初始化函数,0x80
指向的就是名字叫做system_call
中断处理程序,即系统调用
fork()
函数
1、把fork()
对应的系统调用号赋值给eax
寄存器
2、是一段包含INT 0x80的代码,引起中断进入内核:
3、返回值是eax
寄存器里的内容
————————————————————————————————————————————
INT 0x80
作用,1、把用户栈内容压到内核栈,2、执行中断处理程序
1、SS栈底,SP栈顶指针,压栈
2、EFLAGS表示一堆标志位,压栈
3、ret=??
处,存储用户态的返回地址,即mov res,%eax
,压栈
4、0x80
对应system_call
中断处理程序,即系统调用,接下来执行system_call
————————————————————————————————————————————
1.2 系统调用 调用_system_call
代码:
1、ds,es,fs…寄存器入栈代码
2、call sys_fork
,正式创建线程(第2节具体介绍)
3、调度? 判断当前线程状态state
,时间片counter
,是否需要调度reschedule
4、执行ret_from_sys_call
,返回用户态
————————————————————————————————————————————
1.3 返回用户态 ret_from_sys_call
具体代码:
1、各寄存器ebx,ecx等出栈,作用是恢复现场,最终执行iret
2、iret
作用是返回用户态,即返回到mov res,%eax
执行,SS,SP重新指回用户栈,且eax值为0,代表子进程
INT
指令和IRET
指令是一对
————————————————————————————————————————————
若需要调度! 执行reschedule()
函数:
1、先把ret_from_sys_call
的地址入栈,作用是保存执行完调度函数之后,退出内核态代码的地址,再_schedule()
,因为如果不压栈的话,那么调度之后无法从内核态返回用户态了
2、_schedule()
代码,先选下一个线程,再执行switch_to()
switch_to()
代码,是平台相关的,第3节具体介绍
_schedule()
代码,涉及进程调度,我会另外开一个博客介绍 to do
————————————————————————————————————————————
2、进程创建 fork
3个系统调用用来创建新进程:
(1)fork
,fork创造的子进程是父进程的完整副本,复制了父亲进程的资源,包括内存的内容task_struct内容
(2)vfork
,vfork创建的子进程与父进程共享数据段,而且由vfork()创建的子进程将先于父进程运行
(3)clone
,这个系统调用的主要用处是可供pthread库来创建线程。
第1节以fork
为例,宏展开后是sys_fork(void)
所有进程复制(创建)的fork
机制最终都调用了kernel/fork.c中的_do_fork
(一个体系结构无关的函数)
(1)复制进程描述符,copy_process()
的返回值是一个 task_struct 指针
(2)将子进程加入到调度器中,为其分配 CPU,准备执行
(3)如果是 vfork
,将父进程加入至等待队列,等待子进程完成
2.1 任务结构体复制
如下所示为copy_process()
函数源码精简版,task_struct
结构复杂也注定了复制过程的复杂性,因此此处省略了很多,仅保留了各个部分的主要调用函数
static __latent_entropy struct task_struct *copy_process(
unsigned long clone_flags,
unsigned long stack_start, unsigned long stack_size,
int __user *child_tidptr, struct pid *pid, int trace,
unsigned long tls, int node)
{
int retval;
struct task_struct *p;
......
//分配task_struct结构
p = dup_task_struct(current, node);
......
//权限处理
retval