歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> Linux虛擬文件系統之文件打開(sys_open())

Linux虛擬文件系統之文件打開(sys_open())

日期:2017/2/28 15:56:41   编辑:Linux教程
在文件讀寫之前,我們必須先打開文件。從應用程序的角度來看,這是通過標准庫的open函數完成的,該函數返回一個文件描述符。內核中是由系統調用sys_open()函數完成。 [cpp]
  1. /*sys_open*/
  2. SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
  3. {
  4. long ret;
  5. /*檢查是否應該不考慮用戶層傳遞的標志、總是強行設置
  6. O_LARGEFILE標志。如果底層處理器的字長不是32位,就是這種
  7. 情況*/
  8. if (force_o_largefile())
  9. flags |= O_LARGEFILE;
  10. /*實際工作*/
  11. ret = do_sys_open(AT_FDCWD, filename, flags, mode);
  12. /* avoid REGPARM breakage on x86: */
  13. asmlinkage_protect(3, ret, filename, flags, mode);
  14. return ret;
  15. }

實際實現工作

[cpp]
  1. <pre class="cpp" name="code">long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
  2. {
  3. /*從進程地址空間讀取該文件的路徑名*/
  4. char *tmp = getname(filename);
  5. int fd = PTR_ERR(tmp);
  6. if (!IS_ERR(tmp)) {
  7. /*在內核中,每個打開的文件由一個文件描述符表示
  8. 該描述符在特定於進程的數組中充當位置索引(數組是
  9. task_struct->files->fd_arry),該數組的元素包含了file結構,其中
  10. 包括每個打開文件的所有必要信息。因此,調用下面
  11. 函數查找一個未使用的文件描述符,返回的是上面
  12. 說的數組的下標*/
  13. fd = get_unused_fd_flags(flags);
  14. if (fd >= 0) {
  15. /*fd獲取成功則開始打開文件,此函數是主要完成打開功能的函數*/
  16. struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);
  17. if (IS_ERR(f)) {
  18. put_unused_fd(fd);
  19. fd = PTR_ERR(f);
  20. } else {
  21. fsnotify_open(f->f_path.dentry);
  22. fd_install(fd, f);
  23. }
  24. }
  25. putname(tmp);
  26. }
  27. return fd;
  28. }

打開文件主體實現

