久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

淺析Linux計(jì)算機(jī)進(jìn)程地址空間與內(nèi)核裝載ELF

 鳳凰苑兇真 2016-07-24

環(huán)境:lubuntu 13.04   kernel 3.8  gcc 4.7.3 

作者: SA12226265

簡(jiǎn)介: 本文基于Linux?系統(tǒng)對(duì)進(jìn)程創(chuàng)建與加載進(jìn)行分析,文中實(shí)現(xiàn)了Linux庫函數(shù)fork,、exec,,剖析內(nèi)核態(tài)執(zhí)行過程,并進(jìn)一步展示進(jìn)程創(chuàng)建過程中進(jìn)程控制塊字段變化信息及ELF文件加載過程,。


 

一,、初識(shí)Linux進(jìn)程                                                                                                                                                                             

  進(jìn)程這個(gè)概念是針對(duì)系統(tǒng)而不是針對(duì)用戶的,對(duì)用戶來說,他面對(duì)的概念是程序,。當(dāng)用戶敲入命令執(zhí)行一個(gè)程序的時(shí)候,,對(duì)系統(tǒng)而言,它將啟動(dòng)一個(gè)進(jìn)程,。但和程序不同的是,,在這個(gè)進(jìn)程中,系統(tǒng)可能需要再啟動(dòng)一個(gè)或多個(gè)進(jìn)程來完成獨(dú)立的多個(gè)任務(wù),。簡(jiǎn)單介紹下進(jìn)程的結(jié)構(gòu),。

  1.1 Linux下的進(jìn)程查看 

  我們可以使用$ps命令來查詢正在運(yùn)行的進(jìn)程,比如$ps -eo pid,comm,cmd,,下圖為執(zhí)行結(jié)果:

  (-e表示列出全部進(jìn)程,,-o pid,comm,cmd表示我們需要PID,COMMAND,,CMD信息)

  

  每一行代表了一個(gè)進(jìn)程,。每一行又分為三列。第一列PID(process IDentity)是一個(gè)整數(shù),,每一個(gè)進(jìn)程都有一個(gè)唯一的PID來代表自己的身份,進(jìn)程也可以根據(jù)PID來識(shí)別其他的進(jìn)程,。第二列COMMAND是這個(gè)進(jìn)程的簡(jiǎn)稱,。第三列CMD是進(jìn)程所對(duì)應(yīng)的程序以及運(yùn)行時(shí)所帶的參數(shù)。

  (第三列有一些由中括號(hào)[]括起來的,。它們是kernel的一部分功能,,顯示為進(jìn)程的樣子主要是為了方便操作系統(tǒng)管理。)

  我們看第一行,,PID為1,,名字為init。這個(gè)進(jìn)程是執(zhí)行/bin/init這一文件(程序)生成的,。當(dāng)Linux啟動(dòng)的時(shí)候,,init是系統(tǒng)創(chuàng)建的第一個(gè)進(jìn)程,這一進(jìn)程會(huì)一直存在,,直到我們關(guān)閉計(jì)算機(jī),。

  1.2 Linux下進(jìn)程的結(jié)構(gòu)

  Linux下一個(gè)進(jìn)程在內(nèi)存里有三部分的數(shù)據(jù),就是"代碼段",、"堆棧段"和"數(shù)據(jù)段",。其實(shí)學(xué)過匯編語言的人一定知道,一般的CPU都有上述三種段寄存器,,以方便操作系統(tǒng)的運(yùn)行,。這三個(gè)部分也是構(gòu)成一個(gè)完整的執(zhí)行序列的必要的部分。

  "代碼段",,顧名思義,,就是存放了程序代碼的數(shù)據(jù),,假如機(jī)器中有數(shù)個(gè)進(jìn)程運(yùn)行相同的一個(gè)程序,那么它們就可以使用相同的代碼段,。"堆棧段"存放的就是子程序的返回地址,、子程序的參數(shù)以及程序的局部變量。而數(shù)據(jù)段則存放程序的全局變量,,常數(shù)以及動(dòng)態(tài)數(shù)據(jù)分配的數(shù)據(jù)空間(比如用malloc之類的函數(shù)取得的空間),。系統(tǒng)如果同時(shí)運(yùn)行數(shù)個(gè)相同的程序,它們之間就不能使用同一個(gè)堆棧段和數(shù)據(jù)段,。

  1.3 Linux進(jìn)程描述符

  在Linux中每一個(gè)進(jìn)程都由task_struct 數(shù)據(jù)結(jié)構(gòu)來定義.task_struct就是我們通常所說的PCB.它是對(duì)進(jìn)程控制的唯一手段也是最有效的手段. 當(dāng)我們調(diào)用fork() 時(shí),系統(tǒng)會(huì)為我們產(chǎn)生一個(gè)task_struct結(jié)構(gòu),。然后從父進(jìn)程,那里繼承一些數(shù)據(jù), 并把新的進(jìn)程插入到進(jìn)程樹中,以待進(jìn)行進(jìn)程管理。

  以下是進(jìn)程描述符的源碼:

