应用程序在用户态执行时可能会请求系统调用(类似于中断,进行调用前需要保护现场,完成后需要恢复现场),比如读写磁盘的I/O操作,这时就需要int 0x80(十进制128)指令。每个系统调用具有固定的系统调用号(即使这个系统调用失效,这个系统 系统调用号也不会给别人)。
Int0x80之后就进入到内核代码了,在内核中首先执行到system_call的位置,pushl %eax的意思是把eax寄存器的值压入程序内核态堆栈,eax中存放着系统调用号。用户态程序传递给系统调用的参数存放 在ebx、ecx、edx等等寄存器中,而ebx、ecx、edx等寄存器的值通过SAVE_ALL宏压入内核堆栈。
下面为fork系统调用的过程:
在用户态fork系统调用,产生0x80号中断,在进入中断处理程序之前 保护现场,将它的系统调用号存入eax寄存器,通过寄存器传给内核,进入内核空间,通过系统调用号在系统调用表中找到相应的系统调用sys_fork,
int sys_fork(struct pt_regs regs)
{
return do_fork(SIGCHLD, regs.esp, ®s, 0, NULL, NULL);
}
//struct pt_regs中前9个是存放通过SAVE_ALL压入的寄存器值。 orig_eax存放的是 中断系统调用号. 后面的几个是处理器自动压入的寄存器值。
long do_fork(unsigned long clone_flags,unsigned long stack_start,struct
pt_regs *regs, unsigned long stack_size,int __user *parent_tidptr,
int __user *child_tidptr)
1.先申请一个task_struct(进程控制块的描述符)的结构体,表示即将生成新的进程,调用alloc_pidmap,为
新的子进程分配pid。如果pid<0,reutrn -EAGAIN(EAGIN表示危险已经过去)。如果父进程正在百跟踪,就检查debugger
程序是否想跟踪子进程(fork_traceflag),并且子进程不是内核进程(即CLONE_UNTRACE未被设置),那么就
设置CLONE_PTRACE。
alloc_pidmap的具体实现过程:
RESERVED_PIDS = 300,前300pid是固定的,不可以分配。
offset为pid在页框中的偏移量,因为pid可能大于一个页框所能容纳的最大位数,即BIRTS_PER_PAGE,所以
pid要对BITS_PER_PAGE取模,(offset = pid&BITS_PER_PAGE)。
pid/BITS_PER_PAGE求得页面在位图中的下标。
max_scan = (pid_max _BITS_PER_PAGE -1)/ BITS_PER_PAGE - !offset;
//max_scan位最大的扫描次数,如果offset为0,则扫描一次,否则两次。
for循环开始扫描,如果页面不存在则分配页面
如果该页框位数大于0,如果该offset空闲,则返回该值。
否则继续扫描该页框其余位。
然后根据下标的offset重新算出pid。
如果页框分配失败,则继续扫描剩余的页框。
2.调用copy_process复制进程描述符,具体实现为:
p = dup_task_struct(current),为先进城创建一个内核栈、thread_info和task_struct,这里完全拷贝父进程的
内容,所以到目前为止,父进程与子进程是完全没有区别的。
检查所有的进程数目是否已经超出了系统规定的最大进程数,如果没有的话,那么就开始设置进程描述符
中的初始值,从这开始,父进程和子进程就开始区别开了。
设置子进程的状态为不可被TASK_UNINTERRUPTIBLE,从而保证这个进程现在不能被投入运行,因为还有
很多的标志位、数据等没有被设置。
复制标志位(flags成员)以及权限位(PE_SUPERPRIV)和其他的一些标志位。
调用get_pid给子进程获取一个有效的并且是唯一的进程表示服PID。
根据传入 cloning flags对相应的内容进行copy,比如说打开的文件符号、信号等。
父子进程均分父进程剩余的时间片。
return p;返回一个指向子进程的指针。
3.如果设置了CLONE_STOPPED,或者必须跟踪子进程,就设置子进程为TASJ_STOPPED状态,并发送SIGSTOP
信号挂起它。
4.没有设置CLONE_STOPPED,就调用wake_up_new_task它调整父进程和子进程的调度参数.
如果父子进程运行在同一个CPU上,并且不能共享同一组页表(CLONE_VM标志被清0).那么,就把子进程插入父进程
运行队列.并且子进程插在父进程之前.这样做的目的是:如果子进程在创建之后执行新程序,就可以避免写时复制机制
执行不必要时页面复制.否则,如果运行在不同的CPU上,或者父子进程共享同一组页表.就把子进程插入父进程运行队列
的队尾。
5.如果进程正被跟踪,则把子进程的PID插入到父进程的ptrace_message,并调用ptrace_notify
ptrace_notify使当前进程停止运行,并向当前进程的父进程发送SIGCHLD信号.子进程的祖父进程是跟踪父进程的
debugger进程.dubugger进程可以通过ptrace_message获得被创建子进程的PID。
6.如果设置了LCONE_CFORK,就把父进程插入等待队列,并挂起父进程直到子进程结束或者执行了新的程序。