【Linux 1.0核心原始碼剖析】執行程式——exec.c
父程序 fork的子程序的目的自然不是建立一個幾乎與自己一模一樣的程序。而是通過子程序呼叫 exec 函式簇去執行另外一個程式。exec() 系統呼叫必須定位該執行檔案的二進位制映像,載入並執行它。
exec() 的Linux實現支援不同的二進位制格式,這是通過 linux_binfmt 結構來達到的,其中內嵌了兩個指向函式的指標,一個用於載入可執行檔案,另一個用於載入庫函式,每種二進位制格式都實現有這兩個函式。
Linux的核心是單獨實現 sys_execve() 呼叫的,它執行一個非常簡單的任務:載入可執行檔案的頭部,並試著去執行它。如果頭兩個位元組是 “#!”,那麼就會解析該可執行檔案的第一行並呼叫一個直譯器來執行它,否則的話,就會順序地呼叫各個註冊過的二進位制格式。/* This structure defines the functions that are used to load the binary formats that * linux accepts. */ struct linux_binfmt{ int (*load_binary)(struct linux_binprm *, struct pt_regs * regs); int (*load_shlib)(int fd); };
Linux 本身的格式是由 fs/exec.c 直接支援的,並且相關的函式是 load_aout_binary 和 load_aout_library。對於二進位制,函式將載入一個“a.out” 可執行檔案(Linux 環境下程式設計 gcc 生成的可執行檔案預設就是 a.out)並以使用 mmap() 載入磁碟檔案或呼叫 read_exec() 而結束。
Linux1.0(如無特殊說明,本系列均指Linux1.0 核心環境下)。其執行順序為: execve() -> sys_execve() -> do_execve()
/* * sys_execve() executes a new program. */ //系統呼叫的時候,把引數依次放在:ebx,ecx,edx,esi,edi,edp暫存器中 asmlinkage int sys_execve(struct pt_regs regs) { int error; char * filename; //在系統空間建立一個路徑名的副本 error = getname((char *) regs.ebx, &filename); if (error) return error; //執行一個新的程式 error = do_execve(filename, (char **) regs.ecx, (char **) regs.edx, ®s); //釋放路徑名副本 putname(filename); return error; }
/*
* sys_execve() executes a new program.
*/
static int do_execve(char * filename, char ** argv, char ** envp, struct pt_regs * regs)
{
//二進位制程式結構宣告
struct linux_binprm bprm;//用於存放載入二進位制檔案時用的引數
struct linux_binfmt * fmt;//定義了Linux接受的被用於載入二進位制格式的函式,為函式結構體指標
unsigned long old_fs;
int i;
int retval;
int sh_bang = 0;
//如果regs所指的堆疊內容中的cs不是使用者態的值,返回
if (regs->cs != USER_CS)
return -EINVAL;
//引數和環境字串空間中的偏移指標,初始化為最後一個位元組處
bprm.p = PAGE_SIZE*MAX_ARG_PAGES-4;
for (i=0 ; i<MAX_ARG_PAGES ; i++) /* clear page-table */
bprm.page[i] = 0;//清頁面
//開啟檔案程式,bprm.inode 為對應檔案路徑名的i 節點指標
retval = open_namei(filename, 0, 0, &bprm.inode, NULL);
if (retval)
return retval;
bprm.filename = filename;//檔名
bprm.argc = count(argv);//引數個數
bprm.envc = count(envp);//環境變數個數
restart_interp:
//判斷開啟檔案的有效性,必須是常規檔案,可執行程式,許可權允許等,
if (!S_ISREG(bprm.inode->i_mode)) { /* must be regular file */
retval = -EACCES;
goto exec_error2;
}
if (IS_NOEXEC(bprm.inode)) { /* FS mustn't be mounted noexec */
retval = -EPERM;
goto exec_error2;
}
if (!bprm.inode->i_sb) {
retval = -EACCES;
goto exec_error2;
}
i = bprm.inode->i_mode;
if (IS_NOSUID(bprm.inode) && (((i & S_ISUID) && bprm.inode->i_uid != current->
euid) || ((i & S_ISGID) && !in_group_p(bprm.inode->i_gid))) &&
!suser()) {
retval = -EPERM;
goto exec_error2;
}
/* make sure we don't let suid, sgid files be ptraced. */
//如果當前程序被置PF_PTRACED位,則設定結構中的u_uid和e_gid為當前程序中的值
if (current->flags & PF_PTRACED) {
bprm.e_uid = current->euid;
bprm.e_gid = current->egid;
} else {//否則取前面獲得的檔案型別和屬性值
bprm.e_uid = (i & S_ISUID) ? bprm.inode->i_uid : current->euid;
bprm.e_gid = (i & S_ISGID) ? bprm.inode->i_gid : current->egid;
}
//檢查將要執行檔案的檔案許可權
if (current->euid == bprm.inode->i_uid)
i >>= 6;
else if (in_group_p(bprm.inode->i_gid))
i >>= 3;
//判斷檔案是否具有執行許可權
if (!(i & 1) &&
!((bprm.inode->i_mode & 0111) && suser())) {
retval = -EACCES;
goto exec_error2;
}
//清空結構中的緩衝區
memset(bprm.buf,0,sizeof(bprm.buf));
//取得fs暫存器值
old_fs = get_fs();
//重置fs暫存器的內容為核心資料段
set_fs(get_ds());
//讀取可執行檔案,檔案路徑名,引數等資訊放入buf中
retval = read_exec(bprm.inode,0,bprm.buf,128);
//設定fs為老的保留的值,恢復現場
set_fs(old_fs);
if (retval < 0)
goto exec_error2;//讀取出錯處理
//如果執行檔名開始的兩個位元組為#!,並且sh_bang為0,則處理指令碼檔案的執行
if ((bprm.buf[0] == '#') && (bprm.buf[1] == '!') && (!sh_bang)) {
/*
* This section does the #! interpretation.
* Sorta complicated, but hopefully it will work. -TYT
*/
char *cp, *interp, *i_name, *i_arg;
//釋放該執行檔案的i 節點
iput(bprm.inode);
bprm.buf[127] = '\0';//最後位設定0,換句話說讀取前128位元組,判斷檔案格式
//查詢bprm.buf中是否含有換行符,如果沒有則讓cp指向最後一個字元處
if ((cp = strchr(bprm.buf, '\n')) == NULL)
cp = bprm.buf+127;
*cp = '\0';//如果有則設定為0,找到第一個換行符替換為0
//刪除改行的空格以及製表符,替換為0
while (cp > bprm.buf) {
cp--;
if ((*cp == ' ') || (*cp == '\t'))
*cp = '\0';
else
break;
}
//檢查檔名,前面兩個為#!
for (cp = bprm.buf+2; (*cp == ' ') || (*cp == '\t'); cp++);
//如果檔名為空,則轉到exec_error1處
if (!cp || *cp == '\0') {
retval = -ENOEXEC; /* No interpreter name found */
goto exec_error1;
}
//讓i_name指向程式名,如果有/指向最後一個,(/表示目錄,如果有目錄程式名則在最後)
//第一個字串表示的指令碼解釋程式名
interp = i_name = cp;
i_arg = 0;
for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++) {
if (*cp == '/')
i_name = cp+1;
}
//如果檔名後面還有字元的話,則應該是引數,讓i_reg指向它
while ((*cp == ' ') || (*cp == '\t'))
*cp++ = '\0';
if (*cp)
i_arg = cp;
/*
* OK, 我們已經解析出程式的檔名以及(可選)引數
*/
//若sh_bang標誌沒有設定,則設定它,並複製指定個數的環境變數串和引數串到環境和引數空間中
//sh是程式為指令碼程式的標誌位
if (sh_bang++ == 0) {
bprm.p = copy_strings(bprm.envc, envp, bprm.page, bprm.p, 0);
bprm.p = copy_strings(--bprm.argc, argv+1, bprm.page, bprm.p, 0);
}
/*
* Splice in (1) the interpreter's name for argv[0]存放解釋程式名
* (2) (optional) argument to interpreter可選引數
* (3) filename of shell script指令碼程式的名字
*
* This is done in reverse order, because of how the
* user environment and arguments are stored.
*/
//複製指令碼程式檔名到引數和環境空間中
bprm.p = copy_strings(1, &bprm.filename, bprm.page, bprm.p, 2);
bprm.argc++;
//複製解釋程式的引數到引數和環境空間中
if (i_arg) {
bprm.p = copy_strings(1, &i_arg, bprm.page, bprm.p, 2);
bprm.argc++;
}
//複製解釋程式檔名到引數和環境空間中
bprm.p = copy_strings(1, &i_name, bprm.page, bprm.p, 2);
bprm.argc++;
if (!bprm.p) {
retval = -E2BIG;
goto exec_error1;
}
/*
* OK, now restart the process with the interpreter's inode.
* Note that we use open_namei() as the name is now in kernel
* space, and we don't need to copy it.
*/
//開啟檔案
retval = open_namei(interp, 0, 0, &bprm.inode, NULL);
if (retval)
goto exec_error1;
goto restart_interp;
}
//如果sh_bang標誌沒有被設定,則複製指定個數的環境變數字串到引數
if (!sh_bang) {
bprm.p = copy_strings(bprm.envc,envp,bprm.page,bprm.p,0);
bprm.p = copy_strings(bprm.argc,argv,bprm.page,bprm.p,0);
if (!bprm.p) {
retval = -E2BIG;
goto exec_error2;
}
}
//如果sh標誌已經被設定了,則表明改程式是指令碼程式,這個時候的環境變數頁面已經被複制了
bprm.sh_bang = sh_bang;
fmt = formats;//讓fmt執行對應的格式,即下面說明的三種格式之一
//讓fn指向對應的載入二進位制函式
//Linux1.0只支援3中二進位制檔案格式:a.out,elf,coff
do {
//函式指標賦值
int (*fn)(struct linux_binprm *, struct pt_regs *) = fmt->load_binary;
if (!fn)
break;
//載入程式,對應資訊均位於bprm和regs中了
retval = fn(&bprm, regs);
//如果載入成功了,釋放該i節點後返回
if (retval == 0) {
iput(bprm.inode);
current->did_exec = 1;
return 0;
}
fmt++;//遍歷下一個格式,下一個就是載入共享庫,一共只有兩次載入機會
} while (retval == -ENOEXEC);
//出錯處理
exec_error2:
iput(bprm.inode);
exec_error1:
for (i=0 ; i<MAX_ARG_PAGES ; i++)
free_page(bprm.page[i]);
return(retval);
}
現在來講講上面 do_execve() 函式的執行情況:
1、do_execve() 開始執行後,馬上處理引數和環境變數,目的就是要將引數和環境變數載入到記憶體的頁面中,為了對引數和環境變數可能佔用的記憶體頁面進行管理。do_execve() 函式先對page管理結構清0(18-19行)。
2、系統想要對檔案進行全方位檢測,就先要對檔案的 i 節點中提供的檔案屬性資訊進行分析,檔案自身的所有屬性都記載在 i 節點中,其具體步驟是:呼叫 open_namei() 函式,從虛擬盤上讀取檔案的 i 節點,該函式是通過檔案路徑(如“/bin/sh”),最終找到該檔案的 i 節點,並將其登記到 i 節點管理表中。(21行)
3、接下來對引數的個數和環境變數的個數進行統計(25-26行),這是因為,引數和環境變數是系統給使用者預留的自由設定資訊。預設設定是 init/main.c 中的(Linux1.0)
static char * argv_rc[] = { "/bin/sh", NULL };
static char * envp_rc[] = { "HOME=/", term, NULL };
4、接下來開始對檔案的檢測工作,對 i 節點進行分析。通過對 i 節點中“檔案屬性”的分析,可以得知這個檔案是不是一個“常規檔案”。只有常規檔案(除塊裝置檔案、字元裝置檔案、目錄檔案、管道檔案等特殊檔案外的檔案)才有被載入的可能。以及根據 i 節點提供的:inode->i_mode、inode->i_uid、inode->i_gid,檢測當前檔案是否有執行檔案的許可權(30-68行)
5、對檔案頭進行檢測,先將 inode對應資訊讀入緩衝區中(76行),然後對檔案頭進行分析,如果是指令碼檔案(82行),進入if 裡面執行,解析該可執行檔案的第一行(截斷了換行符後面的內容)並呼叫一個直譯器來執行它。(88-125行),否則直接執行後面的程式碼(168行開始)
6、將環境變數和引數拷貝到記憶體指定的頁面中
7、載入二進位制格式檔案(181-195行)
上面do_execve() 函式中 fmt = formats(178行),這裡指明載入方式(Linux1.0 支援三種格式:a.out、elf、coff)
/* Here are the actual binaries that will be accepted */
struct linux_binfmt formats[] = {
{load_aout_binary, load_aout_library},
#ifdef CONFIG_BINFMT_ELF
{load_elf_binary, load_elf_library},
#endif
#ifdef CONFIG_BINFMT_COFF
{load_coff_binary, load_coff_library},
#endif
{NULL, NULL}
};
經過183和187行,這樣執行載入對應二進位制格式的函式(exec.c中只有load_aout_binary 和 load_aout_library)
如果是之前子程序沒有和父程序拉開區別,那麼從載入二進位制檔案開始,子程序就真正開始它的旅途。首先得和父程序“撇清”關係,放棄從父程序繼承而來的全部空間,不管是通過複製還是通過共享,統統放棄,建立自己獨立的空間
/*
* These are the functions used to load a.out style executables and shared
* libraries. There is no binary dependent code anywhere else.
*/
/*該函式用於載入a.out型別的二進位制檔案格式和共享庫。沒有任何的二進位制依賴程式碼*/
int load_aout_binary(struct linux_binprm * bprm, struct pt_regs * regs)
{
struct exec ex;
struct file * file;
int fd, error;
unsigned long p = bprm->p;
//取得執行檔案的頭
ex = *((struct exec *) bprm->buf); /* exec-header */
/*繼續對檔案頭部各種資訊進行判斷*/
//不是需要分頁可執行檔案(ZMAGIC)
if ((N_MAGIC(ex) != ZMAGIC && N_MAGIC(ex) != OMAGIC &&
N_MAGIC(ex) != QMAGIC) ||
ex.a_trsize || ex.a_drsize ||//程式碼、資料重定位部分長度不等於0
//可執行檔案長度小於程式碼段+資料段+符號表長度+執行頭部分長度的總和
bprm->inode->i_size < ex.a_text+ex.a_data+ex.a_syms+N_TXTOFF(ex)) {
return -ENOEXEC;
}
//執行頭部分檔案長度限制
if (N_MAGIC(ex) == ZMAGIC &&
(N_TXTOFF(ex) < bprm->inode->i_sb->s_blocksize)) {
printk("N_TXTOFF < BLOCK_SIZE. Please convert binary.");
return -ENOEXEC;
}
//檔案頭部分長度不等於一個緩衝塊大小,不能執行
if (N_TXTOFF(ex) != BLOCK_SIZE && N_MAGIC(ex) == ZMAGIC) {
printk("N_TXTOFF != BLOCK_SIZE. See a.out.h.");
return -ENOEXEC;
}
/* 釋放從父程序繼承來的使用者空間,與父程序"決裂"*/
flush_old_exec(bprm);//沖刷所有當前執行程式的痕跡,以便新程式開始執行
//各種初始化
current->end_code = N_TXTADDR(ex) + ex.a_text;//設定當前程序程式碼段的大小
current->end_data = ex.a_data + current->end_code;//設定當前程序資料段的大小
current->start_brk = current->brk = current->end_data;//設定當前程序brk段的大小
current->start_code += N_TXTADDR(ex);//設定start_code
//各種值設定
current->rss = 0;
current->suid = current->euid = bprm->e_uid;
current->mmap = NULL;
current->executable = NULL; /* for OMAGIC files */
current->sgid = current->egid = bprm->e_gid;
//根據a.out具體的不同格式,以不同的方式裝入程式碼段和資料段
//如果程式碼和資料段是緊跟在頭部後面
if (N_MAGIC(ex) == OMAGIC) {
do_mmap(NULL, 0, ex.a_text+ex.a_data,
PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_FIXED|MAP_PRIVATE, 0);
read_exec(bprm->inode, 32, (char *) 0, ex.a_text+ex.a_data);
} else {//如果不是
if (ex.a_text & 0xfff || ex.a_data & 0xfff)//沒有按頁對齊
printk("%s: executable not page aligned\n", current->comm);
fd = open_inode(bprm->inode, O_RDONLY);//以只讀方式開啟節點
if (fd < 0)
return fd;
file = current->filp[fd];//讓file指向檔案描述符指標
//如果檔案操作函式為空或者 mmap 為空,則關閉上面開啟的檔案描述符
if (!file->f_op || !file->f_op->mmap) {
sys_close(fd);
do_mmap(NULL, 0, ex.a_text+ex.a_data,
PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_FIXED|MAP_PRIVATE, 0);
read_exec(bprm->inode, N_TXTOFF(ex),
(char *) N_TXTADDR(ex), ex.a_text+ex.a_data);
goto beyond_if;
}
//重新對映該檔案
error = do_mmap(file, N_TXTADDR(ex), ex.a_text,
PROT_READ | PROT_EXEC,
MAP_FIXED | MAP_SHARED, N_TXTOFF(ex));
//如果返回值不等於檔案程式碼段載入到記憶體後的地址,則關閉描述符,併發送SIGSEGV訊號
if (error != N_TXTADDR(ex)) {
sys_close(fd);
send_sig(SIGSEGV, current, 0);
return 0;
};
//出錯則重新對映
error = do_mmap(file, N_TXTADDR(ex) + ex.a_text, ex.a_data,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FIXED | MAP_PRIVATE, N_TXTOFF(ex) + ex.a_text);
sys_close(fd);
//同樣如果,對映地址出錯
if (error != N_TXTADDR(ex) + ex.a_text) {
send_sig(SIGSEGV, current, 0);
return 0;
};
//executable為程序管理結構中的成員,表示該程序所對應的可執行檔案的i節點指標
current->executable = bprm->inode;//指向inode,建立關係
bprm->inode->i_count++;
}
beyond_if:
sys_brk(current->brk+ex.a_bss);
//修改idt表中描述符基地址和段限長
p += change_ldt(ex.a_text,bprm->page);
p -= MAX_ARG_PAGES*PAGE_SIZE;
//在新的使用者堆疊中建立環境和引數變數指標表,並返回該堆疊指標
p = (unsigned long) create_tables((char *)p,bprm->argc,bprm->envc,0);
current->start_stack = p;
//設定eip為可執行檔案的入口點
regs->eip = ex.a_entry; /* eip, magic happens :-) */
regs->esp = p; /* stack pointer */
if (current->flags & PF_PTRACED)
send_sig(SIGTRAP, current, 0);
return 0;
}
ok,最後大致的總結一下:
1、sys_execve(),系統呼叫,讓程序執行某個程式。首先在系統空間中建立一個路徑名的副本,然後帶著這個路徑名資訊去執行do_execve()
上面建立副本是通過 getname() 函式來實現的,該函式首先分配一個物理頁面作為緩衝區,然後在系統空間中定義一個緩衝指標指向他,將路徑名從使用者空間複製到緩衝區,再用緩衝區指標指向這個物理頁面。其實就是將使用者空間的資料複製到系統空間的過程。
2、do_execve(),首先開啟可執行檔案(open_namei),然後初始化bprm這個用來儲存可執行檔案上下文的資料結構,從可執行檔案中讀取頭128個位元組到bprm緩衝區,這128個位元組包含了關於可執行檔案屬性的必要資訊,然後解析,將其中的檔案路徑名從系統空間複製到bprm,再將引數,環境變數從使用者空間複製到bprm
3、load_aout_binary(),載入二進位制格式的可執行檔案。這裡直接是載入a.out格式的可執行檔案。首先進行各種檢查,格式對不對,大小對不對,可否執行等等。然後沖刷掉繼承而來的父程序的使用者空間,開始與父程序真正區別,設定程式碼段,資料段,bss段等等初始化操作。然後根據a.out具體的不同格式,以不同的方式裝入程式碼段和資料段。對映地址。然後與可執行檔案建立關係。
在使用者空間的堆疊區頂部建立一個虛擬記憶體空間,將引數與環境變數所佔的物理頁面與這些虛擬空間建立對映。
4、調整eip值,使之指向程式的第一條指令處,即程式的入口處。暫存器ip存放的就是下一條將要執行的指令地址。所以下一步系統將依據eip提供的指令地址繼續執行,也就是新程式。
ok,fork子程序後,子程序載入二進位制格式檔案執行新程式的大致流程就是這樣了。當然exec.c檔案中還有一部分函式,請參考原始檔。
參考資料《LInux 核心設計的藝術》
相關推薦
【Linux 1.0核心原始碼剖析】執行程式——exec.c
父程序 fork的子程序的目的自然不是建立一個幾乎與自己一模一樣的程序。而是通過子程序呼叫 exec 函式簇去執行另外一個程式。exec() 系統呼叫必須定位該執行檔案的二進位制映像,載入並執行它。 exec() 的Linux實現支援不同的二進位制格式,這是通過 linux
【秒懂Java】【第1章_初識Java】01_程式語言
各位小夥伴們好哇!從今日起,我將開始更新[《秒懂Java》](https://www.cnblogs.com/mjios/category/1789484.html)系列文章,從0開始講解Java的方方面面,後面也將推出**配套的視訊版**,歡迎大家保持關注! - 我會盡力辦到:在保證通俗易懂的同時,不丟失知
【Linux 核心網路協議棧原始碼剖析】socket.c——BSD Socket層(1)
寫在前面:本系列文章先把各個層對應的檔案原始碼剖析一遍,最後再穿插起來,理清整個協議棧網路資料包的上下傳送通道,從整體實現上進行把握。 圖片來源於《Linux 核心網路棧原始碼情景分析》 更上層函式:tcp socket函式介紹。本篇則是介紹BSD Sock
【原始碼剖析】Launcher 8.0 原始碼 (1) --- Launcher 啟動流程 綜述
現在網上關於Launcher啟動流程的原始碼分析流傳最多的是google Launcher2.0的啟動流程。截止2018年5月,google Launcher已經到了8.0版本。 經對比,8.0和2.0的啟動流程大同小異,整體流程依然保留了2.0的結構特徵,以Launc
【Linux 核心網路協議棧原始碼剖析】bind 函式剖析
socket 函式並沒有為套接字繫結本地地址和埠號,對於伺服器端則必須顯性繫結地址和埠號。bind 函式主要是伺服器端使用,把一個本地協議地址賦予套接字。 1、應用層——bind 函式 #include <sys/socket.h> int bind(int
【原始碼剖析】Launcher 8.0 原始碼 26---使用者操作(3)拖拽模式之springload
接下來是第三種狀態,springloader模式,也是drag模式。 到此刻使用者的操作有3中,點選,滑動,長按。 點選是觸發onclick,滑動是GroupView自帶方法,而長按一種是進入overview或allapp模式,另外一種就是接下來學習的drag模式。
【原始碼剖析】Launcher 8.0 原始碼 25---使用者操作(2)模式切換
模式就是介面,除普通模式外,Launcher還有兩個特殊模式,分別是overView模式和Springloader模式。此處採用狀態模式這種設計模式,共有三個狀態。 overView模式是長按桌面空白處,出現特殊功能,比如設定桌布,新增widget,特殊設定(橫屏開關
【190105】VC++ 家庭理財系統1.0(Access)原始碼原始碼
原始碼下載簡介 VC++ 採用Access資料庫開發的家庭理財系統,功能模組主要有:使用者收支詳細情況、收支情況統計與意見、圖表統計資訊、軟體使用日誌等,原始碼中的註釋比較豐富,相信對學習來說,是個不錯的專案教程,部分程式碼註釋: bool addflag,authorflag,logi
【Linux】Linux裝置驅動開發詳解:基於最新的Linux 4.0核心
1 Linux裝置驅動概述及開發環境構建 1.1 裝置驅動的作用 驅使硬體裝置行動 1.2 無作業系統時的裝置驅動 典型架構:一個無限迴圈中夾雜著對裝置中斷的檢測或者對裝置的輪詢 1.3 有作業系統時的裝置驅動 併發 、記
【原始碼剖析】threadpool —— 基於 pthread 實現的簡單執行緒池
部落格新地址:https://github.com/AngryHacker/articles/issues/1#issue-369867252 執行緒池介紹 執行緒池可以說是專案中經常會用到的元件,在這裡假設讀者都有一定的多執行緒基礎,如果沒有的話不妨在這裡進行了解:POSIX
【原始碼剖析】MemoryPool —— 簡單高效的記憶體池 allocator 實現
什麼是記憶體池?什麼是 C++ 的 allocator? 記憶體池簡單說,是為了減少頻繁使用 malloc/free new/delete 等系統呼叫而造成的效能損耗而設計的。當我們的程式需要頻繁地申請和釋放
【原始碼剖析】Webbench —— 簡潔而優美的壓力測試工具
Webbench 是一個古老而著名的網站壓力測試工具,簡單而實用。如果你不清楚你的網站能承受多大的壓力,或者你想分析對比兩個網站的效能,webbench 再好用不過了。 Gitbub 地址:點我  
【原始碼剖析】tornado-memcached-sessions —— Tornado session 支援的實現(三)
新地址:https://github.com/AngryHacker/articles/issues/5#issue-372211594 童鞋,我就知道你是個好學滴好孩子~來吧,讓我們進行最後的探(zuo)索(si)!
【原始碼剖析】tornado-memcached-sessions —— Tornado session 支援的實現(二)
客官您終於回頭了!讓我們本著探(zuo)索(si)精神把 session.py 看完吧... 首先看看需要的庫: pickle 一個用於序列化反序列化的庫(聽
【opencv 原始碼剖析】 四、 Mat的賦值建構函式 和 拷貝建構函式
1.賦值建構函式 右值引用 inline Mat& Mat::operator = (Mat&& m) { if (this == &m) return *this; release(); flags = m.fl
linux下poll和epoll核心原始碼剖析
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
【原始碼剖析】tinyhttpd —— C 語言實現最簡單的 HTTP 伺服器
tinyhttpd 是一個不到 500 行的超輕量型 Http Server,用來學習非常不錯,可以幫助我們真正理解伺服器程式的本質。 看完所有原始碼,真的感覺有很大收穫,無論是 unix 的程式設計,還是 GET/POST 的 Web 處理流程
【Java8原始碼分析】執行緒-Thread類的全面剖析
一、基本知識 (1)執行緒特性 每個執行緒均有優先順序 執行緒能被標記為守護執行緒 每個執行緒均分配一個name (2)建立執行緒的方法 繼承Thread類,並重現run方法 // 繼承Thread類 class PrimeThread e
【Java集合原始碼剖析】Java集合框架
Java集合工具包位於Java.util包下,包含了很多常用的資料結構,如陣列、連結串列、棧、佇列、集合、雜湊表等。學習Java集合框架下大致可以分為如下五個部分:List列表、Set集合、Ma
【Java集合原始碼剖析】ArrayList原始碼剖析
本篇博文參加了CSDN博文大賽,如果您覺得這篇博文不錯,希望您能幫我投一票,謝謝!ArrayList簡介 ArrayList是基於陣列實現的,是一個動態陣列,其容量能自動增長,類似於C語言中的動態申請記憶體,動態增長記憶體。 ArrayList不是執行緒安全的,只