linux進程解析--進程的創建


     通常我們在代碼中調用fork()來創建一個進程或者調用pthread_create()來創建一個線程,創建一個進程需要為其分配內存資源,文件資源,時間片資源等,在這里來描述一下linux進程的創建過程及寫時復制技術。

一寫時復制

    子進程和父進程通常擁有着不同的進程內存空間(線程除外),傳統的unix在創建子進程后,會復制父進程的地址空間的所有內容,這就十分的低效,因為經常子進程會立即執行exec操作,創建一個嶄新的內存空間,另外像進程代碼段這樣的內存,父子進程只是讀,而沒有寫操作,完全可以共享,而不用去復制,這樣會節省大量的時間。
寫時復制機制就是在這個背景下產生的,子進程創建后,不會去復制所有的父進程的內存空間物理內存,通常只復制下頁全局目錄,並把所有父進程的物理頁設置為寫保護,這樣當父子進程中有一個對物理頁進行寫時,就會觸發寫保護異常,就復制一下對應的物理頁,加入到對應的頁表中即可。
二clone(), fork(),vfork()
    fork(),vfork()系統調用都是通過clone()函數來實現的,clone()函數介紹如下:
函數原型:
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);


    這里fn是函數指針,我們知道進程的4要素,這個就是指向程序的指針,就是所謂的“劇本", child_stack明顯是為子進程分配用戶態堆棧空間,flags就是標志用來描述你需要從父進程繼承那些資源, arg就是傳給子進程的參數)。下面是flags可以取的值:


 標志                   含義


 CLONE_PARENT  創建的子進程的父進程是調用者的父進程,新進程與創建它的進程成了“兄弟”而不是“父子”
 CLONE_FS          子進程與父進程共享相同的文件系統,包括root、當前目錄、umask
 CLONE_FILES     子進程與父進程共享相同的文件描述符(file descriptor)表
 CLONE_NEWNS  在新的namespace啟動子進程,namespace描述了進程的文件hierarchy
 CLONE_SIGHAND  子進程與父進程共享相同的信號處理(signal handler)表
 CLONE_PTRACE  若父進程被trace,子進程也被trace
 CLONE_VFORK    父進程被掛起,直至子進程釋放虛擬內存資源
 CLONE_VM          子進程與父進程運行於相同的內存空間
 CLONE_PID         子進程在創建時PID與父進程一致
 CLONE_THREAD   Linux 2.4中增加以支持POSIX線程標准,子進程與父進程共享相同的線程群


下面的例子是創建一個線程(子進程共享了父進程虛存空間,沒有自己獨立的虛存空間不能稱其為進程)。父進程被掛起當子線程釋放虛存資源后再繼續執行。


實現clone()系統調用的服務例程是sys_clone(),sys_clone()例程並沒有fn和arg參數,clone()函數會把fn放在子進程堆棧的某個位置,該位置就是封裝函數本身返回地址的存放位置,arg指針放在fn堆棧的下面,當封裝函數結束時,cpu取出fn,執行fn(arg).
與系統調用clone功能相似的系統調用有fork,但fork事實上只是clone的功能的一部分,clone與fork的主要區別在於傳遞了幾個參數,而當中最重要的參數就是conle_flags,下表是系統定義的幾個clone_flags標志,同時child_stack傳遞的也是父進程的用戶態堆棧,由於寫時復制,會在父子進程對堆棧進行操作時進行復制。
標志 Value 含義
CLONE_VM 0x00000100 置起此標志在進程間共享地址空間
CLONE_FS 0x00000200 置起此標志在進程間共享文件系統信息
CLONE_FILES 0x00000400 置起此標志在進程間共享打開的文件
CLONE_SIGHAND 0x00000800 置起此標志在進程間共享信號處理程序


三sys_clone()服務例程源碼解析

fork(),vfork(),clone()三個系統調用最后都是使用sys_clone()服務例程來完成了系統調用,sys_clone()服務例程會去調用do_fork()函數,主要的處理流程就在do_fork()中。

