在Linux中,程序和線程的差別并不大,程序的建立主要依賴于fork函數(還有vfork函數),普通線程的建立則依賴于clone函數,核心線程比較特殊,後面再講
在Linux 2.6.32版本的核心中,三個函數的實作如下(體系結構為x86):
asmlinkage int sys_fork(struct pt_regs regs)
{
return do_fork(SIGCHLD, regs.sp, ®s, 0, NULL, NULL);
}
asmlinkage int sys_vfork(struct pt_regs regs)
{
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.sp, ®s, 0, NULL, NULL);
}
asmlinkage int sys_clone(struct pt_regs regs)
{
unsigned long clone_flags;
unsigned long newsp;
int __user *parent_tidptr, *child_tidptr;
clone_flags = regs.bx;
newsp = regs.cx;
parent_tidptr = (int __user *)regs.dx;
child_tidptr = (int __user *)regs.di;
if (!newsp)
newsp = regs.sp;
return do_fork(clone_flags, newsp, ®s, 0, parent_tidptr, child_tidptr);
}
可以看到,這三個函數最後都調用了do_fork函數,幾乎唯一的差別就是clone_flags不同,是以在Linux中是否共享位址空間幾乎是程序和普通線程間本質上的唯一差別,當CLONE_VM被設定後,核心就不再需要調用allocate_mm()函數,而僅僅需要在調用copy_mm函數時将mm域指向其父程序的位址空間就可以了。
if (clone_flags & CLONE_VM) {
atomic_inc(&oldmm->mm_users);
mm = oldmm;
goto good_mm;
}
對于核心線程來說,它沒有程序位址空間(mm=NULL),是以也不存在使用者上下文(程序上下文的一部分),因為核心線程不需要通路任何使用者空間的記憶體,但是核心線程還是需要知道一些資料的,比如頁表,為了解決這個問題,Linux采取了一個比較省時省力的方法——直接使用前一個程序的位址空間。因為核心線程不通路使用者空間的記憶體,是以它們僅僅使用位址空間中和核心記憶體相關的資訊,這些資訊的含義和普通程序完全相同。
現在我們來看看核心線程的實作
struct task_struct *kthread_create(int (*threadfn)(void *data),
void *data,
const char namefmt[],
...)
{
struct kthread_create_info create;
create.threadfn = threadfn;
create.data = data;
//初始化完成量
init_completion(&create.started);
init_completion(&create.done);
spin_lock(&kthread_create_lock);
//将核心線程加入kthread_create_list連結清單(需要建立的核心線程資訊連結清單,此時并沒有真的建立線程)中
list_add_tail(&create.list, &kthread_create_list);
spin_unlock(&kthread_create_lock);
//喚醒kthreadd線程
wake_up_process(kthreadd_task);
//等待建立完成,為了防止kthreadd線程發生核心搶占,目前程序重新被排程這種情況的發生
wait_for_completion(&create.done);
if (!IS_ERR(create.result)) {
//下面的代碼就是設定線程的名字還有路徑啥的
va_list args;
va_start(args, namefmt);
vsnprintf(create.result->comm, sizeof(create.result->comm),
namefmt, args);
va_end(args);
}
return create.result;
}
上面的代碼最重要的一段就是wake_up_process(kthreadd_task);這段代碼喚醒了kthreadd核心線程,它的作用就是管理排程其它核心線程,然後kthreadd會調用kthreadd函數
int kthreadd(void *unused)
{
struct task_struct *tsk = current;
/* Setup a clean context for our children to inherit. */
//設定程序名字
set_task_comm(tsk, "kthreadd");
//清空信号
ignore_signals(tsk);
//設定優先級,為高優先級-5,保證能夠第一時間排程
set_user_nice(tsk, KTHREAD_NICE_LEVEL);
set_cpus_allowed(tsk, CPU_MASK_ALL);
current->flags |= PF_NOFREEZE;
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
/* 如果沒有核心線程需要建立,即不是通過kthread_create
*入口進來的,目前程序就應該排程
*/
if (list_empty(&kthread_create_list))
schedule();
__set_current_state(TASK_RUNNING);
spin_lock(&kthread_create_lock);
//周遊全局連結清單kthread_create_list,建立核心線程
while (!list_empty(&kthread_create_list)) {
struct kthread_create_info *create;
create = list_entry(kthread_create_list.next,
struct kthread_create_info, list);
//将核心線程移出kthread_create_list
list_del_init(&create->list);
spin_unlock(&kthread_create_lock);
//此時才是真正建立線程
create_kthread(create);
spin_lock(&kthread_create_lock);
}
spin_unlock(&kthread_create_lock);
}
return 0;
}
然後再來看create_thread函數
static void create_kthread(struct kthread_create_info *create)
{
int pid;
/* We want our own signal handler (we take no signals by default). */
//調用do_fork建立線程,所有線程具有統一的處理函數kthread
pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
if (pid < 0) {
create->result = ERR_PTR(pid);
} else {
struct sched_param param = { .sched_priority = 0 };
//等待剛建立的核心線程把自己設定為不可運作狀态
wait_for_completion(&create->started);
read_lock(&tasklist_lock);
create->result = find_task_by_pid_ns(pid, &init_pid_ns);
read_unlock(&tasklist_lock);
/*
* root may have changed our (kthreadd's) priority or CPU mask.
* The kernel thread should not inherit these properties.
*/
//設定核心線程的排程政策為CFS
sched_setscheduler(create->result, SCHED_NORMAL, ¶m);
set_user_nice(create->result, KTHREAD_NICE_LEVEL);
set_cpus_allowed(create->result, CPU_MASK_ALL);
}
//喚醒調用kthread_create函數的程序
complete(&create->done);
}
kthread函數如下
static int kthread(void *_create)
{
struct kthread_create_info *create = _create;
int (*threadfn)(void *data);
void *data;
int ret = -EINTR;
/* Copy data: it's on kthread's stack */
threadfn = create->threadfn;
data = create->data;
/* OK, tell user we're spawned, wait for stop or wakeup */
__set_current_state(TASK_UNINTERRUPTIBLE);
complete(&create->started);
schedule();
if (!kthread_should_stop())
ret = threadfn(data);
/* It might have exited on its own, w/o kthread_stop. Check. */
if (kthread_should_stop()) {
kthread_stop_info.err = ret;
complete(&kthread_stop_info.done);
}
return 0;
}
到這就說完了,其實在linux中do_fork是建立程序和線程的唯一路口