深刻理解Linux之進程初探

一. 關於fork調用

  fork()調用建立一個新的進程,該進程幾乎是當前進程的一個徹底拷貝。由fork()建立的新進程被稱爲子進程。fork函數被調用一次但返回兩次。兩次返回的惟一區別是子進程中返回0值,而父進程中返回子進程ID。子進程是父進程的副本,它將得到父進程數據空間、堆、棧等資源的副本。注意,子進程持有的是上述存儲空間的「副本」,這意味着父子進程間不共享這些存儲空間。Linux將複製父進程的地址空間內容給子進程,所以,子進程擁有獨立的地址空間。node

  咱們來看一個DEMO:linux

// fork_example.c#include <memory.h>#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>int main(int argc, const char *argv[])
{
    pid_t pid;    char stack_data[] = "stack_data";char *heap_data = malloc(10 * sizeof(char));    
    strcpy(heap_data, "heap_data");
    
    pid = fork();if (pid == 0) {
        printf("CHILD PROCESS: %s, %s\n", stack_data, heap_data);
    } else if (pid > 0) {
        printf("PARENT PROCESS: %s, %s\n", stack_data, heap_data);
    } else {
        printf("FORK FAILED.");
    }return 0;
}

  運行的輸出結果爲:c#

CHILD PROCESS: stack_data, heap_data
PARENT PROCESS: stack_data, heap_data

  能夠看出,父進程和子進程的棧和堆的數據是相同的。這些數據在建立子進程時是經過拷貝產生的。api

二. 關於execl調用

  系統調用exec是以新的進程去代替原來的進程,但進程的PID保持不變。所以,能夠這樣認爲,exec系統調用並無建立新的進程,只是替換了原來進程上下文的內容。原進程的代碼段,數據段,堆棧段被新的進程所代替。安全

  咱們來看一個例子:session

// execl_example.c#include <stdio.h>#include <stdlib.h>#include <unistd.h>int main(int argc, const char *argv[])
{
    execl("./hello_world", NULL, NULL);    /* We can only reach this code when there is an error in execl */    
    printf("The execl must be failed!\n");return 1;
}

  咱們執行一個不存在的hello_world程序,看看輸出結果:數據結構

The execl must be failed!

  如今咱們建立一個hello_world程序,該程序簡單的打印一個Hello World.多線程

// hello_world.c#include <stdio.h>int main(int argc, const char *argv[])
{
    printf("Hello World!\n");
}

  如今咱們繼續運行execl_example程序,這時輸出爲:app

Hello World!

  經過比較兩次輸出,咱們發現:當execl成功時,原有的進程執行就會被打斷,替換爲新的進程繼續執行。less

三. 使用匯編進行系統調用

  咱們知道在Linux中,每一個系統調用都對應一個系統調用號。這個系統調用號是在unistd.h中定義的。在個人機器上文件的位置是在:

/usr/src/linux-headers-2.6.28-11-generic/arch/x86/include/asm/unistd_32.h

  若是找不到,能夠嘗試使用如下命令查找:

locate unistd.h | xargs grep -ri "__NR_fork"

  下面是unistd.h的部份內容:

... ...#define __NR_restart_syscall      0#define __NR_exit          1#define __NR_fork          2#define __NR_read          3#define __NR_write          4#define __NR_open          5#define __NR_close          6#define __NR_waitpid          7#define __NR_creat          8#define __NR_link          9#define __NR_unlink         10#define __NR_execve         11... ...

  使用匯編調用fork:

  能夠看到fork的系統調用號是2,咱們如今使用匯編代碼從新編寫fork_example.c

#include <memory.h>#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>int  main()
{
    pid_t pid;    char stack_data[] = "stack_data";char *heap_data = malloc(10 * sizeof(char));    
    strcpy(heap_data, "heap_data");// pid = fork();asm volatile("mov $0x2, %%eax\n\t" // 將fork的系統調用號2存到eax寄存器  "int $0x80\n\t"       // 產生int 0x80中斷"mov %%eax,%0\n\t"    // 將結果存入pid中: "=m" (pid) 
    );    if (pid == 0) {
        printf("CHILD PROCESS: %s, %s\n", stack_data, heap_data);
    } else if (pid > 0) {
        printf("PARENT PROCESS: %s, %s\n", stack_data, heap_data);
    } else {
        printf("FORK FAILED.\n");
    }return 0;
}

  運行輸出結果是:

CHILD PROCESS: stack_data, heap_data
PARENT PROCESS: stack_data, heap_data

  能夠嘗試將調用號替換一下,改爲$0x3,獲得的結果是:

FORK FAILED.

  使用匯編調用execl:

  咱們再嘗試一下使用匯編調用execl。經過上面的觀察咱們能夠看到execl的系統調用號是11.

