linux 系統調用open 篇一

Linux Open系統調用篇一html

Linux Open系統調用篇二linux

Linux Open系統調用篇三git

Linux Open系統調用篇四api

內核源碼:linux-4.4 目標平臺:ARM體系結構 源碼工具:source insight 4數組

說明: 文中因爲 md 語法問題,沒法在代碼高亮的同時而忽略因爲 __ 或者 * 形成斜體的 問題,因此相似 __user 改爲 __ user,或者 char *filename 改爲 char* filename。 經過在中間添加空格進行避免。註釋統一使用了 \\緩存

open 對應的內核系統調用

應用層的 open 函數是 glibc 庫封裝了系統調用以比較友好的方式提供給開發者。 那麼爲何要這麼作? 這主要是從安全以及性能這兩大方面進行了考慮:安全

在用戶空間和內核空間之間,有一個叫作Syscall(系統調用, system call)的中間層,是鏈接用 戶態和內核態的橋樑。這樣即提升了內核的安全型,也便於移植, 只需實現同一套接口便可。Linux系統,用戶空間經過向內核空間發出Syscall,產生軟中斷, 從而讓程序陷入內核態,執行相應的操做。對於每一個系統調用都會有一個對應的系統調用號 ,比不少操做系統要少不少。markdown

安全性與穩定性:內核駐留在受保護的地址空間,用戶空間程序沒法直接執行內核代碼 ,也沒法訪問內核數據,經過系統調用數據結構

性能:Linux上下文切換時間很短,以及系統調用處理過程很是精簡,內核優化得好,因此性能上 每每比不少其餘操做系統執行要好。app

在應用層對於 open 操做主要使用的是如下兩個函數:

(1) int open(const char *pathname, int flags, mode_t mode);
(2) int openat(int dirfd, const char *pathname, int flags, mode_t mode);
複製代碼

若是打開文件成功,那麼返回文件描述符,值大於或等於0;若是打開文件失敗,返 回負的錯誤號。

下面是該函數參數的說明:

  1. 參數 pathname 是文件路徑,能夠是相對路徑(即不以 「/」 開頭),也能夠是絕對路徑(即以 「/」 開頭)。
  2. 參數 dirfd 是打開一個目錄後獲得的文件描述符,做爲相對路徑的基準目錄。若是文件路徑是 相對路徑,那麼在函數 openat 中解釋爲相對文件描述符 dirfd 引用的目錄,open 函數中解釋爲相對 調用進程的當前工做目錄。若是文件路徑是絕對路徑, openat 忽略參數 dirfd
  3. 參數 flags 必須包含一種訪問模式: O_RDONLY (只讀)、O_ WRONLY (只寫)或 O_RDWR(讀寫)。參數 flags 能夠包含多個文件建立標誌和文件狀態標誌。 兩組標誌的區別是: 文件建立標誌隻影響打開操做, 文件狀態標誌影響後面的讀寫操做。

文件建立標誌包括以下:

  • O_CLOEXEC:開啓 close-on-exc標誌,使用系統調用 execve() 裝載程序的時候關閉文件。
  • CREAT:若是文件不存在,建立文件。
  • ODIRECTORY:參數 pathname 必須是一個日錄。
  • EXCL:一般和標誌位 CREAT 聯合使用,用來建立文件。若是文件已經存在,那麼 open() 失敗,返回錯誤號 EEXIST。
  • NOFOLLOW:不容許參數 pathname 是符號連接,即最後一個份量不能是符號 連接,其餘份量能夠是符號連接。若是參數 pathname 是符號連接,那麼打開失敗,返回錯誤號 ELOOP。
  • O_TMPFILE:建立沒有名字的臨時普通文件,參數 pathname 指定目錄關閉文件的時候,自動刪除文件。
  • O_TRUNC:若是文件已經存在,是普通文件而且訪問模式容許寫,那麼把文件截斷到長度爲0。

