調用fork()函數執行到了unistd.h中的宏函數syscall0
/* XXX - _foo needs to be __foo, while __NR_bar could be _NR_bar. */
/*
* Don't remove the .ifnc tests; they are an insurance against
* any hard-to-spot gcc register allocation bugs.
*/
#define _syscall0(type,name) \
type name(void) \
{ \
register long __a __asm__ ("r10"); \
register long __n_ __asm__ ("r9") = (__NR_##name); \
__asm__ __volatile__ (".ifnc %0%1,$r10$r9\n\t" \
".err\n\t" \
".endif\n\t" \
"break 13" \
: "=r" (__a) \
: "r" (__n_)); \
if (__a >= ) \
return (type) __a; \
errno = -__a; \
return (type) -1; \
}
将宏函數展開後變為
/* XXX - _foo needs to be __foo, while __NR_bar could be _NR_bar. */
/*
3 * Don't remove the .ifnc tests; they are an insurance against
4 * any hard-to-spot gcc register allocation bugs.
5 */
int fork(void)
{
register long __a __asm__ ("r10"); \
register long __n_ __asm__ ("r9") = (__NR_##name); \
__asm__ __volatile__ (".ifnc %0%1,$r10$r9\n\t" \
".err\n\t" \
".endif\n\t" \
"break 13" \
: "=r" (__a) \
: "r" (__n_)); \
if (__a >= ) \
return (type) __a; \
errno = -__a; \
return (type) -; \
}
當name=fork時,在宏中_NR##name就替換成了__NR_fork了。
int $0x80是所有系統調用函數的總入口,fork()是其中之一,”0”(_NR_fork)意思是将fork在sys_call_table[]中對應的函數編号_NR_fork也就是2傳給eax寄存器。這個編号就是sys_fork()函數在sys_call_table中的偏移值。
産生int $0x80軟體中斷,CPU從3級特權的程序跳到0特權級核心代碼中執行。中斷使CPU硬體自動将SS、ESP、EFLAGGS、CS、EIP這五個寄存器的值按照這個順序壓人父程序的核心棧,這些壓棧的資料将在後續的copy_process()函數中用來初始化程序1的任務狀态描述符TSS
CPU自動壓棧完成後,跳轉到system_call.s中的_system_call處執行,繼續将DS、ES、FS、EDX、ECX、EBX壓棧(這些壓棧仍舊是為了初始化子程序中的任務狀态描述符TSS做準備)。最終核心通過剛剛設定的eax的偏移值“2”查詢sys_call_table[],知道此次系統調用對應的函數是sys_fork()。跳轉到_sys_fork處執行。
sys_fork()
asmlinkage int sys_fork(void)
{
#ifndef CONFIG_MMU
/* fork almost works, enough to trick you into looking elsewhere:-( */
return -EINVAL;
#else
return do_fork(SIGCHLD, user_stack(__frame), __frame, , NULL, NULL);//調用do_fork函數
#endif
}
do_fork()
/*
* Ok, this is the main fork-routine.
*
* It copies the process, and if successful kick-starts
* it and waits for it to finish using the VM if required.
*/
/**
* 負責處理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 = ;
/**
* 通過查找pidmap_array位圖,為子程序配置設定新的pid參數.
*/
long pid = alloc_pidmap();
if (pid < )
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 << ) | 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 << ) | SIGTRAP);
}
} else {
free_pidmap(pid);
pid = PTR_ERR(p);
}
return pid;
}
copy_process()
// copy_process複制程序描述符.如果所有必須的資源都是可用的,該函數傳回剛建立的task_struct描述符的位址.
copy_process()的工作:
1、調用dup_task_struct()為子程序建立一個核心棧、thread_info結構和task_struct,這些值與目前程序的值相同。此時子程序和父程序的描述符是完全相同的。
p = dup_task_struct(current)—->(struct task_struct *tsk———->tsk = alloc_task_struct()從slab層配置設定了一個關于程序描述符的slab)
2、檢查并確定新建立這個子程序後,目前使用者所擁有的程序數目沒有超出給它配置設定的資源的限制。
3、子程序着手使自己與父程序差別開來,為程序的task_struct、tss做個性化設定,程序描述符内的許多成員都要被清0或設定為初始值。那些不是繼承而來的程序描述符成員,主要是統計資訊。task_struct中的大多數資料都依然未被修改。
4、為子程序建立第一個頁表,将程序0的頁表項内容賦給這個頁表。
/*
* This creates a new process as a copy of the old one,
* but does not actually start it yet.
*
* It copies the registers, and all the appropriate
* parts of the process environment (as per the clone
* flags). The actual kick-off is left to the caller.
*/
/**
* 建立程序描述符以及子程序執行所需要的所有其他資料結構
* 它的參數與do_fork相同。外加子程序的PID。
*/
static task_t *copy_process(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr,
int pid)
{
int retval;
struct task_struct *p = NULL;
/**
* 檢查clone_flags所傳标志的一緻性。
*/
/**
* 如果CLONE_NEWNS和CLONE_FS标志都被設定,傳回錯誤
*/
if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
return ERR_PTR(-EINVAL);
/*
* Thread groups must share signals as well, and detached threads
* can only be started up within the thread group.
*/
/**
* CLONE_THREAD标志被設定,并且CLONE_SIGHAND沒有設定。
* (同一線程組中的輕量級程序必須共享信号)
*/
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
return ERR_PTR(-EINVAL);
/*
* Shared signal handlers imply shared VM. By way of the above,
* thread groups also imply shared VM. Blocking this case allows
* for various simplifications in other code.
*/
/**
* CLONE_SIGHAND被設定,但是CLONE_VM沒有設定。
* (共享信号處理程式的輕量級程序也必須共享記憶體描述符)
*/
if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
return ERR_PTR(-EINVAL);
/**
* 通過調用security_task_create以及稍後調用security_task_alloc執行所有附加的安全檢查。
* LINUX2.6提供擴充安全性的鈎子函數,與傳統unix相比,它具有更加強壯的安全模型。
*/
retval = security_task_create(clone_flags);
if (retval)
goto fork_out;
retval = -ENOMEM;
/**
* 調用dup_task_struct為子程序擷取程序描述符。
*/
p = dup_task_struct(current);
if (!p)
goto fork_out;
/**
* 檢查存放在current->sigal->rlim[RLIMIT_NPROC].rlim_cur中的限制值,是否小于或者等于使用者所擁有的程序數。
* 如果是,則傳回錯誤碼。當然,有root權限除外。
* p->user表示程序的擁有者,p->user->processes表示程序擁有者目前程序數
* xie.baoyou注:此處比較是用>=而不是>
*/
retval = -EAGAIN;
if (atomic_read(&p->user->processes) >=
p->signal->rlim[RLIMIT_NPROC].rlim_cur) {
/**
* 當然,使用者有root權限就另當别論了
*/
if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) &&
p->user != &root_user)
goto bad_fork_free;
}
/**
* 遞增user結構的使用計數器
*/
atomic_inc(&p->user->__count);
/**
* 增加使用者擁有的程序計數。
*/
atomic_inc(&p->user->processes);
get_group_info(p->group_info);
/*
* If multiple threads are within copy_process(), then this check
* triggers too late. This doesn't hurt, the check is only there
* to stop root fork bombs.
*/
/**
* 檢查系統中的程序數量(nr_threads)是否超過max_threads
* max_threads的預設值是由系統記憶體容量決定的。總的原則是:所有的thread_info描述符和核心棧所占用的空間
* 不能超過實體記憶體的1/8。不過,系統管理可以通過寫/proc/sys/kernel/thread-max檔案來改變這個值。
*/
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count;
/**
* 如果新程序的執行域和可招待格式的核心函數都包含在核心中子產品中,
* 就遞增它們的使用計數器。
*/
if (!try_module_get(p->thread_info->exec_domain->module))
goto bad_fork_cleanup_count;
if (p->binfmt && !try_module_get(p->binfmt->module))
goto bad_fork_cleanup_put_domain;
/**
* 設定幾個與程序狀态相關的關鍵字段。
*/
/**
* did_exec是程序發出的execve系統調用的次數,初始為0
*/
p->did_exec = ;
/**
* 更新從父程序複制到tsk_flags字段中的一些标志。
* 首先清除PF_SUPERPRIV。該标志表示程序是否使用了某種超級使用者權限。
* 然後設定PF_FORKNOEXEC标志。它表示子程序還沒有發出execve系統調用。
*/
copy_flags(clone_flags, p);
/**
* 儲存新程序的pid值。
*/
p->pid = pid;
retval = -EFAULT;
/**
* 如果CLONE_PARENT_SETTID标志被設定,就将子程序的PID複制到參數parent_tidptr指向的使用者态變量中。
* xie.baoyou:想想我們常常調用的pid = fork()語句吧。
*/
if (clone_flags & CLONE_PARENT_SETTID)
if (put_user(p->pid, parent_tidptr))
goto bad_fork_cleanup;
p->proc_dentry = NULL;
/**
* 初始化子程序描述符中的list_head資料結構和自旋鎖。
* 并為挂起信号,定時器及時間統計表相關的幾個字段賦初值。
*/
INIT_LIST_HEAD(&p->children);
INIT_LIST_HEAD(&p->sibling);
p->vfork_done = NULL;
spin_lock_init(&p->alloc_lock);
spin_lock_init(&p->proc_lock);
clear_tsk_thread_flag(p, TIF_SIGPENDING);
init_sigpending(&p->pending);
p->it_real_value = ;
p->it_real_incr = ;
p->it_virt_value = cputime_zero;
p->it_virt_incr = cputime_zero;
p->it_prof_value = cputime_zero;
p->it_prof_incr = cputime_zero;
init_timer(&p->real_timer);
p->real_timer.data = (unsigned long) p;
p->utime = cputime_zero;
p->stime = cputime_zero;
p->rchar = ; /* I/O counter: bytes read */
p->wchar = ; /* I/O counter: bytes written */
p->syscr = ; /* I/O counter: read syscalls */
p->syscw = ; /* I/O counter: write syscalls */
acct_clear_integrals(p);
/**
* 把大核心鎖計數器初始化為-1
*/
p->lock_depth = -; /* -1 = no lock */
do_posix_clock_monotonic_gettime(&p->start_time);
p->security = NULL;
p->io_context = NULL;
p->io_wait = NULL;
p->audit_context = NULL;
#ifdef CONFIG_NUMA
p->mempolicy = mpol_copy(p->mempolicy);
if (IS_ERR(p->mempolicy)) {
retval = PTR_ERR(p->mempolicy);
p->mempolicy = NULL;
goto bad_fork_cleanup;
}
#endif
p->tgid = p->pid;
if (clone_flags & CLONE_THREAD)
p->tgid = current->tgid;
if ((retval = security_task_alloc(p)))
goto bad_fork_cleanup_policy;
if ((retval = audit_alloc(p)))
goto bad_fork_cleanup_security;
/* copy all the process information */
/**
* copy_semundo,copy_files,copy_fs,copy_sighand,copy_signal
* copy_mm,copy_keys,copy_namespace建立新的資料結構,并把父程序相應資料結構的值複制到新資料結構中。
* 除非clone_flags參數指出它們有不同的值。
*/
if ((retval = copy_semundo(clone_flags, p)))
goto bad_fork_cleanup_audit;
if ((retval = copy_files(clone_flags, p)))
goto bad_fork_cleanup_semundo;
if ((retval = copy_fs(clone_flags, p)))
goto bad_fork_cleanup_files;
if ((retval = copy_sighand(clone_flags, p)))
goto bad_fork_cleanup_fs;
if ((retval = copy_signal(clone_flags, p)))
goto bad_fork_cleanup_sighand;
if ((retval = copy_mm(clone_flags, p)))
goto bad_fork_cleanup_signal;
if ((retval = copy_keys(clone_flags, p)))
goto bad_fork_cleanup_mm;
if ((retval = copy_namespace(clone_flags, p)))
goto bad_fork_cleanup_keys;
/**
* 調用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(, clone_flags, stack_start, stack_size, p, regs);
if (retval)
goto bad_fork_cleanup_namespace;
/**
* 如果clone_flags參數的值被置為CLONE_CHILD_SETTID或CLONE_CHILD_CLEARTID
* 就把child_tidptr參數的值分别複制到set_child_tid或clear_child_tid字段。
* 這些标志說明:必須改變子程序使用者态位址空間的dhild_tidptr所指向的變量的值
* 不過實際的寫操作要稍後再執行。
*/
p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;
/*
* Clear TID on mm_release()?
*/
p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr: NULL;
/*
* Syscall tracing should be turned off in the child regardless
* of CLONE_PTRACE.
*/
/**
* 清除TIF_SYSCALL_TRACE标志。使ret_from_fork函數不會把系統調用結束的消息通知給調試程序。
* 也不應該通知給調試程序,因為子程序并沒有調用fork.
*/
clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE);
/* Our parent execution domain becomes current domain
These must match for thread signalling to apply */
p->parent_exec_id = p->self_exec_id;
/* ok, now we should be set up.. */
/**
* 用clone_flags參數低位的信号資料編碼統建始化tsk_exit_signal字段。
* 如CLONE_THREAD标志被置位,就把exit_signal字段初始化為-1。
* 這樣做是因為:當建立線程時,即使被建立的線程死亡,都不應該給領頭程序的父程序發送信号。
* 而應該是領頭程序死亡後,才向其領頭程序的父程序發送信号。
*/
p->exit_signal = (clone_flags & CLONE_THREAD) ? - : (clone_flags & CSIGNAL);
p->pdeath_signal = ;
p->exit_state = ;
/* Perform scheduler related setup */
/**
* 調用sched_fork完成對新程序排程程式資料結構的初始化。
* 該函數把新程序的狀态置為TASK_RUNNING,并把thread_info結構的preempt_count字段設定為1,
* 進而禁止搶占。
* 此外,為了保證公平排程,父子程序共享父程序的時間片。
*/
sched_fork(p);
/*
* Ok, make it visible to the rest of the system.
* We dont wake it up yet.
*/
p->group_leader = p;
INIT_LIST_HEAD(&p->ptrace_children);
INIT_LIST_HEAD(&p->ptrace_list);
/* Need tasklist lock for parent etc handling! */
write_lock_irq(&tasklist_lock);
/*
* The task hasn't been attached yet, so cpus_allowed mask cannot
* have changed. The cpus_allowed mask of the parent may have
* changed after it was copied first time, and it may then move to
* another CPU - so we re-copy it here and set the child's CPU to
* the parent's CPU. This avoids alot of nasty races.
*/
p->cpus_allowed = current->cpus_allowed;
/**
* 初始化子線程的cpu字段。
*/
set_task_cpu(p, smp_processor_id());
/*
* Check for pending SIGKILL! The new thread should not be allowed
* to slip out of an OOM kill. (or normal SIGKILL.)
*/
if (sigismember(¤t->pending.signal, SIGKILL)) {
write_unlock_irq(&tasklist_lock);
retval = -EINTR;
goto bad_fork_cleanup_namespace;
}
/* CLONE_PARENT re-uses the old parent */
/**
* 初始化表示親子關系的字段,如果CLONE_PARENT或者CLONE_THREAD被設定了
* 就用current->real_parent初始化,否則,目前程序就是初建立程序的父程序。
*/
if (clone_flags & (CLONE_PARENT|CLONE_THREAD))
p->real_parent = current->real_parent;
else
p->real_parent = current;
p->parent = p->real_parent;
if (clone_flags & CLONE_THREAD) {
spin_lock(¤t->sighand->siglock);
/*
* Important: if an exit-all has been started then
* do not create this new thread - the whole thread
* group is supposed to exit anyway.
*/
if (current->signal->flags & SIGNAL_GROUP_EXIT) {
spin_unlock(¤t->sighand->siglock);
write_unlock_irq(&tasklist_lock);
retval = -EAGAIN;
goto bad_fork_cleanup_namespace;
}
p->group_leader = current->group_leader;
if (current->signal->group_stop_count > ) {
/*
* There is an all-stop in progress for the group.
* We ourselves will stop as soon as we check signals.
* Make the new thread part of that group stop too.
*/
current->signal->group_stop_count++;
set_tsk_thread_flag(p, TIF_SIGPENDING);
}
spin_unlock(¤t->sighand->siglock);
}
/**
* 把新程序加入到程序連結清單
*/
SET_LINKS(p);
/**
* PT_PTRACED表示子程序必須被跟蹤,就把current->parent賦給tsk->parent,并将子程序插入調試程式的跟蹤連結清單中。
*/
if (unlikely(p->ptrace & PT_PTRACED))
__ptrace_link(p, current->parent);
/**
* 把新程序描述符的PID插入pidhash散清單中。
*/
attach_pid(p, PIDTYPE_PID, p->pid);
attach_pid(p, PIDTYPE_TGID, p->tgid);
/**
* 如果子程序是線程組的領頭程序(CLONE_THREAD标志被清0)
*/
if (thread_group_leader(p)) {
/**
* 将程序插入相應的散清單。
*/
attach_pid(p, PIDTYPE_PGID, process_group(p));
attach_pid(p, PIDTYPE_SID, p->signal->session);
if (p->pid)
__get_cpu_var(process_counts)++;
}
/**
* 計數
*/
nr_threads++;
total_forks++;
write_unlock_irq(&tasklist_lock);
retval = ;
fork_out:
if (retval)
return ERR_PTR(retval);
return p;
bad_fork_cleanup_namespace:
exit_namespace(p);
bad_fork_cleanup_keys:
exit_keys(p);
bad_fork_cleanup_mm:
if (p->mm)
mmput(p->mm);
bad_fork_cleanup_signal:
exit_signal(p);
bad_fork_cleanup_sighand:
exit_sighand(p);
bad_fork_cleanup_fs:
exit_fs(p); /* blocking */
bad_fork_cleanup_files:
exit_files(p); /* blocking */
bad_fork_cleanup_semundo:
exit_sem(p);
bad_fork_cleanup_audit:
audit_free(p);
bad_fork_cleanup_security:
security_task_free(p);
bad_fork_cleanup_policy:
#ifdef CONFIG_NUMA
mpol_free(p->mempolicy);
#endif
bad_fork_cleanup:
if (p->binfmt)
module_put(p->binfmt->module);
bad_fork_cleanup_put_domain:
module_put(p->thread_info->exec_domain->module);
bad_fork_cleanup_count:
put_group_info(p->group_info);
atomic_dec(&p->user->processes);
free_uid(p->user);
bad_fork_free:
free_task(p);
goto fork_out;
}
調用dup_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,);
return tsk;
}
void * kmem_cache_alloc (kmem_cache_t *cachep, int flags)
{
return __cache_alloc(cachep, flags);
}
static inline void * __cache_alloc (kmem_cache_t *cachep, int flags)
{
unsigned long save_flags;
void* objp;
struct array_cache *ac;
cache_alloc_debugcheck_before(cachep, flags);
local_irq_save(save_flags);
/**
* 首先試圖從本地高速緩存獲得一個空閑對象。
*/
ac = ac_data(cachep);
/**
* 如果本地高速緩存有空閑對象,那麼avail字段就包含最後被釋放的對象的項在本地高速緩存中的下标。
*/
if (likely(ac->avail)) {
STATS_INC_ALLOCHIT(cachep);
ac->touched = ;
/**
* 因為本地高速緩存數組正好存放在ac描述符的後面。
* 是以(void**)(ac+1)[--ac->avail]獲得空閑對象的位址,并遞減ac->avail的值。
*/
objp = ac_entry(ac)[--ac->avail];
} else {/* 本地高速緩存中沒有空閑對象。 */
STATS_INC_ALLOCMISS(cachep);
/**
* cache_alloc_refill重新填充本地高速緩存并獲得一個空閑對象。
*/
objp = cache_alloc_refill(cachep, flags);
}
local_irq_restore(save_flags);
objp = cache_alloc_debugcheck_after(cachep, flags, objp, __builtin_return_address());
return objp;
}
copy_fs()函數為子程序複制父程序的頁目錄項
static inline int copy_fs(unsigned long clone_flags, struct task_struct * tsk)
{
if (clone_flags & CLONE_FS) {
atomic_inc(¤t->fs->count);
return ;
}
tsk->fs = __copy_fs_struct(current->fs);
if (!tsk->fs)
return -ENOMEM;
return ;
}
__copy_fs_struct()
static inline struct fs_struct *__copy_fs_struct(struct fs_struct *old)
{
struct fs_struct *fs = kmem_cache_alloc(fs_cachep, GFP_KERNEL);
/* We don't need to lock fs - think why ;-) */
if (fs) {
atomic_set(&fs->count, );
rwlock_init(&fs->lock);
fs->umask = old->umask;
read_lock(&old->lock);
fs->rootmnt = mntget(old->rootmnt);
fs->root = dget(old->root);
fs->pwdmnt = mntget(old->pwdmnt);
fs->pwd = dget(old->pwd);
if (old->altroot) {
fs->altrootmnt = mntget(old->altrootmnt);
fs->altroot = dget(old->altroot);
} else {
fs->altrootmnt = NULL;
fs->altroot = NULL;
}
read_unlock(&old->lock);
}
return fs;
}
fs_struct資料結構,這個資料結構将VFS層裡面的描述頁目錄對象的結構體進行了執行個體化,這樣就可以為子程序建立一個頁目錄項,同時這個fs_strcut結構體和為子程序配置設定核心棧一樣都是通過頁高速緩存實作的:struct fs_struct *fs = kmem_cache_alloc(fs_cachep, GFP_KERNEL);
struct fs_struct {
atomic_t count;
rwlock_t lock;
int umask;
struct dentry * root, * pwd, * altroot; //struct denty 頁目錄項結構體
struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
};
copy_files()函數,為子程序複制父程序的頁表,共享父程序的檔案
/**
* 複制程序檔案描述符
*/
static int copy_files(unsigned long clone_flags, struct task_struct * tsk)
{
struct files_struct *oldf, *newf;
struct file **old_fds, **new_fds;
int open_files, size, i, error = , expand;
/*
* A background process may not have any files ...
*/
oldf = current->files;
if (!oldf)
goto out;
if (clone_flags & CLONE_FILES) {
atomic_inc(&oldf->count);
goto out;
}
/*
* Note: we may be using current for both targets (See exec.c)
* This works because we cache current->files (old) as oldf. Don't
* break this.
*/
tsk->files = NULL;
error = -ENOMEM;
newf = kmem_cache_alloc(files_cachep, SLAB_KERNEL);
if (!newf)
goto out;
atomic_set(&newf->count, );
spin_lock_init(&newf->file_lock);
newf->next_fd = ;
newf->max_fds = NR_OPEN_DEFAULT;
newf->max_fdset = __FD_SETSIZE;
newf->close_on_exec = &newf->close_on_exec_init;
newf->open_fds = &newf->open_fds_init;
newf->fd = &newf->fd_array[];
spin_lock(&oldf->file_lock);
open_files = count_open_files(oldf, oldf->max_fdset);
expand = ;
/*
* Check whether we need to allocate a larger fd array or fd set.
* Note: we're not a clone task, so the open count won't change.
*/
if (open_files > newf->max_fdset) {
newf->max_fdset = ;
expand = ;
}
if (open_files > newf->max_fds) {
newf->max_fds = ;
expand = ;
}
/* if the old fdset gets grown now, we'll only copy up to "size" fds */
if (expand) {
spin_unlock(&oldf->file_lock);
spin_lock(&newf->file_lock);
error = expand_files(newf, open_files-);
spin_unlock(&newf->file_lock);
if (error < )
goto out_release;
spin_lock(&oldf->file_lock);
}
old_fds = oldf->fd;
new_fds = newf->fd;
memcpy(newf->open_fds->fds_bits, oldf->open_fds->fds_bits, open_files/);
memcpy(newf->close_on_exec->fds_bits, oldf->close_on_exec->fds_bits, open_files/);
for (i = open_files; i != ; i--) {
struct file *f = *old_fds++;
if (f) {
get_file(f);
} else {
/*
* The fd may be claimed in the fd bitmap but not yet
* instantiated in the files array if a sibling thread
* is partway through open(). So make sure that this
* fd is available to the new process.
*/
FD_CLR(open_files - i, newf->open_fds);
}
*new_fds++ = f;
}
spin_unlock(&oldf->file_lock);
/* compute the remainder to be cleared */
size = (newf->max_fds - open_files) * sizeof(struct file *);
/* This is long word aligned thus could use a optimized version */
memset(new_fds, , size);
if (newf->max_fdset > open_files) {
int left = (newf->max_fdset-open_files)/;
int start = open_files / ( * sizeof(unsigned long));
memset(&newf->open_fds->fds_bits[start], , left);
memset(&newf->close_on_exec->fds_bits[start], , left);
}
tsk->files = newf;
error = ;
out:
return error;
out_release:
free_fdset (newf->close_on_exec, newf->max_fdset);
free_fdset (newf->open_fds, newf->max_fdset);
free_fd_array(newf->fd, newf->max_fds);
kmem_cache_free(files_cachep, newf);
goto out;
}
files_struct結構體,files_struct結構儲存了程序打開的所有檔案表資料,描述一個正被打開的檔案。
struct files_struct {
atomic_t count; //自動增量
struct fdtable *fdt;
struct fdtable fdtab;
fd_set close_on_exec_init; //執行exec時
需要關閉的檔案描述符初值集合
fd_set open_fds_init; //目前打開檔案
的檔案描述符屏蔽字
struct file * fd_array[NR_OPEN_DEFAULT];
spinlock_t file_lock; /* Protects concurrent
11 writers. Nests inside tsk->alloc_lock */
};
從上面的分析可以看出fork()的流程大概是:
(1)、p = dup_task_struct(current); 為新程序建立一個核心棧、thread_iofo和task_struct,這裡完全copy父程序的内容,是以到目前為止,父程序和子程序是沒有任何差別的。
(2)、為新程序在其記憶體上建立核心堆棧
(3)、對子程序task_struct任務結構體中部分變量進行初始化設定,檢查所有的程序數目是否已經超出了系統規定的最大程序數,如果沒有的話,那麼就開始設定程序描訴符中的初始值,從這開始,父程序和子程序就開始差別開了。
(4)、把父程序的有關資訊複制給子程序,建立共享關系
(5)、設定子程序的狀态為不可被TASK_UNINTERRUPTIBLE,進而保證這個程序現在不能被投入運作,因為還有很多的标志位、資料等沒有被設定
(6)、複制标志位(falgs成員)以及權限位(PE_SUPERPRIV)和其他的一些标志
(7)、調用get_pid()給子程序擷取一個有效的并且是唯一的程序辨別符PID
(8)、return ret_from_fork;傳回一個指向子程序的指針,開始執行