#include <stdio.h>#include <stdlib.h>#include <unistd.h>int main(int argc, const char *argv[])
{// execl("./hello_world", NULL, NULL);const char *program = "./hello_world";
    asm volatile ("mov %0,%%ebx\n\t"   // 使用program作爲參數1"mov $0,%%ecx\n\t"   // 參數2爲NULL"mov $0,%%edx\n\t"   // 參數3爲NULL"mov $0xb,%%eax\n\t" // 將execl的系統調用好11存入eax中"int $0x80\n\t"      // 產生0x80中斷: "=m" (program)
    );    /* We can only reach this code when there is an error in execl */    
    printf("The execl must be failed!\n");return 1;
}

  運行結果爲:

Hello World!

  若是將系統調用號改成0x3,輸出結果爲:

The execl must be failed!

 

四.系統調用過程詳解

  經過第三步的過程,咱們瞭解到,系統調用在內核中的執行是依靠中斷實現的。若是咱們想進一步定位fork和execl的代碼,咱們須要先了解系統調用的詳細過程。即回答如下兩個問題:

  1.中斷是怎麼工做的?

  2.int 0x80中斷是怎麼工做的?

  中斷是怎麼工做的

  在Linux操做系統中,中斷是經過中斷描述符表工做的。中斷描述符表(Interrupt Descriptor Table, IDT)是一個系統表,它與每個中斷或者異常向量相聯繫,每個向量在表中有相應的中斷或者異常處理程序的入口地址。內核在容許中斷髮生前,必須適當的初始化IDT。對於每一箇中斷,都會有對應的中斷處理程序。當產生一箇中斷時,Linux根據中斷向量表中對應的項找到存儲中斷處理程序的地址,而後調用相應的中斷處理程序。中段描述符表在內存中的地址存儲在idtr寄存器中。內核在啓動中斷前,必須初始化IDT,而後將IDT的地址壯載到idtr中。

  內核初始化的時候調用trap_init()函數和init_IRQ()函數初始化中斷向量表。

  int 0x80中斷是怎麼工做的

  經過上面的分析,咱們知道每一箇中斷都有對應的處理程序。在系統調用的過程當中,會有一個系統調用分派表,每一個表項存儲了一個系統調用。系統調用中斷處理程序,根據系統調用號找到對應的系統調用執行。對於系統調用,參數的傳遞是經過寄存器ebx ecx edx進行傳遞的。eax中存儲的是系統調用號。系統調用最大爲__NR_syscalls個。

  

  在arch/x86/include/asm/irq_vectors.h中定義了

# define SYSCALL_VECTOR            0x80

  如今咱們查找trap_init函數,在arch/x86/kernel/traps.c中

set_system_trap_gate(SYSCALL_VECTOR, &system_call);

  如今,查找system_call函數,在arch/x86/kernel/entry_32.s中:

ENTRY(system_call)
    RING0_INT_FRAME            # can't unwind into user space anyway    ASM_CLAC
    pushl_cfi %eax            # save orig_eax
    SAVE_ALL
    GET_THREAD_INFO(%ebp)
                    # system call tracing in operation / emulation
    testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
    jnz syscall_trace_entry
    cmpl $(NR_syscalls), %eax
    jae syscall_badsys
syscall_call:
    call *sys_call_table(,%eax,4)
    movl %eax,PT_EAX(%esp)        # store the return value
syscall_exit:
    LOCKDEP_SYS_EXIT
    DISABLE_INTERRUPTS(CLBR_ANY)    # make sure we don't miss an interrupt                    # setting need_resched or sigpending
                    # between sampling and the iret
    TRACE_IRQS_OFF
    movl TI_flags(%ebp), %ecx
    testl $_TIF_ALLWORK_MASK, %ecx    # current->work
    jne syscall_exit_work

  在include/uapi/asm_generic/unistd.h中找到:

__SYSCALL(__NR_fork, sys_fork)

  fork的系統調用號是2,對應的系統調用分派表中爲sys_fork函數。在kernel/fork.c中找到以下代碼:

#ifdef __ARCH_WANT_SYS_FORK
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMUreturn do_fork(SIGCHLD, 0, 0, NULL, NULL);#else/* can not support in nommu mode */return(-EINVAL);#endif}#endif

四.do_fork源碼分析

  如今查找do_fork函數,也在kernel/fork.c中:

/*
 *  Ok, 這就是fork例程的主要部分。
 *
 * 函數執行進程的複製,若是成功則啓動新進程。而且等待新進程完成VM的使用。 */long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          unsigned long stack_size,          int __user *parent_tidptr,          int __user *child_tidptr)
{struct task_struct *p;int trace = 0;long nr;/* * 在分配以前作一些參數和權限檢查。     */if (clone_flags & (CLONE_NEWUSER | CLONE_NEWPID)) {if (clone_flags & (CLONE_THREAD|CLONE_PARENT))return -EINVAL;
    }/* * 肯定是否須要報告給ptracer,或者哪些須要彙報給ptracer。若是是調用者內核線程
     * 或者標誌了CLONE_UNTRACED,則不報告任何跟蹤信息。不然,報告相應fork的跟蹤信息。     */if (!(clone_flags & CLONE_UNTRACED)) {if (clone_flags & CLONE_VFORK)
            trace = PTRACE_EVENT_VFORK;else if ((clone_flags & CSIGNAL) != SIGCHLD)
            trace = PTRACE_EVENT_CLONE;elsetrace = PTRACE_EVENT_FORK;if (likely(!ptrace_event_enabled(current, trace)))
            trace = 0;
    }
  // copy_process函數建立進程描述符和子進程須要的其餘數據結構。p = copy_process(clone_flags, stack_start, stack_size,
             child_tidptr, NULL, trace);             /* 如今喚醒新線程。*/if (!IS_ERR(p)) {struct completion vfork;

        trace_sched_process_fork(current, p);

        nr = task_pid_vnr(p);if (clone_flags & CLONE_PARENT_SETTID)
            put_user(nr, parent_tidptr);if (clone_flags & CLONE_VFORK) {
            p->vfork_done = &vfork;
            init_completion(&vfork);
            get_task_struct(p);
        }

        wake_up_new_task(p);/* fork已經完成,子進程也已經啓動。如今通知ptracer。 */if (unlikely(trace))
            ptrace_event(trace, nr);if (clone_flags & CLONE_VFORK) {if (!wait_for_vfork_done(p, &vfork))
                ptrace_event(PTRACE_EVENT_VFORK_DONE, nr);
        }
    } else {
        nr = PTR_ERR(p);
    }return nr;
}

  能夠看到do_fork調用了copy_process完成了絕大部分的工做。copy_process位於同一個文件當中:

/*
 * 以複製的方式建立一個新的進程。但不啓動運行新建立的進程。
 *
 * 主要複製寄存器和其它進程環境中的相應的合適部分。真正的
 * 啓動工做則交由調用者完成。 */static struct task_struct *copy_process(unsigned long clone_flags,
                    unsigned long stack_start,
                    unsigned long stack_size,int __user *child_tidptr,struct pid *pid,int trace)
{int retval;struct task_struct *p; // 保存新的進程描述符。/* 刪除了對標誌位的一致性和合法性的檢查 */// security_task_create和security_task_alloc()執行全部附加的安全檢查。retval = security_task_create(clone_flags);// dup_task_struct爲子進程獲取進程描述符。稍後分析。p = dup_task_struct(current);// task結構中ftrace_ret_stack結構變量的初始化,即函數返回用的棧。    ftrace_graph_init_task(p);
    get_seccomp_filter(p);// task中互斥變量的初始化。    rt_mutex_init_task(p);// 第1個if對進程佔用的資源數作出限制,task_rlimit(p, RLIMIT_NPROC)// 限制了改進程用戶能夠擁有的進程總數。 if (atomic_read(&p->real_cred->user->processes) >= task_rlimit(p, RLIMIT_NPROC)) {// 第2個if使用了capable()函數來對權限作出檢查,檢查是否有權對指定// 的資源進行操做,該函數返回0則表明無權操做。if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) && p->real_cred->user != INIT_USER)goto bad_fork_free;
    }
    
    current->flags &= ~PF_NPROC_EXCEEDED; // 將當前進程標誌位中的PF_NPROC_EXCEEDED置0。copy_creds(p, clone_flags); // copy_creds()複製證書,應該是複製權限及身份信息。// 檢查建立的線程是否超過了系統進程總量。if (nr_threads >= max_threads)goto bad_fork_cleanup_count;    // 增長執行實體的模塊引用計數。if (!try_module_get(task_thread_info(p)->exec_domain->module))goto bad_fork_cleanup_count;

    p->did_exec = 0;
    delayacct_tsk_init(p);    /* Must remain after dup_task_struct() */copy_flags(clone_flags, p); // 更新task_struct結構中flags成員INIT_LIST_HEAD(&p->children); // 初始化task_struct結構中的子進程鏈表INIT_LIST_HEAD(&p->sibling); // 初始化task_struct結構中的兄弟進程鏈表rcu_copy_process(p); // rcu相關變量的初始化p->vfork_done = NULL; 
    spin_lock_init(&p->alloc_lock); 

    init_sigpending(&p->pending);

    p->utime = p->stime = p->gtime = 0;
    p->utimescaled = p->stimescaled = 0;
    p->prev_cputime.utime = p->prev_cputime.stime = 0;
    seqlock_init(&p->vtime_seqlock);
    p->vtime_snap = 0;
    p->vtime_snap_whence = VTIME_SLEEPING;

    memset(&p->rss_stat, 0, sizeof(p->rss_stat));

    p->default_timer_slack_ns = current->timer_slack_ns;

    task_io_accounting_init(&p->ioac); // 進程描述符中的io數據記錄的初始化    acct_clear_integrals(p);

    posix_cpu_timers_init(p); // timer初始化do_posix_clock_monotonic_gettime(&p->start_time);
    p->real_start_time = p->start_time;
    monotonic_to_bootbased(&p->real_start_time);
    p->io_context = NULL;
    p->audit_context = NULL;if (clone_flags & CLONE_THREAD)
        threadgroup_change_begin(current);
    cgroup_fork(p);