文件狀態標誌包括以下:

  • APPEND:使用追加模式打開文件,每次調用 write 寫文件的時候寫到文件的末尾。
  • O_ASYNC:啓用信號驅動的輸入輸出,當輸入或輸出可用的時候,發送信號通知進程,默認的信號是 SIGIO。
  • O_DIRECT:直接讀寫存儲設備,不使用內核的頁緩存。雖然會下降讀寫速度, 可是在某些狀況下有用處,例如應用程序使用本身的緩衝區,不須要使用內核的頁緩存文件。
  • DSYNC:調用 write 寫文件時,把數據和檢索數據所須要的元數據寫回到存儲設備
  • LARGEFILE:容許打開長度超過 4 GB 的大文件。
  • NOATIME:調用 read 讀文件時,不要更新文件的訪問時間。
  • O_NONBLOCK:使用非阻塞模式打開文件, open 和之後的操做不會致使調用進程阻塞。
  • PATH:得到文件描述符有兩個用處,指示在目錄樹中的位置以及執行文件描述符層次的操做。 不會真正打開文件,不能執行讀操做和寫操做。
  • O_SYNC:調用 write 寫文件時,把數據和相關的元數據寫回到存儲設備。

參數 mode: 參數 mode 指定建立新文件時的文件模式。當參數 flags 指定標誌位 O_CREATO_TMPFILE 的時候,必須指定參數 mode,其餘狀況下忽略參數 mode。 參數 mode 能夠是下面這些標準的文件模式位的組合。

  1. S_IRWXU(0700,以0開頭表示八進制):用戶(即文件擁有者)有讀、寫和執行權限。
  2. S_IRUSR(00400):用戶有讀權限。
  3. S_IWUSR(00200):用戶有寫權限
  4. S_IXUSR(00100):用戶有執行權限。
  5. S_IRWXG(00070):文件擁有者所在組的其餘用戶有讀、寫和執行權限
  6. S_IRGRP(00040):文件擁有者所在組的其餘用戶有讀權限。
  7. S_IWGRP(00020):文件擁有者所在組的其餘用戶有寫權限。
  8. S_IXGRP(0010):文件擁有者所在組的其餘用戶有執行權限。
  9. S_IRWXO(0007):其餘組的用戶有讀、寫和執行權限。
  10. S_IROTH(0004):其餘組的用戶有讀權限。
  11. S_IWOTH(00002):其餘組的用戶有寫權限。
  12. S_IXOTH(00001):其餘組的用戶有執行權限。

參數 mode 能夠包含下面這些 Linux 私有的文件模式位:

  1. S_ISUID (0004000):set-user-ID 位。
  2. S_ISGID (0002000):set-group-iD位。
  3. S_ISVTX(0001000):粘滯(sticky)位。

以上內容能夠參考:open man7手冊

那麼咱們該如何找到對應的 syscall? 有幾個小技巧能夠用來幫助咱們:

  • 用戶空間的方法xxx,對應系統調用層方法則是 sys_xxx;
  • unistd.h 文件記錄着系統調用中斷號的信息。
  • 宏定義 SYSCALL_DEFINEx(xxx,…),展開後對應的方法則是 sys_xxx;
  • 方法參數的個數x,對應於 SYSCALL_DEFINEx。

細節能夠參考下面給出的連接:Linux系統調用(syscall)原理