3.1do_fork()

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();
//查看當前進程是否被trace,設置新進程的trace狀態,只要進程不是內核線程就應該被trace
if (unlikely(current->ptrace)) {
trace = fork_traceflag (clone_flags);
if (trace)
clone_flags |= CLONE_PTRACE;
}
//將父進程的相關信息,如地址空間,信號等信息復制到新建進程描述符上面
p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid);
if (!IS_ERR(p)) {
struct completion vfork;
//如果該進程是vfork出來的,需要等待子進程結束或退出后再執行父進程
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
}
//如果該進程被追蹤,或者設置了clone_stopped標記,給該進程發送STOP信號,設置了CLONE_STOPPED標記的話,進程不能夠立即執行,需要先stop下來,后面通過
向該進程發送SIG_CONT信號使其恢復執行
if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {
sigaddset(&p->pending.signal, SIGSTOP);
set_tsk_thread_flag(p, TIF_SIGPENDING);
}
//未設置clone_stopped標記,喚醒新進程,此后新進程可以參與調度
if (!(clone_flags & CLONE_STOPPED))
wake_up_new_task(p, clone_flags);
else
p->state = TASK_STOPPED;
++total_forks;
//需要trace的話,向debug進程發送一個trace消息
if (unlikely (trace)) {
current->ptrace_message = pid;
ptrace_notify ((trace << 8) | SIGTRAP);
}
//如果該進程是被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;
}

3.2copy_process()

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;

if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
return ERR_PTR(-EINVAL);

//clone出一個線程,線程必須共享信號處理函數等
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
return ERR_PTR(-EINVAL);

//共享信號處理,但不共享地址空間,clone_sighand表示子進程和父進程共享信號處理函數表,共享阻塞信號表以及掛起信號表
if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
return ERR_PTR(-EINVAL);

if (retval)
goto fork_out;
retval = -ENOMEM;
//復制當前進程進程描述符 ,實質的工作就是把當前進程的task_struct, thread_info都復制到了子進程里面,在該函數里面也會分配進程描述符
p = dup_task_struct(current);
if (!p)
goto fork_out;
retval = -EAGAIN;
//檢查是否進程數符合進程資源限制這個時候p->user其實和父進程中的p->user是一樣的,因為在dup_task_struct里面對父進程的描述符進行了復制
if (atomic_read(&p->user->processes) >=
p->signal->rlim[RLIMIT_NPROC].rlim_cur) {
if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) &&
p->user != &root_user)
goto bad_fork_free;
}
//增加統計信息和進程所屬用戶的引用計數
atomic_inc(&p->user->__count);
atomic_inc(&p->user->processes);

//檢查線程數是否超出了限制
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count;
//新進程尚未調用exec
p->did_exec = 0;
//設置新進程的flag,主要是去除PF_SUPERPRIV,設置PF_FORKNOEXEC,根據clone_falgs設置task的trace字段
copy_flags(clone_flags, p);
p->pid = pid;
retval = -EFAULT;
if (clone_flags & CLONE_PARENT_SETTID)
if (put_user(p->pid, parent_tidptr))
goto bad_fork_cleanup;

p->proc_dentry = NULL;
//初始化進程的子進程鏈表和掛入兄弟鏈表的節點
INIT_LIST_HEAD(&p->children);
INIT_LIST_HEAD(&p->sibling);
//初始化等待隊列,在系統調用vfork()中會使用到
init_waitqueue_head(&p->wait_chldexit);
p->vfork_done = NULL;
spin_lock_init(&p->alloc_lock);
spin_lock_init(&p->proc_lock);

//為新進程清除掉有信號掛起的標記位,表明該進程現在沒有信號尚未處理
clear_tsk_thread_flag(p, TIF_SIGPENDING);
//清除掉新進程的掛起信號表,初始化相應的信號隊列,從這里的代碼看來,父進程未處理完畢的信號不會遺傳給子進程,oh,yeah!
init_sigpending(&p->pending);
: //跳過了很多代碼,這些代碼我們不關注,不影響對主體流程的理解


//設置新進程的tgid,若該新進程是一個輕量級進程即線程的話,設置其tgid為父進程的tgid,否則其tgid和其pid是一樣的
p->tgid = p->pid;
if (clone_flags & CLONE_THREAD)
p->tgid = current->tgid;

//下面主要是根據clone_flags的標志來決定子進程是否來共用內存描述符,文件描述符等,信號處理函數,信號及阻塞碼等,copy_thread()則會為新的子進程內核棧復制父進程內核棧的寄存器信息,設置進程描述符中thread中的相關寄存器信息,為后續的進程切換做好准備,3.3介紹了copy_thread()函數。
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;
retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);
if (retval)
goto bad_fork_cleanup_namespace;

