前言
工具:source insight
Linux源码版本:2.6.11(最新版已经5.几了,根据自己需求选择)
文章目录
在 entry.s中找到系统调用表
转到定义,跳转到Process.c(i386)
可以看出,fork、vfork、clone都是调用do_fork的。
sys_fork:
asmlinkage int sys_fork(struct pt_regs regs)
{
return do_fork(SIGCHLD, regs.esp, ®s, 0, NULL, NULL);
}
参数:可以看出是传进来一堆寄存器。简单理解就是线程的一些信息
struct pt_regs {
long ebx;
long ecx;
long edx;
long esi;
long edi;
long ebp;
long eax;
int xds;
int xes;
long orig_eax;
long eip;
int xcs;
long eflags;
long esp;
int xss;
};
do_fork()
/**
* 负责处理clone,fork,vfork系统调用。
* clone_flags-与clone的flag参数相同
* stack_start-与clone的child_stack相同
* regs-指向通用寄存器的值。是在从用户态切换到内核态时被保存到内核态堆栈中的。
* stack_size-未使用,总是为0
* parent_tidptr,child_tidptr-clone中对应参数ptid,ctid相同
*/
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)
{
struct task_struct *p;
int trace = 0;
/**
* 通过查找pidmap_array位图,为子进程分配新的pid参数.
*/
long pid = alloc_pidmap();
if (pid < 0)
return -EAGAIN;
/**
* 如果父进程正在被跟踪,就检查debugger程序是否想跟踪子进程.并且子进程不是内核进程(CLONE_UNTRACED未设置)
* 那么就设置CLONE_PTRACE标志.
*/
if (unlikely(current->ptrace)) {
trace = fork_traceflag (clone_flags);
if (trace)
clone_flags |= CLONE_PTRACE;
}
/**
* copy_process复制进程描述符.如果所有必须的资源都是可用的,该函数返回刚创建的task_struct描述符的地址.
* 这是创建进程的关键步骤.
*/
p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid);
/*
* Do this prior waking up the new thread - the thread pointer
* might get invalid after that point, if the thread exits quickly.
*/
if (!IS_ERR(p)) {
struct completion vfork;
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
}
/**
* 如果设置了CLONE_STOPPED,或者必须跟踪子进程.
* 就设置子进程为TASK_STOPPED状态,并发送SIGSTOP信号挂起它.
*/
if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {
/*
* We'll start up with an immediate SIGSTOP.
*/
sigaddset(&p->pending.signal, SIGSTOP);
set_tsk_thread_flag(p, TIF_SIGPENDING);
}
/**
* 没有设置CLONE_STOPPED,就调用wake_up_new_task
* 它调整父进程和子进程的调度参数.
* 如果父子进程运行在同一个CPU上,并且不能共享同一组页表(CLONE_VM标志被清0).那么,就把子进程插入父进程运行队列.
* 并且子进程插在父进程之前.这样做的目的是:如果子进程在创建之后执行新程序,就可以避免写时复制机制执行不必要时页面复制.
* 否则,如果运行在不同的CPU上,或者父子进程共享同一组页表.就把子进程插入父进程运行队列的队尾.
*/
if (!(clone_flags & CLONE_STOPPED))
wake_up_new_task(p, clone_flags);
else/*如果CLONE_STOPPED标志被设置,就把子进程设置为TASK_STOPPED状态。*/
p->state = TASK_STOPPED;
/**
* 如果进程正被跟踪,则把子进程的PID插入到父进程的ptrace_message,并调用ptrace_notify
* ptrace_notify使当前进程停止运行,并向当前进程的父进程发送SIGCHLD信号.子进程的祖父进程是跟踪父进程的debugger进程.
* dubugger进程可以通过ptrace_message获得被创建子进程的PID.
*/
if (unlikely (trace)) {
current->ptrace_message = pid;
ptrace_notify ((trace << 8) | SIGTRAP);
}
/**
* 如果设置了CLONE_VFORK,就把父进程插入等待队列,并挂起父进程直到子进程结束或者执行了新的程序.
*/
if (clone_flags & CLONE_VFORK) {
wait_for_completion(&vfork);
if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE))
ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP);
}
} else {
free_pidmap(pid);
pid = PTR_ERR(p);
}
return pid;
}
task_struct首先关注四个东西:
- 进程状态
- 指向内存信息的mm_struct
- 进程pid
- 退出码exit_code
分配PID:
由于是2.6版本的内核,所以这里的pid最大值为32768,如果超过了这个最大值,会分配值为300的pid。
int alloc_pidmap(void)
{
int i, offset, max_scan, pid, last = last_pid;
pidmap_t *map;
pid = last + 1;
if (pid >= pid_max) // 32768
pid = RESERVED_PIDS; // 300
offset = pid & BITS_PER_PAGE_MASK;
map = &pidmap_array[pid/BITS_PER_PAGE];
max_scan = (pid_max + BITS_PER_PAGE - 1)/BITS_PER_PAGE - !offset;
for (i = 0; i <= max_scan; ++i) {
if (unlikely(!map->page)) {
unsigned long page = get_zeroed_page(GFP_KERNEL);
/*
* Free the page if someone raced with us
* installing it:
*/
spin_lock(&pidmap_lock);
if (map->page)
free_page(page);
else
map->page = (void *)page;
spin_unlock(&pidmap_lock);
if (unlikely(!map->page))
break;
}
if (likely(atomic_read(&map->nr_free))) {
do {
if (!test_and_set_bit(offset, map->page)) {
atomic_dec(&map->nr_free);
last_pid = pid;
return pid;
}
offset = find_next_offset(map, offset);
pid = mk_pid(map, offset);
/*
* find_next_offset() found a bit, the pid from it
* is in-bounds, and if we fell back to the last
* bitmap block and the final block was the same
* as the starting point, pid is before last_pid.
*/
} while (offset < BITS_PER_PAGE && pid < pid_max &&
(i != max_scan || pid < last ||
!((last+1) & BITS_PER_PAGE_MASK)));
}
if (map < &pidmap_array[(pid_max-1)/BITS_PER_PAGE]) {
++map;
offset = 0;
} else {
map = &pidmap_array[0];
offset = RESERVED_PIDS;
if (unlikely(last == offset))
break;
}
pid = mk_pid(map, offset);
}
return -1;
}
分配完pid,接下来重要的步骤是copy_process()
重要的参数有
regs:通用的寄存器值(现场信息)
pid:进程id
指针变量p可能就是要返回的指针。
接下来一个·重要的函数是dup_task_struct():
current为当前进程的task_struct,传进去的目的就是创建子进程,复制当前进程信息。
/**
* 为子进程获取进程描述符。
*/
static struct task_struct *dup_task_struct(struct task_struct *orig)
{
struct task_struct *tsk;
struct thread_info *ti;
/**
* prepare_to_copy中会调用unlazy_fpu。
* 它把FPU、MMX和SSE/SSE2寄存器的内容保存到父进程的thread_info结构中。
* 稍后,dup_task_struct将把这些值复制到子进程的thread_info中。
*/
prepare_to_copy(orig);
/**
* alloc_task_struct宏为新进程获取进程描述符,并将描述符保存到tsk局部变量中。
*/
tsk = alloc_task_struct();
if (!tsk)
return NULL;
/**
* alloc_thread_info宏获取一块空闲内存区,用来存放新进程的thread_info结构和内核栈。
* 这块内存区字段的大小是8KB或者4KB。
*/
ti = alloc_thread_info(tsk);
if (!ti) {
free_task_struct(tsk);
return NULL;
}
/**
* 将current进程描述符的内容复制到tsk所指向的task_struct结构中,然后把tsk_thread_info置为ti
* 将current进程的thread_info内容复制给ti指向的结构中,并将ti_task置为tsk.
*/
*ti = *orig->thread_info;
*tsk = *orig;
tsk->thread_info = ti;
ti->task = tsk;
/* One for us, one for whoever does the "release_task()" (usually parent) */
/**
* 把新进程描述符的使用计数器usage设置为2,用来表示描述符正在被使用而且其相应的进程处于活动状态。
* 进程状态既不是EXIT_ZOMBIE,也不是EXIT_DEAD
*/
atomic_set(&tsk->usage,2);
return tsk;
}
查看alloc_task_struct()
/**
* 为新进程获取进程描述符
*/
#define alloc_task_struct() kmem_cache_alloc(task_struct_cachep, GFP_KERNEL)
#define free_task_struct(tsk) kmem_cache_free(task_struct_cachep, (tsk))
从高速缓存中申请PCB
void * kmem_cache_alloc (kmem_cache_t *cachep, int flags)
{
return __cache_alloc(cachep, flags);
}
alloc_thread_info()申请一块用来存放新进程thread_info结构和内核栈的内存区。
再次回到copy_process继续看,这段代码是做了一些限制检查,用户默认运行最大进程数等。
示例,该用户被允许运行7549个进程
copy_file()就是把父进程的文件表拷贝到子进程,所以说父进程打开的文件,子进程也可以访问。
copy_mm()为复制进程实体、内存空间。
/**
* 当创建一个新的进程时,内核调用copy_mm函数,
* 这个函数通过建立新进程的所有页表和内存描述符来创建进程的地址空间。
* 通常,每个进程都有自己的地址空间,但是轻量级进程共享同一地址空间,即允许它们对同一组页进行寻址。
*/
static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
{
struct mm_struct * mm, *oldmm;
int retval;
tsk->min_flt = tsk->maj_flt = 0;
tsk->nvcsw = tsk->nivcsw = 0;
tsk->mm = NULL;
tsk->active_mm = NULL;
/*
* Are we cloning a kernel thread?
*
* We need to steal a active VM for that..
*/
oldmm = current->mm;
/**
* 内核线程??
*/
if (!oldmm)
return 0;
/**
* 指定了CLONE_VM标志,表示创建线程。
*/
if (clone_flags & CLONE_VM) {
/**
* 新线程共享父进程的地址空间,所以需要将mm_users加一。
*/
atomic_inc(&oldmm->mm_users);
mm = oldmm;
/*
* There are cases where the PTL is held to ensure no
* new threads start up in user mode using an mm, which
* allows optimizing out ipis; the tlb_gather_mmu code
* is an example.
*/
/**
* 如果其他CPU持有进程页表自旋锁,就通过spin_unlock_wait保证在释放锁前,缺页处理程序不会结果。
* 实际上,这个锁除了保护页表,还必须禁止创建新的轻量级进程。因为它们共享mm描述符
*/
spin_unlock_wait(&oldmm->page_table_lock);
/**
* 在good_mm中,将父进程的地址空间赋给子进程。
* 注意前面对mm的赋值,表示了新线程使用的mm
* 完了,就这么简单
*/
goto good_mm;
}
/**
* 没有CLONE_VM标志,就必须创建一个新的地址空间。
* 必须要有地址空间,即使此时并没有分配内存。
*/
retval = -ENOMEM;
/**
* 分配一个新的内存描述符。把它的地址存放在新进程的mm中。
*/
mm = allocate_mm();
if (!mm)
goto fail_nomem;
/* Copy the current MM stuff.. */
/**
* 并从当前进程复制mm的内容。
*/
memcpy(mm, oldmm, sizeof(*mm));
if (!mm_init(mm))
goto fail_nomem;
/**
* 调用依赖于体系结构的init_new_context。
* 对于80X86来说,该函数检查当前进程是否有定制的局部描述符表。
* 如果有,就复制一份局部描述符表并把它插入tsk的地址空间
*/
if (init_new_context(tsk,mm))
goto fail_nocontext;
/**
* dup_mmap不但复制了线程区和页表,也设置了mm的一些属性.
* 它也会改变父进程的私有,可写的页为只读的,以使写时复制机制生效。
*/
retval = dup_mmap(mm, oldmm);
if (retval)
goto free_pt;
mm->hiwater_rss = mm->rss;
mm->hiwater_vm = mm->total_vm;
good_mm:
tsk->mm = mm;
tsk->active_mm = mm;
return 0;
free_pt:
mmput(mm);
fail_nomem:
return retval;
fail_nocontext:
/*
* If init_new_context() failed, we cannot use mmput() to free the mm
* because it calls destroy_context()
*/
mm_free_pgd(mm);
free_mm(mm);
return retval;
}
/**
* 指向线性区对象的链表头。
*/
struct vm_area_struct * mmap;
mm_struct需要关注的:
/**
* 既复制父进程的线性区,也复制它的页表。
*/
static inline int dup_mmap(struct mm_struct * mm, struct mm_struct * oldmm)
{}
然后复制线程
/**
* 调用copy_thread,用发出clone系统调用时CPU寄存器的值(它们保存在父进程的内核栈中)
* 来初始化子进程的内核栈。不过,copy_thread把eax寄存器对应字段的值(这是fork和clone系统调用在子进程中的返回值)
* 强行置为0。子进程描述符的thread.esp字段初始化为子进程内核栈的基地址。ret_from_fork的地址存放在thread.eip中。
* 如果父进程使用IO权限位图。则子进程获取该位图的一个拷贝。
* 最后,如果CLONE_SETTLS标志被置位,则子进程获取由CLONE系统调用的参数tls指向的用户态数据结构所表示的TLS段。
*/
retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs)
int copy_thread(int nr, unsigned long clone_flags, unsigned long esp,
unsigned long unused,
struct task_struct * p, struct pt_regs * regs)
{
struct pt_regs * childregs;
struct task_struct *tsk;
int err;
childregs = ((struct pt_regs *) (THREAD_SIZE + (unsigned long) p->thread_info)) - 1;
*childregs = *regs;
childregs->eax = 0;
childregs->esp = esp;
p->thread.esp = (unsigned long) childregs;
p->thread.esp0 = (unsigned long) (childregs+1);
p->thread.eip = (unsigned long) ret_from_fork;
savesegment(fs,p->thread.fs);
savesegment(gs,p->thread.gs);
tsk = current;
if (unlikely(NULL != tsk->thread.io_bitmap_ptr)) {
p->thread.io_bitmap_ptr = kmalloc(IO_BITMAP_BYTES, GFP_KERNEL);
if (!p->thread.io_bitmap_ptr) {
p->thread.io_bitmap_max = 0;
return -ENOMEM;
}
memcpy(p->thread.io_bitmap_ptr, tsk->thread.io_bitmap_ptr,
IO_BITMAP_BYTES);
}
/*
* Set a new TLS for the child thread?
*/
if (clone_flags & CLONE_SETTLS) {
struct desc_struct *desc;
struct user_desc info;
int idx;
err = -EFAULT;
if (copy_from_user(&info, (void __user *)childregs->esi, sizeof(info)))
goto out;
err = -EINVAL;
if (LDT_empty(&info))
goto out;
idx = info.entry_number;
if (idx < GDT_ENTRY_TLS_MIN || idx > GDT_ENTRY_TLS_MAX)
goto out;
desc = p->thread.tls_array + idx - GDT_ENTRY_TLS_MIN;
desc->a = LDT_entry_a(&info);
desc->b = LDT_entry_b(&info);
}
err = 0;
out:
if (err && p->thread.io_bitmap_ptr) {
kfree(p->thread.io_bitmap_ptr);
p->thread.io_bitmap_max = 0;
}
return err;
}