#ifdef CONFIG_NUMA
    p->mempolicy = mpol_dup(p->mempolicy);if (IS_ERR(p->mempolicy)) {
        retval = PTR_ERR(p->mempolicy);
        p->mempolicy = NULL;goto bad_fork_cleanup_cgroup;
    }
    mpol_fix_fork_child_flag(p);#endif/* 設置CPU */p->cpuset_mem_spread_rotor = NUMA_NO_NODE;
    p->cpuset_slab_spread_rotor = NUMA_NO_NODE;
    seqcount_init(&p->mems_allowed_seq);/* 設置跟蹤中斷標誌 */ 
    p->irq_events = 0;
    p->hardirqs_enabled = 0;
    p->hardirq_enable_ip = 0;
    p->hardirq_enable_event = 0;
    p->hardirq_disable_ip = _THIS_IP_;
    p->hardirq_disable_event = 0;
    p->softirqs_enabled = 1;
    p->softirq_enable_ip = _THIS_IP_;
    p->softirq_enable_event = 0;
    p->softirq_disable_ip = 0;
    p->softirq_disable_event = 0;
    p->hardirq_context = 0;
    p->softirq_context = 0;/* 設置鎖深度 */p->lockdep_depth = 0; /* no locks held yet */p->curr_chain_key = 0;
    p->lockdep_recursion = 0;

#ifdef CONFIG_DEBUG_MUTEXES
    p->blocked_on = NULL; /* not blocked yet */#endif#ifdef CONFIG_MEMCG
    p->memcg_batch.do_batch = 0;
    p->memcg_batch.memcg = NULL;#endifsched_fork(p); // 調度相關初始化,將新進程分配到某個CPU上。perf_event_init_task(p);
    audit_alloc(p);        /* 如下根據clone_flags的設置複製相應的部分,進行從新分配或者共享父進程的內容 */copy_semundo(clone_flags, p);
    copy_files(clone_flags, p);
    copy_fs(clone_flags, p);
    copy_sighand(clone_flags, p);
    copy_signal(clone_flags, p);
    copy_mm(clone_flags, p);
    copy_namespaces(clone_flags, p);
    copy_io(clone_flags, p);
    copy_thread(clone_flags, stack_start, stack_size, p);if (pid != &init_struct_pid) {
        retval = -ENOMEM;
        pid = alloc_pid(p->nsproxy->pid_ns);if (!pid)goto bad_fork_cleanup_io;
    }

    p->pid = pid_nr(pid);
    p->tgid = p->pid;// 若是設置了同在一個線程組則繼承TGID。 // 對於普通進程來講TGID和PID相等, // 對於線程來講,同一線程組內的全部線程的TGID都相等, // 這使得這些多線程能夠經過調用getpid()得到相同的PID。if (clone_flags & CLONE_THREAD)
        p->tgid = current->tgid;

    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;
    uprobe_copy_process(p);/* * sigaltstack should be cleared when sharing the same VM     */if ((clone_flags & (CLONE_VM|CLONE_VFORK)) == CLONE_VM)
        p->sas_ss_sp = p->sas_ss_size = 0;/* * Syscall tracing and stepping should be turned off in the
     * child regardless of CLONE_PTRACE.     */user_disable_single_step(p);
    clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE);
#ifdef TIF_SYSCALL_EMU
    clear_tsk_thread_flag(p, TIF_SYSCALL_EMU);#endifclear_all_latency_tracing(p);/* ok, now we should be set up.. */if (clone_flags & CLONE_THREAD)
        p->exit_signal = -1;else if (clone_flags & CLONE_PARENT)
        p->exit_signal = current->group_leader->exit_signal;elsep->exit_signal = (clone_flags & CSIGNAL);

    p->pdeath_signal = 0;
    p->exit_state = 0;

    p->nr_dirtied = 0;
    p->nr_dirtied_pause = 128 >> (PAGE_SHIFT - 10);
    p->dirty_paused_when = 0;/* * Ok, make it visible to the rest of the system.
     * We dont wake it up yet.     */p->group_leader = p;
    INIT_LIST_HEAD(&p->thread_group);
    p->task_works = NULL;/* Need tasklist lock for parent etc handling! */write_lock_irq(&tasklist_lock);// 若是這兩個標誌設定了,那麼和父進程有相同的父進程if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) {
        p->real_parent = current->real_parent;
        p->parent_exec_id = current->parent_exec_id;
    } else { // 不然父進程爲實際父進程p->real_parent = current;
        p->parent_exec_id = current->self_exec_id;
    }

    spin_lock(&current->sighand->siglock);/* * Process group and session signals need to be delivered to just the
     * parent before the fork or both the parent and the child after the
     * fork. Restart if a signal comes in before we add the new process to
     * it's process group.
     * A fatal signal pending means that current will exit, so the new
     * thread can't slip out of an OOM kill (or normal SIGKILL).*/recalc_sigpending();if (signal_pending(current)) {
        spin_unlock(&current->sighand->siglock);
        write_unlock_irq(&tasklist_lock);
        retval = -ERESTARTNOINTR;goto bad_fork_free_pid;
    }    // 若是和父進程有相同的線程組if (clone_flags & CLONE_THREAD) {
        current->signal->nr_threads++;
        atomic_inc(&current->signal->live);
        atomic_inc(&current->signal->sigcnt);
        p->group_leader = current->group_leader;
        list_add_tail_rcu(&p->thread_group, &p->group_leader->thread_group);
    }if (likely(p->pid)) {
        ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace); // ptrace的相關初始化        // 若是進程p是線程組leaderif (thread_group_leader(p)) {if (is_child_reaper(pid)) {
                ns_of_pid(pid)->child_reaper = p;
                p->signal->flags |= SIGNAL_UNKILLABLE;
            }

            p->signal->leader_pid = pid;
            p->signal->tty = tty_kref_get(current->signal->tty);            /* 加入對應的PID哈希表 */attach_pid(p, PIDTYPE_PGID, task_pgrp(current));
            attach_pid(p, PIDTYPE_SID, task_session(current));
            
            list_add_tail(&p->sibling, &p->real_parent->children);
            list_add_tail_rcu(&p->tasks, &init_task.tasks); // 加入隊列__this_cpu_inc(process_counts); // 將per cpu變量加一        }
        attach_pid(p, PIDTYPE_PID, pid); // 維護pid變量nr_threads++; // 線程數加1。    }

    total_forks++; // 將全局變量total_forks加1.spin_unlock(&current->sighand->siglock);
    write_unlock_irq(&tasklist_lock);
    proc_fork_connector(p);
    cgroup_post_fork(p);if (clone_flags & CLONE_THREAD)
        threadgroup_change_end(current);
    perf_event_fork(p);

    trace_task_newtask(p, clone_flags);return p;
}