[cpp]
  1. /*
  2. * Note that the low bits of the passed in "open_flag"
  3. * are not the same as in the local variable "flag". See
  4. * open_to_namei_flags() for more details.
  5. */
  6. struct file *do_filp_open(int dfd, const char *pathname,
  7. int open_flag, int mode, int acc_mode)
  8. {
  9. struct file *filp;
  10. struct nameidata nd;
  11. int error;
  12. struct path path;
  13. struct dentry *dir;
  14. int count = 0;
  15. int will_write;
  16. /*改變參數flag的值,具體做法是flag+1*/
  17. int flag = open_to_namei_flags(open_flag);
  18. /*設置訪問權限*/
  19. if (!acc_mode)
  20. acc_mode = MAY_OPEN | ACC_MODE(flag);
  21. /* O_TRUNC implies we need access checks for write permissions */
  22. /*根據 O_TRUNC標志設置寫權限 */
  23. if (flag & O_TRUNC)
  24. acc_mode |= MAY_WRITE;
  25. /* Allow the LSM permission hook to distinguish append
  26. access from general write access. */
  27. /* 設置O_APPEND 標志*/
  28. if (flag & O_APPEND)
  29. acc_mode |= MAY_APPEND;
  30. /*
  31. * The simplest case - just a plain lookup.
  32. */
  33. /*如果不是創建文件*/
  34. if (!(flag & O_CREAT)) {
  35. /*當內核要訪問一個文件的時候,第一步要做的是找到這個文件,
  36. 而查找文件的過程在vfs裡面是由path_lookup或者path_lookup_open函數來完成的。
  37. 這兩個函數將用戶傳進來的字符串表示的文件路徑轉換成一個dentry結構,
  38. 並建立好相應的inode和file結構,將指向file的描述符返回用戶。用戶隨後
  39. 通過文件描述符,來訪問這些數據結構*/
  40. error = path_lookup_open(dfd, pathname, lookup_flags(flag),
  41. &nd, flag);
  42. if (error)
  43. return ERR_PTR(error);
  44. goto ok;/*跳過下面的創建部分*/
  45. }
  46. /*
  47. * Create - we need to know the parent.
  48. */
  49. /*到此則是要創建文件*/
  50. /* path-init為查找作准備工作,path_walk真正上路查找,
  51. 這兩個函數聯合起來根據一段路徑名找到對應的dentry */
  52. error = path_init(dfd, pathname, LOOKUP_PARENT, &nd);
  53. if (error)
  54. return ERR_PTR(error);
  55. error = path_walk(pathname, &nd);
  56. if (error) {
  57. if (nd.root.mnt)
  58. path_put(&nd.root);
  59. return ERR_PTR(error);
  60. }
  61. if (unlikely(!audit_dummy_context()))
  62. /*保存inode節點信息*/
  63. audit_inode(pathname, nd.path.dentry);
  64. /*
  65. * We have the parent and last component. First of all, check
  66. * that we are not asked to creat(2) an obvious directory - that
  67. * will not do.
  68. */
  69. error = -EISDIR;
  70. /*父節點信息*/
  71. if (nd.last_type != LAST_NORM || nd.last.name[nd.last.len])
  72. goto exit_parent;
  73. error = -ENFILE;
  74. /*獲取文件指針*/
  75. filp = get_empty_filp();
  76. if (filp == NULL)
  77. goto exit_parent;
  78. /*填充nameidata 結構*/
  79. nd.intent.open.file = filp;
  80. nd.intent.open.flags = flag;
  81. nd.intent.open.create_mode = mode;
  82. dir = nd.path.dentry;
  83. nd.flags &= ~LOOKUP_PARENT;
  84. nd.flags |= LOOKUP_CREATE | LOOKUP_OPEN;
  85. if (flag & O_EXCL)
  86. nd.flags |= LOOKUP_EXCL;
  87. mutex_lock(&dir->d_inode->i_mutex);
  88. /*從哈希表中查找目的文件對應的dentry,上面路徑搜索的是父節點
  89. 也就是目的文件的上一層目錄,為了得到目的文件的
  90. path結構,我們用nd中的last結構和上一層目錄的dentry結構
  91. 可以找到*/
  92. path.dentry = lookup_hash(&nd);
  93. path.mnt = nd.path.mnt;
  94. /*到此目標節點的path結構已經找到*/
  95. do_last:
  96. error = PTR_ERR(path.dentry);
  97. if (IS_ERR(path.dentry)) {
  98. mutex_unlock(&dir->d_inode->i_mutex);
  99. goto exit;
  100. }
  101. if (IS_ERR(nd.intent.open.file)) {
  102. error = PTR_ERR(nd.intent.open.file);
  103. goto exit_mutex_unlock;
  104. }
  105. /* Negative dentry, just create the file */
  106. /*如果此dentry結構沒有對應的inode節點,說明是無效的,應該創建文件節點 */
  107. if (!path.dentry->d_inode) {
  108. /*
  109. * This write is needed to ensure that a
  110. * ro->rw transition does not occur between
  111. * the time when the file is created and when
  112. * a permanent write count is taken through
  113. * the 'struct file' in nameidata_to_filp().
  114. */
  115. /*write權限是必需的*/
  116. error = mnt_want_write(nd.path.mnt);
  117. if (error)
  118. goto exit_mutex_unlock;
  119. /*按照namei格式的flag open*,主要是創建inode*/
  120. error = __open_namei_create(&nd, &path, flag, mode);
  121. if (error) {
  122. mnt_drop_write(nd.path.mnt);
  123. goto exit;
  124. }
  125. /*根據nameidata 得到相應的file結構*/
  126. filp = nameidata_to_filp(&nd, open_flag);
  127. if (IS_ERR(filp))
  128. ima_counts_put(&nd.path,
  129. acc_mode & (MAY_READ | MAY_WRITE |
  130. MAY_EXEC));
  131. /*放棄寫權限*/
  132. mnt_drop_write(nd.path.mnt);
  133. if (nd.root.mnt)
  134. path_put(&nd.root);
  135. return filp;
  136. }
  137. /*
  138. * It already exists.
  139. */
  140. /*要打開的文件已經存在*/
  141. mutex_unlock(&dir->d_inode->i_mutex);
  142. /*保存inode節點*/
  143. audit_inode(pathname, path.dentry);
  144. error = -EEXIST;
  145. if (flag & O_EXCL)
  146. goto exit_dput;
  147. /*如果path上安裝了文件系統,則依次往下找,直到找到
  148. 的文件系統沒有安裝別的文件系統,更新path結構為
  149. 此文件系統的根目錄信息*/
  150. if (__follow_mount(&path)) {
  151. error = -ELOOP;
  152. if (flag & O_NOFOLLOW)
  153. goto exit_dput;
  154. }
  155. error = -ENOENT;
  156. if (!path.dentry->d_inode)
  157. goto exit_dput;
  158. if (path.dentry->d_inode->i_op->follow_link)
  159. goto do_link;/*順次遍歷符號鏈接*/
  160. /*路徑轉化為相應的nameidata 結構*/
  161. path_to_nameidata(&path, &nd);
  162. error = -EISDIR;
  163. /*如果是文件夾*/
  164. if (path.dentry->d_inode && S_ISDIR(path.dentry->d_inode->i_mode))
  165. goto exit;
  166. /*到這裡,nd結構中存放的信息已經是最後的目的文件信息*/
  167. ok:
  168. /*
  169. * Consider:
  170. * 1. may_open() truncates a file
  171. * 2. a rw->ro mount transition occurs
  172. * 3. nameidata_to_filp() fails due to
  173. * the ro mount.
  174. * That would be inconsistent, and should
  175. * be avoided. Taking this mnt write here
  176. * ensures that (2) can not occur.
  177. */
  178. will_write = open_will_write_to_fs(flag, nd.path.dentry->d_inode);
  179. if (will_write) {
  180. error = mnt_want_write(nd.path.mnt);
  181. if (error)
  182. goto exit;
  183. }
  184. /*may_open執行權限檢測、文件打開和truncate的操作*/
  185. error = may_open(&nd.path, acc_mode, flag);
  186. if (error) {
  187. if (will_write)
  188. mnt_drop_write(nd.path.mnt);
  189. goto exit;
  190. }
  191. /*將nameidata轉化為file*/
  192. filp = nameidata_to_filp(&nd, open_flag);
  193. if (IS_ERR(filp))
  194. ima_counts_put(&nd.path,
  195. acc_mode & (MAY_READ | MAY_WRITE | MAY_EXEC));
  196. /*
  197. * It is now safe to drop the mnt write
  198. * because the filp has had a write taken
  199. * on its behalf.
  200. */
  201. if (will_write)
  202. /*釋放寫權限*/
  203. mnt_drop_write(nd.path.mnt);
  204. if (nd.root.mnt)
  205. /*釋放引用計數*/
  206. path_put(&nd.root);
  207. return filp;
  208. exit_mutex_unlock:
  209. mutex_unlock(&dir->d_inode->i_mutex);
  210. exit_dput:
  211. path_put_conditional(&path, &nd);
  212. exit:
  213. if (!IS_ERR(nd.intent.open.file))
  214. release_open_intent(&nd);
  215. exit_parent:
  216. if (nd.root.mnt)
  217. path_put(&nd.root);
  218. path_put(&nd.path);
  219. return ERR_PTR(error);
  220. /*允許遍歷連接文件,則手工找到連接文件對應的文件*/
  221. do_link:
  222. error = -ELOOP;
  223. if (flag & O_NOFOLLOW)
  224. goto exit_dput;/*不允許遍歷連接文件,返回錯誤*/
  225. /*
  226. * This is subtle. Instead of calling do_follow_link() we do the
  227. * thing by hands. The reason is that this way we have zero link_count
  228. * and path_walk() (called from ->follow_link) honoring LOOKUP_PARENT.
  229. * After that we have the parent and last component, i.e.
  230. * we are in the same situation as after the first path_walk().
  231. * Well, almost - if the last component is normal we get its copy
  232. * stored in nd->last.name and we will have to putname() it when we
  233. * are done. Procfs-like symlinks just set LAST_BIND.
  234. */
  235. /*以下是手工找到鏈接文件對應的文件dentry結構代碼
  236. */
  237. /*設置查找LOOKUP_PARENT標志*/
  238. nd.flags |= LOOKUP_PARENT;
  239. /*判斷操作是否安全*/
  240. error = security_inode_follow_link(path.dentry, &nd);
  241. if (error)
  242. goto exit_dput;
  243. /*處理符號鏈接,即路徑搜索,結果放入nd中*/
  244. error = __do_follow_link(&path, &nd);
  245. if (error) {
  246. /* Does someone understand code flow here? Or it is only
  247. * me so stupid? Anathema to whoever designed this non-sense
  248. * with "intent.open".
  249. */
  250. release_open_intent(&nd);
  251. if (nd.root.mnt)
  252. path_put(&nd.root);
  253. return ERR_PTR(error);
  254. }
  255. nd.flags &= ~LOOKUP_PARENT;
  256. /*檢查最後一段文件或目錄名的屬性情況*/
  257. if (nd.last_type == LAST_BIND)
  258. goto ok;
  259. error = -EISDIR;
  260. if (nd.last_type != LAST_NORM)
  261. goto exit;
  262. if (nd.last.name[nd.last.len]) {
  263. __putname(nd.last.name);
  264. goto exit;
  265. }
  266. error = -ELOOP;
  267. /*出現回環標志: 循環超過32次*/
  268. if (count++==32) {
  269. __putname(nd.last.name);
  270. goto exit;
  271. }
  272. dir = nd.path.dentry;
  273. mutex_lock(&dir->d_inode->i_mutex);
  274. /*更新路徑的掛接點和dentry*/
  275. path.dentry = lookup_hash(&nd);
  276. path.mnt = nd.path.mnt;
  277. __putname(nd.last.name);
  278. goto do_last;
  279. }

在內核中要打開一個文件,首先應該找到這個文件,而查找文件的過程在vfs裡面是由do_path_lookup或者path_lookup_open函數來完成的,關於文件路徑查找在前面已經分析過相關的代碼了。這兩個函數將用戶傳進來的字符串表示的文件路徑轉換成一個dentry結構,並建立好相應的inode和file結構,將指向file的描述符返回用戶。用戶隨後通過文件描述符,來訪問這些數據結構。

Copyright © Linux教程網 All Rights Reserved