//清除掉p->thread的TIF_SYSCALL_TRACE的標志位,以使ret_from_fork不會把系統調用結束的消息發送給trace進程
clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE);
//設置exit signal,對於線程組來說,只有線程組的最后一個線程退出,整個線程組才退出,並向線程組的父進程發送child信號,exit_signal其實主要是用來表明該進程是否是線程組首進程:exit_signal!=-1
p->exit_signal = (clone_flags & CLONE_THREAD) ? -1 : (clone_flags & CSIGNAL);
p->pdeath_signal = 0;
p->exit_state = 0;

//給新進程分配時間片,分配的額度是父進程的一半,同時禁止內核搶占
sched_fork(p);

//設置進程的組長進程就是自己,這里的組長進程是指線程組的組長進程,在后面的處理中會把對輕量級進程的組長進程設為真正的線程組組長進程
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);

//子進程繼承父進程的cpus_allowed字段,cpus_allowed字段指明進程可以在哪些cpu上運行
p->cpus_allowed = current->cpus_allowed;
//設置子進程的thread->cpu,當前正在執行sys_clone()服務例程的cpu
set_task_cpu(p, smp_processor_id());
if (sigismember(¤t->pending.signal, SIGKILL)) {
write_unlock_irq(&tasklist_lock);
retval = -EINTR;
goto bad_fork_cleanup_namespace;
}

//對於輕量級線程而言,新建子進程的parent,real_parent進程是父進程的父進程,即線程的父進程是fork()出線程組組長進程的進程
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);
//對於線程而言,其線程組長進程就是其父進程的組長進程
p->group_leader = current->group_leader;
spin_unlock(¤t->sighand->siglock);
}
//把新進程插入到進程列表中,是組長進程,才插入到全局的進程鏈表,即以init_task為進程鏈表頭的鏈表,但還是會把該進程放入到PIDTYPE的hash表中
SET_LINKS(p);
//如果進程必須被追蹤,將該進程放入current的父進程即調試進程的跟蹤列表中,同時子進程的父進程也被設置為調試進程
if (unlikely(p->ptrace & PT_PTRACED))
__ptrace_link(p, current->parent);
//將進程插入到PIDTYPE_PID的hash表中
attach_pid(p, PIDTYPE_PID, p->pid);
//將進程插入到PIDTYPE_TGID的hash標中
attach_pid(p, PIDTYPE_TGID, p->tgid);
//該進程為線程組組長進程
if (thread_group_leader(p)) {
//將該進程插入到PIDTYPE_PGID hash表中
attach_pid(p, PIDTYPE_PGID, process_group(p));
//將該進程插入到PIDTYPE_SID hash表中
attach_pid(p, PIDTYPE_SID, p->signal->session);
if (p->pid)
__get_cpu_var(process_counts)++;
}

nr_threads++;
write_unlock_irq(&tasklist_lock);
retval = 0;


}

3.3copy_thread

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;
//將子進程的eax寄存器值設為0,eax表示返回值
childregs->eax = 0;
//childregs->esp中存放的是子進程用戶態的棧地址
childregs->esp = esp;

//thread.esp存放的是內核態的棧頂地址
p->thread.esp = (unsigned long) childregs;
p->thread.esp0 = (unsigned long) (childregs+1);
//thread.eip存放的是內核態的返回地址
p->thread.eip = (unsigned long) ret_from_fork;

savesegment(fs,p->thread.fs);
savesegment(gs,p->thread.gs);

tsk = current;
//copy父進程的io權限位圖
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);
}
:
:
return err;
}

四do_fork()之后發生了什么
現在我們有了完整的可以運行的進程,但還有對其進行調度,在以后的進程切換時,會對其進行完善,將子進程描述符中thread字段的值放入幾個寄存器中,主要是將thread.esp放入esp寄存器中,把ret_from_fork()函數的地址放入到eip寄存器中,(參看上面的copy_thread函數)然后進程切換后會去執行ret_from_fork()函數,ret_form_fork()回去調用schedule_tail()函數,用存放在內核棧中的值裝載所有的寄存器,並強迫cpu返回用戶態。系統調用的返回值存放在了eax中,返回給子進程的是0,父進程的是子進程的id號。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com