復(fù)制代碼
 1 struct task_struct {
 2   volatile long state; 
 3   unsigned long flags;  
 4   int sigpending;   
 5   mm_segment_taddr_limit; 
 6   volatile long need_resched;
 7   int lock_depth;
 8   long nice;
 9   unsigned long policy;
10   struct mm_struct *mm;
11   int processor;
12   unsigned long cpus_runnable, cpus_allowed;
13   struct list_head run_list; 
14   unsigned longsleep_time; 
15   struct task_struct *next_task, *prev_task;
16   struct mm_struct *active_mm;
17   struct list_headlocal_pages;   
18   unsigned int allocation_order, nr_local_pages;
19   struct linux_binfmt *binfmt; 
20   int exit_code, exit_signal;
21   int pdeath_signal;
22   unsigned long personality;
23   int did_exec:1; 
24   pid_t pid; 
25   pid_t pgrp; 
26   pid_t tty_old_pgrp;
27   pid_t session;  
28   pid_t tgid;
29   int leader;
30   struct task_struct*p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
31   struct list_head thread_group;
32   struct task_struct *pid hash_next; 
33   struct task_struct **pid hash_pprev;
34   wait_queue_head_t wait_chldexit;  
35   struct completion *vfork_done;  
36    unsigned long rt_priority;
37    unsigned long it_real_value, it_prof_value, it_virt_value;
38    unsigned long it_real_incr, it_prof_incr, it_virt_value;
39    struct timer_listreal_timer;  
40    struct tmstimes;    
41   unsigned long start_time;  
42   long per_cpu_utime[NR_CPUS],per_cpu_stime[NR_CPUS]; 
43   uid_t uid,euid,suid,fsuid;
44   gid_t gid,egid,sgid,fsgid;
45   int ngroups; 
46   gid_t groups[NGROUPS]; 
47   kernel_cap_tcap_effective, cap_inheritable, cap_permitted;
48   int keep_capabilities:1;
49   struct user_struct *user;
50   struct rlimit rlim[RLIM_NLIMITS];  
51   unsigned shortused_math;  
52   charcomm[16];  
53   int link_count, total_link_count;
54   struct tty_struct*tty;
55   unsigned int locks;
56   struct sem_undo*semundo;  
57   struct sem_queue *semsleeping; 
58   struct thread_struct thread;
59   struct fs_struct *fs;
60   struct files_struct *files;
61   spinlock_t sigmask_lock;
62   struct signal_struct *sig; 
63   sigset_t blocked;  
64   struct sigpendingpending;  
65   unsigned long sas_ss_sp;
66   size_t sas_ss_size;
67   int (*notifier)(void *priv);
68   void *notifier_data;
69   sigset_t *notifier_mask;
70   u32 parent_exec_id;
71   u32 self_exec_id;
72   spinlock_t alloc_lock;
73   void *journal_info;
74 };
復(fù)制代碼

主要結(jié)構(gòu)分析:

volatile long state; 說明了該進(jìn)程是否可以執(zhí)行,還是可中斷等信息

unsigned long flags; Flage 是進(jìn)程號(hào),在調(diào)用fork()時(shí)給出

int sigpending; 進(jìn)程上是否有待處理的信號(hào)

