实验二:进程的创建与可执行程序的加载
學號:SA*****259 姓名:呂良
?關鍵字:fork() exec() task_struct 進程地址空間 ELF文件格式 動態鏈接庫
?
實驗總結(應該說是回答實驗問題,因為300~500字真的不夠,還是看附錄吧)
一、fork和exec系統調用在內核中的執行過程
1>libc庫對系統調用進行了封裝,在執行int $0x80進入內核之前,封裝例程就已經把系統調用號裝入eax寄存器了。
2>當用戶態進程發出int $0x80指令時,CPU切換到內核態并開始從地址system_call處開始執行指令。( 在trap_init()中:set_system_gate(0x80,&system_call);?)
3>system_call()函數:
- 首先把系統調用號和這個異常處理程序可以用到的所有CPU寄存器保存到相應的棧中。
- 如果系統調用號無效,該函數就把-ENOSYS值存放在棧中曾保存eax寄存器的單元中。當進程恢復它在用戶態的執行時,會在eax中發現一個負的返回碼。
- 如果系統調用號有效,則調用與eax中所包含的系統調用號對應的特定服務例程:call *sys_call_table(0,%eax,4);
二、exec系統調用返回到用戶態時EIP指向的位置
EIP指向ELF可執行文件的入口點,這個入口點取決于程序的鏈接方式:
- 對于靜態鏈接的ELF可執行文件,這個程序入口點就是ELF文件的文件頭中e_entry所指的地址;
- 對于動態鏈接的ELF可執行文件,這個程序入口點就是動態鏈接器。
三、task_struct進程控制塊,ELF文件格式與進程地址空間的聯系(詳見附錄)
四、動態鏈接庫在ELF文件格式中與進程地址空間中的表現形式(詳見附錄)
?
附錄導讀:
?
?
附錄?
一、進程的創建--fork()
do_fork()函數負責處理clone()、fork()和vfork()系統調用
1、為子進程分配新的PID
2、調用copy_process()復制進程描述符task_struct
a.執行alloc_task_struct()宏,為新進程獲取進程描述符(task_struct結構)
b.執行alloc_thread_info宏以獲取一塊空閑內存區,用來存放新進程的thread_info結構和內核棧
3、設置子進程的狀態,并將其插入到對應的進程隊列
4、結束并返回子進程的PID
?
二、進程描述符task_struct-----進程存在的唯一標識
進程描述符都是task_struct類型結構,它的字段包含了與一個進程相關的所有信息。
打開/include/linux/sched.h可以找到task_struct 的定義
? ? ?? ? ? ? ? ? ??
?
三、mm_struct-----task_struct與進程地址空間的紐帶
這里有必要先介紹一下進程的地址空間。進程的地址空間是一個線性地址空間。在一個帶虛擬存儲器的系統中,CPU從一個有N=2^n個地址的地址空間中生成虛擬地址,這個地址空間成為虛擬地址空間。說到底,進程地址空間是虛擬地址空間,這使得每個進程都有一個獨立的進程地址空間。要注意區別進程地址空間與物理內存地址,這是兩個完全不同的概念,它倆是通過頁表聯系起來的。
? ? ?
創建進程的地址空間
當創建一個新的進程時內核調用copy_mm()函數。這個函數通過建立新進程的所有頁表和內存描述符來創建進程的地址空間。
mm_struct
在task_struct中有一個名為mm的mm_struct數據結構,這個數據結構叫做內存描述符,包含了與進程地址空間有關的全部信息。
struct mm_struct {struct vm_area_struct * mmap; /* list of VMAs */struct rb_root mm_rb;struct vm_area_struct * mmap_cache; /* last find_vma result */unsigned long (*get_unmapped_area) (struct file *filp,unsigned long addr, unsigned long len,unsigned long pgoff, unsigned long flags);void (*unmap_area) (struct vm_area_struct *area);unsigned long mmap_base; /* base of mmap area */unsigned long free_area_cache; /* first hole */pgd_t * pgd;atomic_t mm_users; /* How many users with user space? */atomic_t mm_count; /* How many references to "struct mm_struct" (users count as 1) */int map_count; /* number of VMAs */struct rw_semaphore mmap_sem;spinlock_t page_table_lock; /* Protects page tables, mm->rss, mm->anon_rss */struct list_head mmlist; /* List of maybe swapped mm's. These are globally strung* together off init_mm.mmlist, and are protected* by mmlist_lock*/unsigned long start_code, end_code, start_data, end_data;unsigned long start_brk, brk, start_stack;unsigned long arg_start, arg_end, env_start, env_end;unsigned long rss, anon_rss, total_vm, locked_vm, shared_vm;unsigned long exec_vm, stack_vm, reserved_vm, def_flags, nr_ptes;unsigned long saved_auxv[42]; /* for /proc/PID/auxv */unsigned dumpable:1;cpumask_t cpu_vm_mask;/* Architecture-specific MM context */mm_context_t context;/* Token based thrashing protection. */unsigned long swap_token_time;char recent_pagein;/* coredumping support */int core_waiters;struct completion *core_startup_done, core_done;/* aio bits */rwlock_t ioctx_list_lock;struct kioctx *ioctx_list;struct kioctx default_kioctx;unsigned long hiwater_rss; /* High-water RSS usage */unsigned long hiwater_vm; /* High-water virtual memory usage */ };?
進程的地址空間都是通過task_struct中的mm_struct結構來管理的
?
mm_struct中的兩個字段:pgd和mmap
pgd指向第一級頁表(頁全局目錄)的基址,當內核運行這個進程時,它就將pgd存放在CR3控制寄存器中
mmap指向一個vm_area_struct(區域結構)的鏈表,其中每個vm_area_struct都描述了當前虛擬地址空間的一個區域(area)
vm_area_struct(一個具體區域的區域結構)包含下面的字段:
vm_start:指向這個區域的起始處。
vm_end:指向這個區域的結束處。
vm_prot:描述這個區域內包含的所有頁的讀寫許可權限。
vm_flag:描述這個區域內的頁面是與其他進程共享的,還是這個進程私有的(還描述了其他一些信息)。
vm_next:指向鏈表中下一個區域結構。
?
四、exec() -----進程地址空間與ELF文件的紐帶
當我們利用fork()創建了一個子進程時,子進程雖然有自己獨立的進程地址空間,但它卻還是生活在父進程的陰影中,它所有的東西都copy自父進程,它就是父進程的副本,父進程的影子。如果子進程一直這樣,那么我們創建它是毫無意義。創建一個進程,就是要讓它完成一件新事情---對滴,這就是exec()的用武之地。
/* hello.c gcc hello.c -o hello */#include<stdio.h> int main() {printf("hello\n");while(1){}return 0;
}
exec()的功能就是加載和運行可執行文件,格式如下:
execve("\usr\bin\hello",NULL,NULL);
我們首先來看一下ELF可執行文件格式
- ELF頭部:描述文件的總體格式。它還包括程序的入口點(entry point),也就是當程序運行時要執行的第一條指令的地址。
- 段頭部表:描述可執行文件與虛擬存儲器(也就是進程地址空間)的映射關系,可執行文件連續的片(chunk)被映射到連續的虛擬存儲器段。
- .init:定義了一個小函數,叫做_init,程序的初始化代碼會調用它。
- .text:已編譯程序的機器代碼。
- .rodata:只讀數據。
- .data:已初始化的全局C變量。
- .bss:未初始化的全局變量。
- .symtab:符號表,存放程序中定義和引用的函數和全局變量的信息。
- .debug:調試符號表,其條目是程序中定義的局部變量和類型定義,程序中定義和引用的全局變量,以及原始的C源文件。
- .line:原始C源程序中的行號和.text節中機器指令之間的映射。
- .strtab:字符串表,其內容包括.symtab和.debug節中的符號表,以及節頭部中的節名字。
?
exec是如何將ELF可執行程序映射到進程的用戶地址空間區域的呢?
加載并運行hello需要以下幾個步驟:
- 刪除已存在的用戶區域。刪除當前進程虛擬地址的用戶部分中的已存在的區域結構。
- 映射私有區域。為新程序的文本、數據、bss和棧區域創建新的區域結構。
- 映射共享區域。如果hello程序與共享對象(或目標)鏈接,比如標準C庫libc.so,那么這些對象都是動態鏈接到這個程序的,然后再映射到用戶虛擬地址空間中的共享區域內。
- 設置程序計數器(PC)。exec做的最后一件事情就是設置當前進程上下文中的程序計數器,使之指向文本區域的入口點。
?
五、動態鏈接庫在ELF文件格式中與進程地址空間中的表現形式
?
?
1>動態鏈接器在ELF文件中的表現形式?
exec()加載和運行可執行文件hello時,它是怎么知道hello需要鏈接動態鏈接庫libc.so的呢?它又是怎樣找到動態鏈接庫libc.so的呢?
當exec加載部分鏈接的可執行文件hello時,它注意到hello包含一個.interp段(如下圖所示),這說明該ELF文件需要動態鏈接。
.interp段
".interp"的內容很簡單,里面保存的就是一個字符串,這個字符串就是可執行文件所需要的動態鏈接器的路徑,動態連接器本身就是一個共享目標(比如,在Linux系統上的LD-LINUX.SO)。
.dynamic段
.dynamic段保存了動態鏈接器所需要的基本信息,比如依賴于哪些共享對象、動態鏈接符號表的位置、動態鏈接重定位表的位置、共享對象初始化代碼的地址等。
.dynamic段的結構定義在“elf.h”中:
typedef struct {Elf32_Sword d_tag;union {Elf32_Word d_val;Elf32_Addr d_ptr;} d_un; } Elf32_Dyn;有了.interp段和.dynamic段的信息,加載器就可以工作了。
加載器不再像它通常那樣將控制傳遞給應用程序,而是加載和運行這個動態鏈接器。
然后,動態鏈接器通過執行下面的重定位完成鏈接任務:
- 重定位libc.so的文本和數據到某個存儲器段。
- 重定位hello中所有對由libc.so定義的符號的引用。
最后,動態鏈接器將控制傳遞給應用程序(也就是hello)。從這個時刻開始,動態鏈接庫的位置就固定了,并且在程序執行的過程中都不會改變。
?
?
?
2>動態鏈接庫在進程地址空間的表現形式
執行上面的hello程序,獲取進程的PID,然后查看進程的地址空間,如下圖:
從圖中可以看到ELF可執行文件hello的各個段、動態鏈接庫libc-2.17.so、動態鏈接器ld-2.17.so在進程地址空間中的映射位置。
注意一下,動態鏈接庫libc-2.17.so和hello的各個段不是相鄰的,它應該是位于運行時堆和用戶棧之間,如下圖所示。
?
? ? ? ?轉載于:https://www.cnblogs.com/lvl-ustc/archive/2013/05/28/3104825.html
總結
以上是生活随笔為你收集整理的实验二:进程的创建与可执行程序的加载的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阿里要未来,百度要利益?
- 下一篇: hive ALLOW_UNQUOTED_