根據第一個小技巧,咱們知道咱們須要找的函數爲:sys_open。 具體代碼流程比較複雜,這裏使用取巧的方式,找到對應的內核函數,前面提到須要找的的函數 爲 sys_open。 這種函數在內核中是經過宏定義 SYSCALL_DEFINEx 展開後獲得的。那麼能夠 利用 source insight 的搜索功能。應用層 open 函數的參數的個數爲 3,能夠假想先從 SYSCALL_DEFINE3 進行全局搜索。隨便選擇一個搜索結果,這裏假設選擇的是 SYSCALL_DEFINE3(mknod,這步主要是爲了獲取代碼格式,把 mknod 改爲 open ,而後搜索 SYSCALL_DEFINE3(open。 很快咱們就在 kernel\fs\open.c 文件中找到惟一的搜索結果,代碼以下:

SYSCALL_DEFINE3
SYSCALL_DEFINE3(open, const char __ user*, filename, int, flags, umode_t, mode)
{
 if (force_o_largefile())
   flags |= O_LARGEFILE;

 return do_sys_open(AT_FDCWD, filename, flags, mode);
}
複製代碼
if (force_o_largefile())
   flags |= O_LARGEFILE;
複製代碼

表示 flags 會在 64 位 Kernel 的狀況下強制麼設置 O_LARGEFILE 來表示支持大文件。 接着跳轉到 do_sys_open 函數。

do_sys_open 系統調用主體
long do_sys_open(int dfd, const char __ user *filename, int flags, umode_t mode) {
 struct open_flags op;
 //檢查幷包裝傳遞進來的標誌位
 int fd = build_open_flags(flags, mode, &op);
 struct filename * tmp;

 if (fd)
   return fd;
 //用戶空間的路徑名複製到內核空間
 tmp = getname(filename);
 if (IS_ERR(tmp))
   return PTR_ERR(tmp);
 //獲取一個未使用的 fd 文件描述符
 fd = get_unused_fd_flags(flags);
 if (fd >= 0) {
   //調用 do_filp_open 完成對路徑的搜尋和文件的打開
   struct file * f = do_filp_open(dfd, tmp, &op);
   if (IS_ERR(f)) {
     //若是發生了錯誤,釋放已分配的 fd 文件描述符
     put_unused_fd(fd);
     //釋放已分配的 struct file 數據
     fd = PTR_ERR(f);
   } else {
     fsnotify_open(f);
     //綁定 fd 與 f。
     fd_install(fd, f);
   }
 }
 //釋放已分配的 filename 結構體。
 putname(tmp);
 return fd;
}
複製代碼

fd 是一個整數,它實際上是一個數組的下標,用來獲取指向 file 描述符的指針, 每一個進程都有個 task_struct 描述符用來描述進程相關的信息,其中有個 files_struct 類型的 files 字段,裏面有個保存了當前進程全部已打開文件 描述符的數組,而經過 fd 就能夠找到具體的文件描述符,之間的關係能夠參考下圖:

fd與file文件描述符

這裏的參數已經在上面提到過了,惟一須要注意的是 AT_FDCWD,其定義在 include/uapi/linux/fcntl.h,是一個特殊值(** -100 **), 該值代表當 filename 爲相對路徑的狀況下將當前進程的工做目錄設置爲起始路徑。相對而言, 你能夠在另外一個系統調用 openat 中爲這個起始路徑指定一個目錄, 此時 AT_FDCWD 就會被該目錄的描述符所替代。

build_open_flags 初始化 flags
static inline int build_open_flags(int flags, umode_t mode, struct open_flags *op) {
	int lookup_flags = 0;
        //O_CREAT 或者 `__O_TMPFILE*` 設置了,acc_mode 纔有效。
	int acc_mode;


	 // Clear out all open flags we don't know about so that we don't report
	 // them in fcntl(F_GETFD) or similar interfaces.
	 // 只保留當前內核支持且已被設置的標誌,防止用戶空間亂設置不支持的標誌。
	flags &= VALID_OPEN_FLAGS;

	if (flags & (O_CREAT | __ O_TMPFILE))
		op->mode = (mode & S_IALLUGO) | S_IFREG;
	else
                //若是 O_CREAT | __ O_TMPFILE 標誌都沒有設置,那麼忽略 mode。
		op->mode = 0;

	// Must never be set by userspace
	flags &= ~FMODE_NONOTIFY & ~O_CLOEXEC;


	// O_SYNC is implemented as __ O_SYNC|O_DSYNC. As many places only
	// check for O_DSYNC if the need any syncing at all we enforce it's
	// always set instead of having to deal with possibly weird behaviour
	// for malicious applications setting only __ O_SYNC.
	if (flags & __ O_SYNC)
		flags |= O_DSYNC;

        //若是是建立一個沒有名字的臨時文件,參數 pathname 用來表示一個目錄,
        //會在該目錄的文件系統中建立一個沒有名字的 iNode。
	if (flags & __ O_TMPFILE) {
		if ((flags & O_TMPFILE_MASK) != O_TMPFILE)
			return -EINVAL;
		acc_mode = MAY_OPEN | ACC_MODE(flags);
		if (!(acc_mode & MAY_WRITE))
			return -EINVAL;
	} else if (flags & O_PATH) {

		// If we have O_PATH in the open flag. Then we
		// cannot have anything other than the below set of flags
		// 若是設置了 O_PATH 標誌,那麼 flags 只能設置如下 3 個標誌。
		flags &= O_DIRECTORY | O_NOFOLLOW | O_PATH;
		acc_mode = 0;
	} else {
		acc_mode = MAY_OPEN | ACC_MODE(flags);
	}

	op->open_flag = flags;

        // O_TRUNC implies we need access checks for write permissions
        // 若是設置了,那麼寫以前可能須要清空內容。
	if (flags & O_TRUNC)
		acc_mode |= MAY_WRITE;

	// Allow the LSM permission hook to distinguish append
	// access from general write access.
        // 讓 LSM 有能力區分 追加訪問和普通訪問。
	if (flags & O_APPEND)
		acc_mode |= MAY_APPEND;

	op->acc_mode = acc_mode;

        //設置意圖,若是沒有設置 O_PATH,表示這次調用有打開文件的意圖。
	op->intent = flags & O_PATH ? 0 : LOOKUP_OPEN;

	if (flags & O_CREAT) {
                //是否有建立文件的意圖
		op->intent |= LOOKUP_CREATE;
		if (flags & O_EXCL)
			op->intent |= LOOKUP_EXCL;
	}
        //判斷查找的目標是不是目錄。
	if (flags & O_DIRECTORY)
		lookup_flags |= LOOKUP_DIRECTORY;
        //判斷當發現符號連接時是否繼續跟下去
	if (!(flags & O_NOFOLLOW))
		lookup_flags |= LOOKUP_FOLLOW; //查找標誌設置了 LOOKUP_FOLLOW 表示會繼續跟下去。

        //設置查找標誌,lookup_flags 在路徑查找時會用到
	op->lookup_flags = lookup_flags;
	return 0;
}
複製代碼

上面的函數主要是根據用戶傳遞進來的 flags 進一步設置具體的標誌,而後把這些標誌封裝到 open_flags 結構體中。以便後續使用。

接下來就是函數 getname() ,這個函數定義在 fs/namei.c,主體是 getname_flags, 咱們撿重點的分析,可有可無的代碼以 ... 略過。

getname_flags 複製路徑名
struct filename * getname(const char __ user *filename) {
	return getname_flags(filename, 0, NULL);
}
複製代碼
struct filename {
	const char* name;	// pointer to actual string ---指向真實的字符串
	const __ user char* uptr;	// original userland pointer -- 指向原來用戶空間的 filename
	struct audit_names* aname;
	int			refcnt;
	const char		iname[]; //用來保存 pathname
};
複製代碼
struct filename * getname_flags(const char __ user *filename, int flags, int* empty) {
	struct filename* result;
	char* kname;
	int len;

        // 這裏通常來講賦值爲 NULL。這裏主要是針對Linux 審計工具 audit,咱們無論。
	result = audit_reusename(filename);

        // 若是不爲空直接返回。
	if (result)
		return result;

        // 經過__getname 在內核緩衝區專用隊列裏申請一塊內存用來放置路徑名(filemname 結構體)
	result = __getname();

	if (unlikely(!result))
		return ERR_PTR(-ENOMEM);


	//First, try to embed the struct filename inside the names_cache
	//allocation
        //kname 指向 struct filename 的 iname 數組。
	kname = (char*)result->iname;
        // 把 filename->name 指向 iname[0],待會 iname 用來保存用戶空間傳遞過來的路徑名(filemname 結構體)。
	result->name = kname;

        //該函數把用戶空間的 filename 複製到 iname
	len = strncpy_from_user(kname, filename, EMBEDDED_NAME_MAX);

        //若是複製失敗,釋放已分配的 result 並返回錯誤。
	if (unlikely(len < 0)) {
		__putname(result);
		return ERR_PTR(len);
	}


	// Uh-oh. We have a name that's approaching PATH_MAX. Allocate a
	// separate struct filename so we can dedicate the entire
	// names_cache allocation for the pathname, and re-do the copy from
	// userland.
	// 這裏判斷用戶空間傳遞過來的路徑名的長度接近了 PATH_MAX,因此須要分配一個獨立的空間
        // 用來保存 struct filename 前面的字段,並把 name_cache 所有空間用來保存路徑名 (filename->iname)。
        //
        // #define PATH_MAX 4096 // 4 kb 大小。
        // #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE * )0)->MEMBER)
	// #define EMBEDDED_NAME_MAX (PATH_MAX - offsetof(struct filename, iname))
        // EMBEDDED_NAME_MAX 指的就是:字段 iname 在 filename 結構體中的偏移。

	if (unlikely(len == EMBEDDED_NAME_MAX)) {
                // 注意,這裏是把 iname[1] 的偏移賦值給了 size。這樣 size 的大小包含了 inaem[0]
                // 能夠用來保存 iname 數組的首地址。
		const size_t size = offsetof(struct filename, iname[1]);

                // 把舊 result 的首地址賦值給了 kanme。
		kname = (char * )result;


		// size is chosen that way we to guarantee that
		// result->iname[0] is within the same object and that
		// kname can't be equal to result->iname, no matter what.
		// 分配一個獨立空間用來保存 filename,這樣就能夠把 filename 分離出來。
		result = kzalloc(size, GFP_KERNEL);

                //分配失敗,釋放資源並返回錯誤。
		if (unlikely(!result)) {
			__putname(kname);
			return ERR_PTR(-ENOMEM);
		}
                // 把原來的 filename 的首地址賦值給新分配的 result。這樣就實現了分離。
		result->name = kname;

                // 把用戶空間的 filename 複製到 kname(name_cache 起始地址)。
		len = strncpy_from_user(kname, filename, PATH_MAX);
		
                // 原來:
                // filename struct(內核空間,用 name_cach 來保存)
                // result ---> name_cache-----> name
                // uptr
                // aname
                // .... 複製操做(strncpy_from_user())
                // iname <--------------> filename struct(用戶空間)
                //
                // 如今:
                // filename struct(內核空間,注意這裏是新開獨立的空間。)
                // result ---> name ------> name_cache <---------------> filename struct(用戶空間)
                // uptr 複製操做(strncpy_from_user())
                // aname
                // ....
                // iname
                // 新分配的 filename 的首地址指向 name_cach,而 name_cach 又保存了用戶
                // 空間的 filename,因此新的 filename(result) 能間接訪問到用戶空間的 filename。

                // 複製失敗,釋放資源,返回。
		if (unlikely(len < 0)) {
			__putname(kname);
			kfree(result);
			return ERR_PTR(len);
		}

                // 路徑過長,一樣返回錯誤(從這裏也能夠看出,在 Linux 中路徑名的長度不能超過 4096 字節)。
		if (unlikely(len == PATH_MAX)) {
			__putname(kname);
			kfree(result);
			return ERR_PTR(-ENAMETOOLONG);
		}
	}

        // 引用計數爲 1
	result->refcnt = 1;

        // The empty path is special.空路徑的處理。
	if (unlikely(!len)) {
		if (empty)
			* empty = 1;
                // 若是 LOOKUP_EMPTY 沒有設置,也就是本次 open 操做的目標不是空路徑,可是傳遞了一個
                // 空路徑,因此返回錯誤。
		if (! (flags & LOOKUP_EMPTY)) {
                        //回收資源
			putname(result);
			return ERR_PTR(-ENOENT);
		}
	}
        // 指向用戶空間的 filename
	result->uptr = filename;
	result->aname = NULL;
	audit_getname(result);
	return result;
}
複製代碼
struct filename {
	const char* name;	 	//pointer to actual string ---指向真實的字符串
	const __ user char* uptr;	//original userland pointer --- 指向原來用戶空間
	struct audit_names* aname;
	int			refcnt;
	const char		iname[];  //用來保存 pathname
};
複製代碼

首先經過 __getname 在內核緩衝區專用隊列裏申請一塊內存用來放置路徑名,其實這塊內存就是 一個 4KB 的內存頁。這塊內存頁是這樣分配的,在開始的一小塊空間放置結構體 struct filename 結構體前面字段的信息,這裏咱們假設 iname 字段以前的結構使用 struct filename-iname 表示, 以後的空間放置字符串(保存在 iname)。初始化字符串指針 kname,使其指向這個字符串 (iname[])的首地址。而後就是拷貝字符串,返回值 len 表明了 已經 拷貝的字符串長度。若是這個字符串已經填滿了內存頁剩餘空間,就說明該字符串的長度已經大於 4KB - (sizeof(struct filename-iname)了,這時就須要將結構體 struct filename-iname 從這個內存頁中分離並單獨分配空間,而後用整個內存頁保存該字符串。

get_unused_fd_flags 獲取 fd

get_unused_fd_flags() 函數用來查找一個可用的 fd(文件描述符)。

int get_unused_fd_flags(unsigned flags) {
	return __alloc_fd(current->files, 0, rlimit(RLIMIT_NOFILE), flags);
}
複製代碼
/* * allocate a file descriptor, mark it busy. */
int __alloc_fd(struct files_struct *files,
	       unsigned start, unsigned end, unsigned flags)
{
	unsigned int fd;
	int error;
	struct fdtable * fdt;

	spin_lock(&files->file_lock);
repeat:
        // 經過 files 字段獲取 fdt 字段。(該函數考慮了線程競爭,較複雜不展開了。)
	fdt = files_fdtable(files);
        //從 start 開始搜索
	fd = start;
        // 進程上一次獲取的 fd 的下一個號(fd + 1)保存在 next_fd 中。因此從 next_fd 開始進行查找。
	if (fd < files->next_fd)
		fd = files->next_fd;

	if (fd < fdt->max_fds)
                //獲取下一個 fd
		fd = find_next_fd(fdt, fd);


	// N.B. For clone tasks sharing a files structure, this test
	// will limit the total number of files that can be opened.
	error = -EMFILE;
	if (fd >= end)
		goto out;
        // 獲取 fd 後,判斷是否須要擴展用來保存 file struct 描述符的數組(fdtable->fd)的容量。
        // 返回 0 表示不須要,<0 表示錯誤,1 表示成功。
	error = expand_files(files, fd);
	if (error < 0)
		goto out;

	// If we needed to expand the fs array we
	// might have blocked - try again.
        // 1,擴容成功,而且從新嘗試獲取fd
        // 由於擴容過程可能會發生阻塞,這期間就有可能其餘線程也在獲取 fd,因此前面獲取的 fd
        // 可能被其餘線程搶先佔用了,由於 Linux 的喚醒是不保證順序的。
	if (error)
		goto repeat;

	if (start <= files->next_fd)
		files->next_fd = fd + 1;
      // 在 fdtable->open_fds 位圖中置位表示當前獲取的 fd 處於使用狀態。
      // 也就是說當釋放該 fd 位圖中對應的位清除,從而達到重複使用的的目的。
	__set_open_fd(fd, fdt);

      // 若是設置了 O_CLOEXEC 標誌,那麼在 fdtable->close_on_exec 位圖對應的位置位。
      // 前面提到過開啓 close-on-exc 標誌,使用系統調用 execve() 裝載程序的時候會關閉設置過該標誌的文件。
      // Linux 中使用 fork() 產生子進程的時候回繼承父進程已打開的文件描述符集。execve() 通常就是在子進程
      // 裏用來運行新程序。
	if (flags & O_CLOEXEC)
		__set_close_on_exec(fd, fdt);
	else
		__clear_close_on_exec(fd, fdt);
        // 設置返回值。
	error = fd;
#if 1
	// Sanity check 一些合法性檢查。
	if (rcu_access_pointer(fdt->fd[fd]) != NULL) {
		printk(KERN_WARNING "alloc_fd: slot %d not NULL!\n", fd);
		rcu_assign_pointer(fdt->fd[fd], NULL);
	}
#endif

out:
	spin_unlock(&files->file_lock);
	return error;
}
複製代碼
struct fdtable {
	unsigned int max_fds;
	struct file __ rcu ** fd;      // current fd array
	unsigned long * close_on_exec;
	unsigned long * open_fds;
	unsigned long * full_fds_bits;
	struct rcu_head rcu;
};
複製代碼
#ifdef CONFIG_64BIT
#define BITS_PER_LONG 64
#else
#define BITS_PER_LONG 32
#endif /* CONFIG_64BIT */


static inline void __set_open_fd(unsigned int fd, struct fdtable *fdt)
{
	__set_bit(fd, fdt->open_fds);
	fd /= BITS_PER_LONG;
	if (!~fdt->open_fds[fd])
		__set_bit(fd, fdt->full_fds_bits);
}
複製代碼

這裏以 32 位 arm 芯片爲例。其中函數 __set_bit 表示以某個地址開始在某個位置 1。 假設咱們目前數組的容量爲 128 ,那麼以下表:共有 4 行,一行 32 列,fd = 32 * row + column。 每一個格子中 0 表示當前 fd 沒有被佔用,1 表示佔用了。其中 ... 表示全部的列爲 1。 假設咱們如今獲取的 fd 爲 66 也就是第 3 行第 3 列,此時咱們能夠看到該格子爲 0。 調用 __set_bit(fd, fdt->open_fds); 把該位(66)置1,fd /= BITS_PER_LONG; 獲取行號 66 / 32 = 2(行號從 0 開始),!~fdt->open_fds[fd]open_fds爲 long 類型 指針,也就是說步長爲 32 位,至關於取第 3 個 long 數據的值,而後位取反,由於此時該 long 數據 的每一位都置 1 了,因此取反後的值爲 0,!0 就爲 true 了。此時咱們能夠肯定第 3 行全部的 列都被使用了,因此咱們能夠把 full_fds_bits 的第 2 位置 1,表示該行已所有被使用。

0 1 2 ... 30 31
1 1 1 ... 1 1
1 1 1 ... 1 1
1 1 0 ... 1 1
1 1 1 ... 0 0

接下來看找 fd 函數 find_next_fd() 就很簡單了。

static unsigned long find_next_fd(struct fdtable *fdt, unsigned long start) {
	unsigned long maxfd = fdt->max_fds;
        // 當前容量最後的一行
	unsigned long maxbit = maxfd / BITS_PER_LONG;
        // 開始行
	unsigned long bitbit = start / BITS_PER_LONG;
        // 先找到一個空行(有空閒位的某一行)
	bitbit = find_next_zero_bit(fdt->full_fds_bits, maxbit, bitbit) * BITS_PER_LONG;
	if (bitbit > maxfd)
		return maxfd;
	if (bitbit > start)
		start = bitbit;
        // 在該行上找到一個具體的空位。
	return find_next_zero_bit(fdt->open_fds, maxfd, start);
}
複製代碼

儘可能以本身的能力對每行代碼進行了註釋,同時只是爲了學習內核大神是如何玩轉指針以及數據結構。 能夠從 __set_open_fd() 函數看出對指針熟練的使用方式,以及快速定位的思想。

相關文章
相關標籤/搜索