dup_task_struct也在fork.c文件中

static struct task_struct *dup_task_struct(struct task_struct *orig)
{struct task_struct *tsk; // 存放新的task_sturct結構體struct thread_info *ti; // 存放線程信息unsigned long *stackend; int node = tsk_fork_get_node(orig); int err;

    tsk = alloc_task_struct_node(node); // 經過alloc_task_struct()函數建立task_struct結構空間ti = alloc_thread_info_node(tsk, node); // 分配thread_info結構空間err = arch_dup_task_struct(tsk, orig); // 關於浮點結構的複製    
    tsk->stack = ti; // task的對應棧setup_thread_stack(tsk, orig);
    clear_user_return_notifier(tsk);
    clear_tsk_need_resched(tsk);
    stackend = end_of_stack(tsk);*stackend = STACK_END_MAGIC;    /* for overflow detection */#ifdef CONFIG_CC_STACKPROTECTOR
    tsk->stack_canary = get_random_int(); // 金絲雀的設置,用於防護棧溢出攻擊#endif/* * One for us, one for whoever does the "release_task()" (usually
     * parent)     */atomic_set(&tsk->usage, 2); // 設置進程塊的使用計數。#ifdef CONFIG_BLK_DEV_IO_TRACE
    tsk->btrace_seq = 0;#endiftsk->splice_pipe = NULL;
    tsk->task_frag.page = NULL;

    account_kernel_stack(ti, 1);return tsk;
}

  經過上面的代碼,能夠總結出fork的工做的基本流程是:

五.do_execve的分析

execve對應的內核服務例程位於fs/exec.c中。

/*
 * sys_execve() 服務例程執行一個程序.
 * filename須要執行的文件的絕對路徑
 * argv傳入系統調用的參數
 * regs是系統調用時系統堆棧的狀況 */static int do_execve_common(const char *filename,struct user_arg_ptr argv,struct user_arg_ptr envp)
{struct linux_binprm *bprm;struct file *file;struct files_struct *displaced;bool clear_in_exec;int retval;const struct cred *cred = current_cred(); 

    unshare_files(&displaced); // 動態分配一個linux_binprm數據結構,並用新的可執行文件的數據填充這個結構bprm = kzalloc(sizeof(*bprm), GFP_KERNEL); 

    retval = prepare_bprm_creds(bprm);

    retval = check_unsafe_exec(bprm); 
    clear_in_exec = retval;
    current->in_execve = 1;

    file = open_exec(filename); // 打開可執行文件並讀入到內存。retval = PTR_ERR(file);

    sched_exec(); // 肯定最小負載的CPU以執行新程序,並把當前進程轉移過去。bprm->file = file;
    bprm->filename = filename;
    bprm->interp = filename;

    bprm_mm_init(bprm);

    bprm->argc = count(argv, MAX_ARG_STRINGS);

    bprm->envc = count(envp, MAX_ARG_STRINGS);    // prepare_binprm()填充linux_binprm數據結構,這個函數依次執行:// a.檢查文件是否可執行。// b.初始化bprm的e_uid和e_gid字段。// c.用可執行文件的前128個字節填充bprm的buf字段。    prepare_binprm(bprm); 
    /* 把文件路徑名拷貝、命令行參數及環境串拷貝到一個或多個新分配的頁框中 */copy_strings_kernel(1, &bprm->filename, bprm);
    bprm->exec = bprm->p;
    copy_strings(bprm->envc, envp, bprm);
    copy_strings(bprm->argc, argv, bprm);    // 掃描formats鏈表,並盡力應用每一個元素的load_binary方法,把bprm傳遞給這個// 函數。只要load_binary方法成功應答了文件的可執行格式,對formats掃描終止。    search_binary_handler(bprm);/* 成功,釋放bprm,返回從該文件可執行格式的load_binary方法中所得到的代碼。 */current->fs->in_exec = 0;
    current->in_execve = 0;
    acct_update_integrals(current);
    free_bprm(bprm);if (displaced)
        put_files_struct(displaced);return retval;
}