mm_segment_taddr_limit; 進(jìn)程地址空間,區(qū)分內(nèi)核進(jìn)程與普通進(jìn)程在內(nèi)存存放的位置不同(0-0xBFFFFFFF foruser-thead     0-0xFFFFFFFF forkernel-thread)

volatile long need_resched;調(diào)度標(biāo)志,表示該進(jìn)程是否需要重新調(diào)度,若非0,則當(dāng)從內(nèi)核態(tài)返回到用戶態(tài),會(huì)發(fā)生調(diào)度

struct mm_struct *mm; 進(jìn)程內(nèi)存管理信息

pid_tpid; 進(jìn)程標(biāo)識(shí)符,用來代表一個(gè)進(jìn)程

pid_tpgrp; 進(jìn)程組標(biāo)識(shí),表示進(jìn)程所屬的進(jìn)程組

task_struct的數(shù)據(jù)成員mm指向關(guān)于存儲(chǔ)管理的struct mm_struct結(jié)構(gòu),。它包含著進(jìn)程內(nèi)存管理的很多重要數(shù)據(jù),,如進(jìn)程代碼段、數(shù)據(jù)段,、未未初始化數(shù)據(jù)段,、調(diào)用參數(shù)區(qū)和進(jìn)程。


 

二,、 如何創(chuàng)建一個(gè)進(jìn)程                                                                                                                                                   

 

  2.1 Linux下的進(jìn)程控制

  在傳統(tǒng)的Linux環(huán)境下,,有兩個(gè)基本的操作用于創(chuàng)建和修改進(jìn)程:函數(shù)fork()用來創(chuàng)建一個(gè)新的進(jìn)程,該進(jìn)程幾乎是當(dāng)前進(jìn)程的一個(gè)完全拷貝,;函數(shù)族e(cuò)xec( )用來啟動(dòng)另外的進(jìn)程以取代當(dāng)前運(yùn)行的進(jìn)程,。

  關(guān)于fork()與execl(),去年寫過一篇文章對(duì)部分源碼進(jìn)行過分析:system()和execv()函數(shù)使用詳解

  2.2 fork()

  一個(gè)進(jìn)程在運(yùn)行中,,如果使用了fork,,就產(chǎn)生了另一個(gè)進(jìn)程。下面就看看如何具體使用fork,,這段程序演示了使用fork的基本框架:

復(fù)制代碼
#include <stdio.h>

void main()
{
    int i;
    if ( fork() == 0 ) 
    {
       /* 子進(jìn)程程序 */
       for ( i = 1; i <1000; i ++ ) 
          printf("This is child process\n");
    }
    else 
    {
       /* 父進(jìn)程程序*/
       for ( i = 1; i <1000; i ++ ) 
         printf("This is origin process\n");
    }
}
復(fù)制代碼

  運(yùn)行結(jié)果如下:

  從上圖可以看出父進(jìn)程和子進(jìn)程并發(fā)運(yùn)行,,內(nèi)核能夠以任意方式交替運(yùn)行它們,這里是父進(jìn)程先運(yùn)行,,然后是子進(jìn)程,。但是在另外一個(gè)系統(tǒng)上運(yùn)行時(shí)不一定是這個(gè)順序。

  使用fork函數(shù)創(chuàng)建的子進(jìn)程從父進(jìn)程的繼承了全部進(jìn)程的地址空間,,包括:進(jìn)程上下文,、進(jìn)程堆棧、內(nèi)存信息,、打開的文件描述符,、信號(hào)控制設(shè)置、進(jìn)程優(yōu)先級(jí)、進(jìn)程組號(hào),、當(dāng)前工作目錄,、根目錄、資源限制,、控制終端等,。

  fork創(chuàng)建子進(jìn)程,首先調(diào)用int80中斷,,然后將系統(tǒng)調(diào)用號(hào)保存在eax寄存器中,,進(jìn)入內(nèi)核態(tài)后調(diào)用do_fork(),實(shí)際上是創(chuàng)建了一份父進(jìn)程的拷貝,,他們的內(nèi)存空間里包含了完全相同的內(nèi)容,,包括當(dāng)前打開的資源,數(shù)據(jù),,當(dāng)然也包含了程序運(yùn)行到的位置,,也就是說fork后子進(jìn)程也是從fork函數(shù)的位置開始往下執(zhí)行的,而不是從頭開始,。而為了判別當(dāng)前正在運(yùn)行的是哪個(gè)進(jìn)程,,fork函數(shù)返回了一個(gè)pid,在父進(jìn)程里標(biāo)識(shí)了子進(jìn)程的id,,在子進(jìn)程里其值為0,,在我們的程序里就根據(jù)這個(gè)值來分開父進(jìn)程的代碼和子進(jìn)程的代碼。

  一旦使用fork創(chuàng)建子進(jìn)程,,則進(jìn)程地址空間中的任何有效地址都只能位于唯一的區(qū)域,,這些區(qū)域不能相互覆蓋,。編寫如下代碼進(jìn)行測(cè)試:

復(fù)制代碼
 1 #include <stdio.h>
 2 #include <sys/types.h>
 3 #include <unistd.h>
 4 
 5 struct con { 
 6         int a; 
 7 }; 
 8 
 9 int main() { 
10         pid_t pid; 
11         struct con s; 
12         s.a = 2; 
13         struct con* sp = &s; 
14         pid = fork(); 
15         if (pid > 0) { 
16                 printf("parent show %p, %p, a =  %d\n", sp, &sp->a, sp->a); 
17                 sp->a = 1; 
18                 sleep(10); 
19                 printf("parent show %p, %p, a = %d\n", sp, &sp->a, sp->a); 
20                 printf("parent exit\n");
21         } 
22         else { 
23                 printf("child show %p, %p, a = %d\n", sp, &sp->a, sp->a); 
24                 sp->a = -1; 
25                 printf("child change a to %d\n", sp->a); 
26         } 
27         return 0; 
28 }
復(fù)制代碼

獲得結(jié)果如下:

  從上面的分析可以看出進(jìn)程copy過程中,,fork就是基于寫時(shí)復(fù)制,只讀代碼段是可以同享的,,一般CPU都是以"頁"為單位來分配內(nèi)存空間的,,每一個(gè)頁都是實(shí)際物理內(nèi)存的一個(gè)映像,象INTEL的CPU,,其一頁在通常情況下是 4086字節(jié)大小,,而無論是數(shù)據(jù)段還是堆棧段都是由許多"頁"構(gòu)成的,fork函數(shù)復(fù)制這兩個(gè)段,,物理空間上兩個(gè)進(jìn)程的數(shù)據(jù)段和堆棧段都還是共享著的,,當(dāng)有一個(gè)進(jìn)程寫了某個(gè)數(shù)據(jù)時(shí),這時(shí)兩個(gè)進(jìn)程之間的數(shù)據(jù)才有了區(qū)別,,系統(tǒng)就將有區(qū)別的" 頁"從物理上也分開,。系統(tǒng)在空間上的開銷就可以達(dá)到最小。

   2.3 exec( )函數(shù)族

  下面我們來看看一個(gè)進(jìn)程如何來啟動(dòng)另一個(gè)程序的執(zhí)行。在Linux中要使用exec函數(shù)族,。系統(tǒng)調(diào)用execve()對(duì)當(dāng)前進(jìn)程進(jìn)行替換,,替換者為一個(gè)指定的程序,其參數(shù)包括文件名(filename),、參數(shù)列表(argv)以及環(huán)境變量(envp),。exec函數(shù)族當(dāng)然不止一個(gè),但它們大致相同,,在 Linux中,,它們分別是:execl,execlp,,execle,,execv,execve和execvp,,下面以execve為例,。

  一個(gè)進(jìn)程一旦調(diào)用exec類函數(shù),它本身就"死亡"了,,execve首先調(diào)用int80中斷,,然后將系統(tǒng)調(diào)用號(hào)保存在eax寄存器中,調(diào)用sys_exec,,將可執(zhí)行程序加載到當(dāng)前進(jìn)程中,,系統(tǒng)把代碼段替換成新的程序的代碼,廢棄原有的數(shù)據(jù)段和堆棧段,,并為新程序分配新的數(shù)據(jù)段與堆棧段,,唯一留下的,就是進(jìn)程號(hào),,也就是說,,對(duì)系統(tǒng)而言,還是同一個(gè)進(jìn)程,,不過已經(jīng)是另一個(gè)程序了,。(不過exec類函數(shù)中有的還允許繼承環(huán)境變量之類的信息。)

  那么如果我的程序想啟動(dòng)另一程序的執(zhí)行但自己仍想繼續(xù)運(yùn)行的話,,怎么辦呢,?那就是結(jié)合fork與exec的使用。下面一段代碼顯示如何啟動(dòng)運(yùn)行其它程序:

復(fù)制代碼
1 #include <stdio.h>
2 #include <unistd.h>
3 int main(){
4   if(!fork())
5     execve("./test",NULL,NULL);
6   else
7     printf("origin process!\n");
8   return 0;
9 }
復(fù)制代碼

  輸出結(jié)果如下:

    

  原始進(jìn)程和execve創(chuàng)建的新進(jìn)程,,并發(fā)運(yùn)行,,exec函數(shù)在當(dāng)前進(jìn)程的上下文中加載并運(yùn)行一個(gè)新的程序,并且不返回創(chuàng)建進(jìn)程的函數(shù),。

  接下來,,我們分析一下execve函數(shù)執(zhí)行過程中,,以及可執(zhí)行程序的加載過程,在內(nèi)核中execve()系統(tǒng)調(diào)用相應(yīng)的入口是sys_execve(),,函數(shù)首先通過 pt_regs參數(shù)檢查賦值在執(zhí)行該系統(tǒng)調(diào)用時(shí),,用戶態(tài)下的CPU寄存器在核心態(tài)的棧中的保存情況。通過這個(gè)參數(shù),,sys_execve可以獲得保存在用戶空間的以下信息:可執(zhí)行文件路徑的指針(regs.ebx中),、命令行參數(shù)的指針(regs.ecx中)和環(huán)境變量的指針(regs.edx中)。

復(fù)制代碼
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;
}
復(fù)制代碼

  然后調(diào)用do_execve函數(shù),,首先查找被執(zhí)行的文件,,讀取前128個(gè)字節(jié),確實(shí)加載的可執(zhí)行文件的類型,,然后調(diào)用search_binary_handle()搜索和匹配合適的可執(zhí)行文件裝載處理過程,,elf調(diào)用load_elf_binary();

復(fù)制代碼
struct linux_binprm{     
     char buf[BINPRM_BUF_SIZE]; //保存可執(zhí)行文件的頭128字節(jié)               struct page *page[MAX_ARG_PAGES];
     struct mm_struct *mm;
unsigned long p; //當(dāng)前內(nèi)存頁最高地址 int sh_bang; struct file * file; //要執(zhí)行的文件 int e_uid, e_gid; //要執(zhí)行的進(jìn)程的有效用戶ID和有效組ID kernel_cap_t cap_inheritable, cap_permitted, cap_effective;
void *security; int argc, envc; //命令行參數(shù)和環(huán)境變量數(shù)目 char * filename; //要執(zhí)行的文件的名稱 char * interp; //要執(zhí)行的文件的真實(shí)名稱,通常和filename相同 unsigned interp_flags; unsigned interp_data; unsigned long loader, exec; };
復(fù)制代碼

  load_elf_binary()加載過程如下:

  a.檢查ELF可執(zhí)行文件的有效性,,比如魔數(shù)(開頭四個(gè)字節(jié),,elf文件為0x7F),段“Segment”的數(shù)量,;

  b.尋找動(dòng)態(tài)鏈接.interp段,,設(shè)置動(dòng)態(tài)連接器的路徑;

  c.根據(jù)elf可執(zhí)行文件的程序頭表的描述,,對(duì)elf文件進(jìn)行映射,;

  d.初始化elf進(jìn)程環(huán)境,比如啟動(dòng)時(shí)候的edx寄存器地址是DT_FINI的地址,;

  e.將系統(tǒng)調(diào)用的返回地址修改為elf可執(zhí)行文件的入口點(diǎn),,就是e_entry所存的地址。對(duì)于動(dòng)態(tài)鏈接的elf可執(zhí)行文件就是動(dòng)態(tài)連接器,。

  

