1. 程式人生 > >【Linux 1.0核心原始碼剖析】執行程式——exec.c

【Linux 1.0核心原始碼剖析】執行程式——exec.c

父程序 fork的子程序的目的自然不是建立一個幾乎與自己一模一樣的程序。而是通過子程序呼叫 exec 函式簇去執行另外一個程式。exec() 系統呼叫必須定位該執行檔案的二進位制映像,載入並執行它。

exec() 的Linux實現支援不同的二進位制格式,這是通過 linux_binfmt 結構來達到的,其中內嵌了兩個指向函式的指標,一個用於載入可執行檔案,另一個用於載入庫函式,每種二進位制格式都實現有這兩個函式。

/* 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的核心是單獨實現 sys_execve() 呼叫的,它執行一個非常簡單的任務:載入可執行檔案的頭部,並試著去執行它。如果頭兩個位元組是 “#!”,那麼就會解析該可執行檔案的第一行並呼叫一個直譯器來執行它,否則的話,就會順序地呼叫各個註冊過的二進位制格式。
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, &regs);
//釋放路徑名副本
	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

秒懂Java1章_初識Java01_程式語言

各位小夥伴們好哇!從今日起,我將開始更新[《秒懂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,特殊設定(橫屏開關

190105VC++ 家庭理財系統1.0(Access)原始碼原始碼

原始碼下載簡介 VC++ 採用Access資料庫開發的家庭理財系統,功能模組主要有:使用者收支詳細情況、收支情況統計與意見、圖表統計資訊、軟體使用日誌等,原始碼中的註釋比較豐富,相信對學習來說,是個不錯的專案教程,部分程式碼註釋:   bool addflag,authorflag,logi

LinuxLinux裝置驅動開發詳解:基於最新的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不是執行緒安全的,只