下面咱們看看load_elf_binary函數,該函數位於fs/binfmt_elf.c中

 

static int load_elf_binary(struct linux_binprm *bprm)
{struct file *interpreter = NULL; /* to shut gcc up */ unsigned long load_addr = 0, load_bias = 0;int load_addr_set = 0;char * elf_interpreter = NULL;
    unsigned long error;struct elf_phdr *elf_ppnt, *elf_phdata;
    unsigned long elf_bss, elf_brk;int retval, i;
    unsigned int size;
    unsigned long elf_entry;
    unsigned long interp_load_addr = 0;
    unsigned long start_code, end_code, start_data, end_data;
    unsigned long reloc_func_desc __maybe_unused = 0;int executable_stack = EXSTACK_DEFAULT;
    unsigned long def_flags = 0;struct pt_regs *regs = current_pt_regs();struct {struct elfhdr elf_ex;struct elfhdr interp_elf_ex;
    } *loc;

    loc = kmalloc(sizeof(*loc), GFP_KERNEL);    /* 讀取可執行文件的首部。首部描述程序的段和所需的共享庫。 */loc->elf_ex = *((struct elfhdr *)bprm->buf);/* 檢測一致性 */if (memcmp(loc->elf_ex.e_ident, ELFMAG, SELFMAG) != 0)goto out;if (loc->elf_ex.e_type != ET_EXEC && loc->elf_ex.e_type != ET_DYN)goto out;if (!elf_check_arch(&loc->elf_ex))goto out;if (!bprm->file->f_op || !bprm->file->f_op->mmap)goto out;/* 讀取全部的首部信息 */loc->elf_ex.e_phentsize != sizeof(struct elf_phdr);if (loc->elf_ex.e_phnum < 1 || loc->elf_ex.e_phnum > 65536U / sizeof(struct elf_phdr))goto out;
    size = loc->elf_ex.e_phnum * sizeof(struct elf_phdr);
    retval = -ENOMEM;
    elf_phdata = kmalloc(size, GFP_KERNEL);

    retval = kernel_read(bprm->file, loc->elf_ex.e_phoff, (char *)elf_phdata, size);if (retval != size) {if (retval >= 0)
            retval = -EIO;goto out_free_ph;
    }

    elf_ppnt = elf_phdata;
    elf_bss = 0;
    elf_brk = 0;

    start_code = ~0UL;
    end_code = 0;
    start_data = 0;
    end_data = 0;for (i = 0; i < loc->elf_ex.e_phnum; i++) {if (elf_ppnt->p_type == PT_INTERP) {/* This is the program interpreter used for
             * shared libraries - for now assume that this
             * is an a.out format binary             */retval = -ENOEXEC;if (elf_ppnt->p_filesz > PATH_MAX || 
                elf_ppnt->p_filesz < 2)goto out_free_ph;

            retval = -ENOMEM;
            elf_interpreter = kmalloc(elf_ppnt->p_filesz,
                          GFP_KERNEL);if (!elf_interpreter)goto out_free_ph;

            retval = kernel_read(bprm->file, elf_ppnt->p_offset,
                         elf_interpreter,
                         elf_ppnt->p_filesz);if (retval != elf_ppnt->p_filesz) {if (retval >= 0)
                    retval = -EIO;goto out_free_interp;
            }/* make sure path is NULL terminated */retval = -ENOEXEC;if (elf_interpreter[elf_ppnt->p_filesz - 1] != '\0')goto out_free_interp;

            interpreter = open_exec(elf_interpreter);
            retval = PTR_ERR(interpreter);if (IS_ERR(interpreter))goto out_free_interp;/* * If the binary is not readable then enforce
             * mm->dumpable = 0 regardless of the interpreter's
             * permissions.             */would_dump(bprm, interpreter);

            retval = kernel_read(interpreter, 0, bprm->buf,
                         BINPRM_BUF_SIZE);if (retval != BINPRM_BUF_SIZE) {if (retval >= 0)
                    retval = -EIO;goto out_free_dentry;
            }/* Get the exec headers */loc->interp_elf_ex = *((struct elfhdr *)bprm->buf);break;
        }
        elf_ppnt++;
    }

    elf_ppnt = elf_phdata;for (i = 0; i < loc->elf_ex.e_phnum; i++, elf_ppnt++)if (elf_ppnt->p_type == PT_GNU_STACK) {if (elf_ppnt->p_flags & PF_X)
                executable_stack = EXSTACK_ENABLE_X;elseexecutable_stack = EXSTACK_DISABLE_X;break;
        }/* Some simple consistency checks for the interpreter */if (elf_interpreter) {
        retval = -ELIBBAD;/* Not an ELF interpreter */if (memcmp(loc->interp_elf_ex.e_ident, ELFMAG, SELFMAG) != 0)goto out_free_dentry;/* Verify the interpreter has a valid arch */if (!elf_check_arch(&loc->interp_elf_ex))goto out_free_dentry;
    }// 釋放前一個計算所佔用的幾乎全部資源retval = flush_old_exec(bprm);/* OK, This is the point of no return */current->mm->def_flags = def_flags;/* Do this immediately, since STACK_TOP as used in setup_arg_pages
       may depend on the personality.  */SET_PERSONALITY(loc->elf_ex);if (elf_read_implies_exec(loc->elf_ex, executable_stack))
        current->personality |= READ_IMPLIES_EXEC;if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
        current->flags |= PF_RANDOMIZE;

    setup_new_exec(bprm);/* Do this so that we can load the interpreter, if need be.  We will
       change some of these later */current->mm->free_area_cache = current->mm->mmap_base;
    current->mm->cached_hole_size = 0;// 爲進程的用戶態堆棧分配一個新的線性區描述符,並把那個線性區插入到進程的地址空間。    setup_arg_pages(bprm, randomize_stack_top(STACK_TOP), executable_stack);
    
    
    current->mm->start_stack = bprm->p;/* 如今將ELF鏡像文件映射到內存中正確的位置 */for(i = 0, elf_ppnt = elf_phdata;
        i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {int elf_prot = 0, elf_flags;
        unsigned long k, vaddr;if (elf_ppnt->p_type != PT_LOAD)continue;if (unlikely (elf_brk > elf_bss)) {
            unsigned long nbyte;                /* There was a PT_LOAD segment with p_memsz > p_filesz
               before this one. Map anonymous pages, if needed,
               and clear the area.  */retval = set_brk(elf_bss + load_bias,
                     elf_brk + load_bias);if (retval) {
                send_sig(SIGKILL, current, 0);goto out_free_dentry;
            }
            nbyte = ELF_PAGEOFFSET(elf_bss);if (nbyte) {
                nbyte = ELF_MIN_ALIGN - nbyte;if (nbyte > elf_brk - elf_bss)
                    nbyte = elf_brk - elf_bss;if (clear_user((void __user *)elf_bss +load_bias, nbyte)) {/* * This bss-zeroing can fail if the ELF
                     * file specifies odd protections. So
                     * we don't check the return value                     */}
            }
        }if (elf_ppnt->p_flags & PF_R)
            elf_prot |= PROT_READ;if (elf_ppnt->p_flags & PF_W)
            elf_prot |= PROT_WRITE;if (elf_ppnt->p_flags & PF_X)
            elf_prot |= PROT_EXEC;

        elf_flags = MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE;

        vaddr = elf_ppnt->p_vaddr;if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {
            elf_flags |= MAP_FIXED;
        } else if (loc->elf_ex.e_type == ET_DYN) {/* Try and get dynamic programs out of the way of the
             * default mmap base, as well as whatever program they
             * might try to exec.  This is because the brk will
             * follow the loader, and is not movable.  */#ifdef CONFIG_ARCH_BINFMT_ELF_RANDOMIZE_PIE/* Memory randomization might have been switched off
             * in runtime via sysctl.
             * If that is the case, retain the original non-zero
             * load_bias value in order to establish proper
             * non-randomized mappings.             */if (current->flags & PF_RANDOMIZE)
                load_bias = 0;elseload_bias = ELF_PAGESTART(ELF_ET_DYN_BASE - vaddr);#elseload_bias = ELF_PAGESTART(ELF_ET_DYN_BASE - vaddr);#endif}

        error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
                elf_prot, elf_flags, 0);if (BAD_ADDR(error)) {
            send_sig(SIGKILL, current, 0);
            retval = IS_ERR((void *)error) ?PTR_ERR((void*)error) : -EINVAL;goto out_free_dentry;
        }if (!load_addr_set) {
            load_addr_set = 1;
            load_addr = (elf_ppnt->p_vaddr - elf_ppnt->p_offset);if (loc->elf_ex.e_type == ET_DYN) {
                load_bias += error - ELF_PAGESTART(load_bias + vaddr);
                load_addr += load_bias;
                reloc_func_desc = load_bias;
            }
        }
        k = elf_ppnt->p_vaddr;if (k < start_code)
            start_code = k;if (start_data < k)
            start_data = k;/* * Check to see if the section's size will overflow the
         * allowed task size. Note that p_filesz must always be
         * <= p_memsz so it is only necessary to check p_memsz.         */if (BAD_ADDR(k) || elf_ppnt->p_filesz > elf_ppnt->p_memsz ||elf_ppnt->p_memsz > TASK_SIZE ||TASK_SIZE - elf_ppnt->p_memsz < k) {/* set_brk can never work. Avoid overflows. */send_sig(SIGKILL, current, 0);
            retval = -EINVAL;goto out_free_dentry;
        }

        k = elf_ppnt->p_vaddr + elf_ppnt->p_filesz;if (k > elf_bss)
            elf_bss = k;if ((elf_ppnt->p_flags & PF_X) && end_code < k)
            end_code = k;if (end_data < k)
            end_data = k;
        k = elf_ppnt->p_vaddr + elf_ppnt->p_memsz;if (k > elf_brk)
            elf_brk = k;
    }

    loc->elf_ex.e_entry += load_bias;
    elf_bss += load_bias;
    elf_brk += load_bias;
    start_code += load_bias;
    end_code += load_bias;
    start_data += load_bias;
    end_data += load_bias;/* Calling set_brk effectively mmaps the pages that we need
     * for the bss and break sections.  We must do this before
     * mapping in the interpreter, to make sure it doesn't wind
     * up getting placed where the bss needs to go.     */retval = set_brk(elf_bss, elf_brk);if (retval) {
        send_sig(SIGKILL, current, 0);goto out_free_dentry;
    }if (likely(elf_bss != elf_brk) && unlikely(padzero(elf_bss))) {
        send_sig(SIGSEGV, current, 0);
        retval = -EFAULT; /* Nobody gets to see this, but.. */goto out_free_dentry;
    }// 調用一個動態連接程序的函數。若是動態連接程序是elf可執行的,這// 個函數就叫作load_elf_interp()。if (elf_interpreter) {
        unsigned long interp_map_addr = 0;
        
        elf_entry = load_elf_interp(&loc->interp_elf_ex,
                        interpreter,&interp_map_addr,
                        load_bias);if (!IS_ERR((void *)elf_entry)) {/* * load_elf_interp() returns relocation
             * adjustment             */interp_load_addr = elf_entry;
            elf_entry += loc->interp_elf_ex.e_entry;
        }if (BAD_ADDR(elf_entry)) {
            force_sig(SIGSEGV, current);
            retval = IS_ERR((void *)elf_entry) ?(int)elf_entry : -EINVAL;goto out_free_dentry;
        }
        reloc_func_desc = interp_load_addr;

        allow_write_access(interpreter);
        fput(interpreter);
        kfree(elf_interpreter);
    } else {
        elf_entry = loc->elf_ex.e_entry;if (BAD_ADDR(elf_entry)) {
            force_sig(SIGSEGV, current);
            retval = -EINVAL;goto out_free_dentry;
        }
    }

    kfree(elf_phdata);// 把可執行格式的linux_binfmt對象的地址存放在進程描述符的binfmt字段中。set_binfmt(&elf_format);

#ifdef ARCH_HAS_SETUP_ADDITIONAL_PAGES
    retval = arch_setup_additional_pages(bprm, !!elf_interpreter);if (retval < 0) {
        send_sig(SIGKILL, current, 0);goto out;
    }#endif /* ARCH_HAS_SETUP_ADDITIONAL_PAGES */install_exec_creds(bprm);
    retval = create_elf_tables(bprm, &loc->elf_ex,
              load_addr, interp_load_addr);if (retval < 0) {
        send_sig(SIGKILL, current, 0);goto out;
    }/* N.B. passed_fileno might not be initialized? */current->mm->end_code = end_code;
    current->mm->start_code = start_code;
    current->mm->start_data = start_data;
    current->mm->end_data = end_data;
    current->mm->start_stack = bprm->p;

#ifdef arch_randomize_brkif ((current->flags & PF_RANDOMIZE) && (randomize_va_space > 1)) {
        current->mm->brk = current->mm->start_brk =arch_randomize_brk(current->mm);
#ifdef CONFIG_COMPAT_BRK
        current->brk_randomized = 1;#endif}#endifif (current->personality & MMAP_PAGE_ZERO) {/* Why this, you ask???  Well SVr4 maps page 0 as read-only,
           and some applications "depend" upon this behavior.
           Since we do not have the power to recompile these, we
           emulate the SVr4 behavior. Sigh. */error = vm_mmap(NULL, 0, PAGE_SIZE, PROT_READ | PROT_EXEC,
                MAP_FIXED | MAP_PRIVATE, 0);
    }

#ifdef ELF_PLAT_INIT/* * The ABI may specify that certain registers be set up in special
     * ways (on i386 %edx is the address of a DT_FINI function, for
     * example.  In addition, it may also specify (eg, PowerPC64 ELF)
     * that the e_entry field is the address of the function descriptor
     * for the startup routine, rather than the address of the startup
     * routine itself.  This macro performs whatever initialization to
     * the regs structure is required as well as any relocations to the
     * function descriptor entries when executing dynamically links apps.     */ELF_PLAT_INIT(regs, reloc_func_desc);#endifstart_thread(regs, elf_entry, bprm->p);
    retval = 0;out:
    kfree(loc);
out_ret:return retval;/* error cleanup */out_free_dentry:
    allow_write_access(interpreter);if (interpreter)
        fput(interpreter);
out_free_interp:
    kfree(elf_interpreter);
out_free_ph:
    kfree(elf_phdata);goto out;
}
相關文章
相關標籤/搜索