  加載完成后返回do_execve返回到exeve(),從內(nèi)核態(tài)轉(zhuǎn)化為用戶態(tài)并返回e步所在更改的程序入口地址,。即eip存儲(chǔ)器直接跳轉(zhuǎn)到elf程序的入口地址,新進(jìn)程執(zhí)行,。


 

三,、 進(jìn)程虛擬地址空間與可執(zhí)行程序格式                                                                                                                         

  從操作系統(tǒng)來看,,一進(jìn)程最關(guān)鍵的特征是它擁有獨(dú)立的虛擬地址空間,,一般情況下,創(chuàng)建過程如下:

 ?、賱?chuàng)建一個(gè)獨(dú)立的虛擬空間,。

  ②讀取可執(zhí)行文件頭,,并且簡(jiǎn)歷虛擬空間與可執(zhí)行文件的映射關(guān)系,。

 ?、蹖PU的指令寄存器設(shè)置成可執(zhí)行文件的入口地址,啟動(dòng)運(yùn)行,。

  在討論地址空間,,進(jìn)程描述符以及ELF文件格式的之前,我們先介紹一點(diǎn)預(yù)備知識(shí),,由于第一節(jié)已經(jīng)介紹了進(jìn)程描述符的部分信息,,在這里介紹下ELF文件格式:

  在第二節(jié)使用execve時(shí),我們使用了test可執(zhí)行程序進(jìn)行測(cè)試,,代碼如下:

復(fù)制代碼
#include <stdio.h>

int main(int argc, char const *argv[])
{
    printf("%s\n","execve the new process!");
    return 0;
}
復(fù)制代碼

  描述“Segment”的結(jié)構(gòu)叫程序頭,,它描述了ELF文件該如何被操作系統(tǒng)映射到進(jìn)程的虛擬空間:

          

 

  上圖共有5個(gè)Segment。從裝載的角度看,,我們只關(guān)心兩個(gè)LOAD和DYNAMIC,,其他Segment在裝載過程中只具有輔助作用,映射過程中,,根據(jù)讀寫執(zhí)行權(quán)限映射到不同的虛擬內(nèi)存區(qū)域

    第四行LOAD表示代碼段,,具有可讀可執(zhí)行權(quán)限,被映射到虛擬地址0x08048000,,長度為0x005c4字節(jié)的虛擬存儲(chǔ)區(qū)域中,。

    第五行LOAD表示長度為0x100個(gè)字節(jié)的數(shù)據(jù)段,具有可讀可寫權(quán)限,,被映射到開始于虛擬地址0x08049f08處,,長度為0x0011c字節(jié)的虛擬存儲(chǔ)區(qū)域中。

    DYNAMIC字段表示的是動(dòng)態(tài)鏈接器所需要的基本信息,,具有可讀可寫權(quán)限,,被映射到開始于虛擬地址0x08049f14處,長度為0x000e8字節(jié)的虛擬存儲(chǔ)區(qū)域中,。

  

  在第二節(jié)中執(zhí)行如下命令后,,ELF文件正式開始加載工作,執(zhí)行第二節(jié)中的加載過程:

execve("./test",NULL,NULL);

  文件在加載過程中是以elf可執(zhí)行文件的形式加載,,加載過程初始化時(shí),,根據(jù)elf段頭部表信息,初始化bss段,、代碼段和數(shù)據(jù)段的起始地址和終止地址,。

  然后調(diào)用mm_release釋放掉當(dāng)前進(jìn)程所占用的內(nèi)存(old_mm),并且將當(dāng)前進(jìn)程的內(nèi)存空間替換成bprm->mm所指定的頁面,,而這塊空間,,便是新進(jìn)程在初始化時(shí)暫時(shí)向內(nèi)核借用的存儲(chǔ)空間,當(dāng)這段空間讀取到目前進(jìn)程的mm以后,,事實(shí)上也就完成了舊進(jìn)程到新進(jìn)程的替換,。這個(gè)時(shí)候bprm->mm這塊內(nèi)核空間也就完成了它的使命,,于是被置為NULL予以回收。(bprm為中保存了讀取128字節(jié)elf文件頭),。

  mm指向關(guān)于存儲(chǔ)管理的struct mm_struct結(jié)構(gòu),,其包含在task_struct中。

  然后加載段地址到虛擬內(nèi)存地址,,映射如下:

  

  然后另一部分段映射到數(shù)據(jù)區(qū),,關(guān)系如下:

  到這里,對(duì)于elf文件的載入(包括之前對(duì)可執(zhí)行文件運(yùn)行環(huán)境準(zhǔn)備工作)的分析基本上可以告一段落了,。


 

 

四,、進(jìn)程創(chuàng)建中動(dòng)態(tài)鏈接庫的表現(xiàn)形式                                                                                                                           

 

  動(dòng)態(tài)鏈接的基本思想是把程序按照模塊拆分,運(yùn)行時(shí)才將它們鏈接在一起形成一個(gè)完整的程序,,而不是像靜態(tài)鏈接一樣把所有的程序模塊都鏈接成一個(gè)單獨(dú)的可執(zhí)行文件,。多個(gè)動(dòng)態(tài)鏈接庫均以ELF文件存儲(chǔ),執(zhí)行過程中以依賴樹的關(guān)系存在,,并以深度優(yōu)先的方式加載動(dòng)態(tài)鏈接庫,,最終將可執(zhí)行程序返回給用戶。

  我們通過以下實(shí)例來測(cè)試動(dòng)態(tài)鏈接庫在虛擬地址及ELF文件的中表現(xiàn)形式:

復(fù)制代碼
/* Lib.h */
#ifndef LIB_H
#define LIB_H

void lab(int i)

#endif
復(fù)制代碼

 

復(fù)制代碼
/* Lib.c */
#include <stdio.h>

void lab(int i){
    printf("Printing from lib.so %d\n", i);
  sleep(-1); }
復(fù)制代碼

 

復(fù)制代碼
/* dyn.c*/
#include "lib.h"

int main(){
    lab(1);
    return 0;
}
復(fù)制代碼

  使用gcc編譯生成一個(gè)共享對(duì)象文件,,然后鏈接dyn.c程序,,生成可執(zhí)行文件dyn:

gcc -fPIC -shared -o lib.so lib.c
gcc -o dyn dyn.c ./lib.so

  運(yùn)行并查看進(jìn)程的虛擬地址空間分布:

 

  整個(gè)進(jìn)程的虛擬地址空間中,多出了幾個(gè)文件的映射,。dyn與lib.so一樣,,都被系統(tǒng)映射到進(jìn)程的虛擬地址空間,地址與長度均不相同,。由第二節(jié)可知,,在映射完可執(zhí)行文件之后,操作系統(tǒng)會(huì)先啟動(dòng)一個(gè)動(dòng)態(tài)鏈接器,。

  動(dòng)態(tài)鏈接器的的位置由ELF文件中的“.interp”段決定,,而段“.dynamic”為動(dòng)態(tài)鏈接提供了:依賴哪些共享對(duì)象、動(dòng)態(tài)鏈接符號(hào)表的位置,,動(dòng)態(tài)鏈接重定位表的位置,、共享對(duì)象初始化代碼的地址等??赏ㄟ^readelf查看".dynamic" 段的內(nèi)容:

       動(dòng)態(tài)鏈接過程需要?jiǎng)討B(tài)符號(hào)表來確定函數(shù)的定義和引用關(guān)系,,還需要重定位表來修正導(dǎo)入符號(hào)的引用。初始化完成后堆棧中保存了動(dòng)態(tài)連接器所需要的一些輔助信息數(shù)組(其中包括程序入口地址,,程序表頭地址,,程序表頭項(xiàng)數(shù)及大?。?。動(dòng)態(tài)鏈接庫最后被映射到進(jìn)程地址空間的共享庫區(qū)域段,。

  完成重定位和初始化后,所有準(zhǔn)備工作結(jié)束,,所需要的共享對(duì)象也都已經(jīng)裝載并且鏈接完成,。最后將進(jìn)程的控制權(quán)轉(zhuǎn)交給dyn程序的入口并開始執(zhí)行。

 


  以上內(nèi)容均為個(gè)人理解,,由于能力有限,,可能會(huì)有諸多錯(cuò)誤,希望能夠和大家一起討論修正,。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多