\section{用户空间相对于内核空间}
\label{sec:user_kernl_space}
内核就是对资源的访问,无论所讨论的资源是否恰好是一个显卡,一个硬盘或甚至内存。%
-ç¨\8båº\8fé\80\9a常ç«\9f争相同的资源。当我刚刚保存此文档时,updatedb 开始更新本地数据库。我的%
+ç¨\8båº\8fé\80\9a常ç«\9e争相同的资源。当我刚刚保存此文档时,updatedb 开始更新本地数据库。我的%
vim 会话与 updatedb 两者同时使用硬盘驱动。内核需要保持事件的有序性,并且不让用%
户随时访问资源。为此,一个 CPU 可以运行于不同的模式。每种模式都提供不同程度的自%
由度,让你可以在系统上执行你想要的操作。Intel 80386 构架有\,4\,种这样的模式,称%
告诉用户该操作是不被支持的。如果你看不到我们如何处理读入缓冲区的数据,请不要担%
心;我们对此没做太多的事情。我们只是读入数据并打印一条消息来确认我们收到了它。
-å\9c¨å¤\9a线ç¨\8bç\8e¯å¢\83ï¼\8c没æ\9c\89ä»»ä½\95ä¿\9dæ\8a¤ï¼\8c对äº\8eç\9b¸å\90\8cå\86\85å\98å\9c°å\9d\80ç\9a\84å¹¶å\8f\91访é\97®å\8f¯è\83½å¯¼è\87´ç«\9f争情况,并且不%
-è\83½ä¿\9dæ\8c\81æ\80§è\83½ã\80\82å\9c¨å\86\85æ ¸æ¨¡å\9d\97ä¸ï¼\8cç\94±äº\8eå¤\9a个å®\9eä¾\8b访é\97®å\85±äº«èµ\84æº\90ï¼\8cå\8f¯è\83½äº§ç\94\9få¹¶å\8f\91ç«\9f争的问题。我%
+å\9c¨å¤\9a线ç¨\8bç\8e¯å¢\83ï¼\8c没æ\9c\89ä»»ä½\95ä¿\9dæ\8a¤ï¼\8c对äº\8eç\9b¸å\90\8cå\86\85å\98å\9c°å\9d\80ç\9a\84å¹¶å\8f\91访é\97®å\8f¯è\83½å¯¼è\87´ç«\9e争情况,并且不%
+è\83½ä¿\9dæ\8c\81æ\80§è\83½ã\80\82å\9c¨å\86\85æ ¸æ¨¡å\9d\97ä¸ï¼\8cç\94±äº\8eå¤\9a个å®\9eä¾\8b访é\97®å\85±äº«èµ\84æº\90ï¼\8cå\8f¯è\83½äº§ç\94\9få¹¶å\8f\91ç«\9e争的问题。我%
们用原子化 Compare-And-Swap (CAS) 来保持状态,原子化的 \cpp|CDEV_NOT_USED| 与原%
子化的 \cpp|CDEV_EXCLUSIVE_OPEN| 来决定当前文件是否被其它人打开或没有被打开。%
CAS 用一个希望的值比较一个内存位置的内容,只有两者相同时,才能更改那个内存位置%
\chapter{/proc文件系统}
\label{sec:procfs}
在 Linux 中,内核和模块有一个额外的机制来向进程发送信息,这个机制就是 %
-\verb|/proc/modules|。最初设计它的意图是允许轻松访问有关进程的信息(因此得名),现%
-在它被内核的每一个需要报告的有趣部分所使用,例如 \verb|/proc/modules| 提供模块%
-列表, \verb|/proc/meminfo| 它收集内存使用的信息。
+\verb|/proc/modules|。最初设计它的意图,是允许轻松访问有关进程的信息(因此得名),%
+现在它被内核每一个需要报告的有趣部分所使用,例如 \verb|/proc/modules| 提供模块%
+列表, \verb|/proc/meminfo| 收集内存使用的信息。
使用 proc 文件系统的方法与设备驱动程序使用方法非常相似,使用 \verb|proc| 文件所%
-需的所有信息创建一个数据结构,包括指向任可处理函数的指针(在我们的例子中只有一个,%
-当有人试从 \verb|/proc| 文件中读取时调用的函数)。然后,\cpp|init_module| 向内核%
-注册该结构,并且 \cpp|cleanup_module| 注销该结构。
+需的所有信息创建一个数据结构,包括指向任何可处理函数的指针(在我们的例子中只有一%
+个,当有人尝试从 \verb|/proc| 文件中读取时,调用的函数)。然后,\cpp|init_module| %
+向内核注册该结构,并且 \cpp|cleanup_module| 注销该结构。
正常的文件系统位于一个磁盘,而不是只在内存(它是 \verb|/proc| 的位置),并且在这种%
-æ\83\85å\86µä¸\8bï¼\8cindex-node(ç®\80ç§° inode)æ\95°å\97æ\98¯ä¸\80个æ\8c\87å\90\91该æ\96\87ä»¶ç\9a\84 inode ä½\8däº\8eç\9a\84ç£\81ç\9b\98ä½\8dç½®ç\9a\84æ\8c\87é\92\88ã\80\82%
+情况下,index-node(简称 inode)数字是一个指向该文件的 inode 位于磁盘位置的指针。%
inode 包含关于该文件的信息,例如文件的许可权限,以及指向磁盘位置或可以找到文件%
-数据的位置指针。
+数据位置的指针。
-因为在文件被打开或关闭时,我们不会被调用,\cpp|try_module_get| 与 \cpp|module_put| %
-所以对我们来说,在模块中没有地方来被放置。如果文件已被打开,并且之后模块被移除%
+因为在文件被打开或关闭时,我们不会被调用,所以 \cpp|try_module_get| 与 %
+\cpp|module_put| 对我们来说没有地方在模块中放置。如果文件已被打开,然后模块被移除%
了,则无法避免不良后果。
-这里有一个如何使用 \verb|/proc| 文件的简单示例。这是针对 \verb|/proc| 文件系统的%
-HelloWorldã\80\82è¿\87ç¨\8bå\88\86ä¸\89æ¥:在函数 \cpp|init_module| 中 \verb|/proc/helloworld| 文件%
-被创建,当å\9c¨å\9b\9eè°\83å\87½æ\95° \cpp|procfile_read| 读æ\96\87ä»¶ \verb|/proc/helloworld| æ\97¶ï¼\8cè¿\94å\9b\9e%
+这里有一个如何使用 \verb|/proc| 文件的简单示例。这是针对 \verb|/proc| 文件系统的 %
+HelloWorldã\80\82è¿\99é\87\8cæ\9c\89ä¸\89é\83¨å\88\86:在函数 \cpp|init_module| 中 \verb|/proc/helloworld| 文件%
+被创建,当ç\94¨å\9b\9eè°\83å\87½æ\95° \cpp|procfile_read| 读æ\96\87ä»¶ \verb|/proc/helloworld| æ\97¶ï¼\8cè¿\94å\9b\9e%
一个值(与一个缓冲),并在 \cpp|cleanup_module| 函数中删除 \verb|/proc/helloworld| %
文件。
返回值是一个 \cpp|struct proc_dir_entry|,并且文件 \verb|/proc/helloworld| 的配%
置将使用它(例如,该文件的拥有者)。一个空返回值意味着创建过程失败。
-每次文件 \verb|/proc/helloworld| 被读取,\cpp|procfile_read| 函数会被调用。这个%
-函数的两个参数非常重要:缓存(第二个参数)与偏移量(第四个参数)。缓存的内容将被返回%
-到应用,其内容会被读取(例如,\cpp|cat| 命令)。偏移量的值是在文件中位于的当前位置。%
-å¦\82æ\9e\9cå\87½æ\95°è¿\94å\9b\9eå\80¼ä¸\8d为空ï¼\8cé\82£ä¹\88è¿\99个å\87½æ\95°ä¼\9aå\86\8d次被è°\83ç\94¨ã\80\82å\9b æ¤ï¼\8cç»\86å¿\83使ç\94¨è¿\99个å\87½æ\95°ï¼\8cå¦\82æ\9e\9cå®\83å\86³%
-不返回零,读函数被没完没了的被调用。
+每次文件 \verb|/proc/helloworld| 被读取,\cpp|procfile_read| 函数都会被调用。这%
+个函数的两个参数非常重要:缓存(第二个参数)与偏移量(第四个参数)。缓存的内容将被%
+返回到应用,其内容会被读取(例如,\cpp|cat| 命令)。偏移量的值是在文件中位于的当%
+å\89\8dä½\8dç½®ã\80\82å¦\82æ\9e\9cå\87½æ\95°è¿\94å\9b\9eå\80¼ä¸\8d为空ï¼\8cé\82£ä¹\88è¿\99个å\87½æ\95°ä¼\9aå\86\8d次被è°\83ç\94¨ã\80\82å\9b æ¤ï¼\8cä»\94ç»\86å\9c°ä½¿ç\94¨è¿\99个å\87½%
+数,如果它决不返回零,读函数被没完没了的被调用。
\begin{verbatim}
$ cat /proc/helloworld
\cpp|proc_ops| 结构在 Linux v5.6+ 中的 \src{include/linux/proc\_fs.h} 文件中被%
定义。在旧的内核中,它用 \cpp|file_operations| 在 \verb|/proc| 文件系统中用于定%
制挂钩,但它存在一些在 VFS 中不需要的结构成员,且每次 VFS 扩展 \cpp|file_operations| %
-集,\verb|/proc| 变得臃肿。另一方面,这种结构不仅节省了空间,还节省了一些操作,%
-ä»\8eè\80\8cæ\8f\90é«\98äº\86æ\80§è\83½ã\80\82ä¾\8bå¦\82ï¼\8c\verb|/proc| 䏿°¸ä¸\8dæ¶\88失ç\9a\84æ\96\87ä»¶å\8f¯ä»¥è®¾ç½® \cpp|proc_flag| ä½\9c为%
-\cpp|PROC_ENTRY_PERMANENT| 在每个打开/读取/关闭序列中保存两个原子操作,一个分配,%
-一个释放。
+数据集合,\verb|/proc| 代码变得臃肿。另一方面,这种结构不仅节省了空间,还节省了%
+ä¸\80äº\9bæ\93\8dä½\9cï¼\8cä»\8eè\80\8cæ\8f\90é«\98äº\86æ\80§è\83½ã\80\82ä¾\8bå¦\82ï¼\8c\verb|/proc| 䏿°¸ä¸\8dæ¶\88失ç\9a\84æ\96\87ä»¶å\8f¯ä»¥è®¾ç½® \cpp|proc_flag| %
+作为 \cpp|PROC_ENTRY_PERMANENT| 在每个打开/读取/关闭序列中保存两个原子操作,一%
+个å\88\86é\85\8dï¼\8cä¸\80个é\87\8aæ\94¾ã\80\82
\section{读与写一个/proc文件}
\label{sec:read_write_procfs}
我们已经看过一个对 \verb|/proc| 文件非常简单的示例,该示例只是读取 %
\verb|/proc/helloworld| 文件。也可能写入一个 \verb|/proc| 文件。其工作与读的方%
法相同,当 \verb|/proc| 文件被写入时,一个函数被调用,但在这里与读操作有少许不%
-同,数据来自用户,因此你必段从用户户向内核空间输入数据(用 \cpp|copy_from_user| %
+同,数据来自用户,因此你必须从用户空间向内核空间输入数据(用 \cpp|copy_from_user| %
或 \cpp|get_user|)。
-
用 \cpp|copy_from_user| 或 \cpp|get_user| 的原因是 Linux 内存(在 Intel 架构,有%
些其它处理器上可能是不同的) 被分段。这意味着一个指针,本身,在内存中并不引用唯%
-一的位置。而仅用内存段中位置,并且你需要知首它是哪个内存段后,才能使用它。对内%
-核来说,内核有一个内存段,每个进程也有一个内存段。
+一的位置,而引用内存段中一个位置,并且你需要知道该内存位置是哪个内存段后,才能%
+使用它。对内核来说,只有一个内存段,且每个进程也拥有一个内存段。
进程唯一可以访问的内存段是它自己的内存段,因此在编写作为进程运行的常规程序时,%
-无需担心段问题。当你编写内核模块时,通常需要访问内核内存段,即由系统自动处理。%
-然而,当内存缓冲区的内容需要在当前运行的进程和内核之间传递时,内核函数会收到一%
-个指向进程段中的内存缓冲区的指针。\cpp|put_user| 与 \cpp|get_user| 宏允许你去%
-访é\97®é\82£äº\9bå\86\85å\98ã\80\82è¿\99äº\9bå\87½æ\95°å\8fªå¤\84ç\90\86ä¸\80个å\97符ï¼\8cç\94¨ \cpp|copy_to_user| ä¸\8e \cpp|copy_from_user| %
-你可以处理几个字符。由于缓冲区(在读或写函数中)位于内核空间,因此对于写函数,你%
-é\9c\80è¦\81导å\85¥æ\95°æ\8d®ï¼\8cå\9b 为å®\83æ\9d¥è\87ªç\94¨æ\88·ç©ºé\97´ï¼\8cä½\86对äº\8e读å\87½æ\95°ï¼\8cå\88\99ä¸\8dé\9c\80è¦\81导å\85¥æ\95°æ\8d®ï¼\8cå\9b 为æ\95°æ\8d®å·²ç»\8f%
-在内核空间中。
+无需担心内存分段问题。当你编写内核模块时,通常需要访问内核内存段,即由系统自动%
+处理。然而,当内存缓冲区的内容需要在当前运行的进程和内核之间传递时,内核函数会%
+收到一个指向进程段中的内存缓冲区的指针。\cpp|put_user| 与 \cpp|get_user| 宏允%
+è®¸ä½ å\8e»è®¿é\97®é\82£äº\9bå\86\85å\98ã\80\82è¿\99äº\9bå\87½æ\95°å\8fªå¤\84ç\90\86ä¸\80个å\97符ï¼\8cç\94¨ \cpp|copy_to_user| ä¸\8e %
+\cpp|copy_from_user| 你可以处理几个字符。由于缓冲区(在读或写函数中)位于内核空%
+é\97´ï¼\8cå\9b æ¤å¯¹äº\8e write å\87½æ\95°ï¼\8cä½ é\9c\80è¦\81导å\85¥æ\95°æ\8d®ï¼\8cå\9b 为å®\83æ\9d¥è\87ªç\94¨æ\88·ç©ºé\97´ï¼\8cä½\86对äº\8e read å\87½æ\95°ï¼\8c%
+å\88\99ä¸\8dé\9c\80è¦\81导å\85¥æ\95°æ\8d®ï¼\8cå\9b 为æ\95°æ\8d®å·²ç»\8få\9c¨å\86\85æ ¸ç©ºé\97´ä¸ã\80\82
\samplec{examples/procfs2.c}
收到其它人的不同 ioctl,或如果他们收到你的 ioctl,你将知道有地方出错了。更多信息,%
查看内核源代码树中 \src{Documentation/userspace-api/ioctl/ioctl-number.rst}。
-å\8f¦å¤\96ï¼\8cæ\88\91们é\9c\80è¦\81å°\8få¿\83对å\85±äº«èµ\84æº\90ç\9a\84å¹¶å\8f\91访é\97®ï¼\8cå°\86导è\87´ç«\9f争条件产生。解决方法是使用原子 %
+å\8f¦å¤\96ï¼\8cæ\88\91们é\9c\80è¦\81å°\8få¿\83对å\85±äº«èµ\84æº\90ç\9a\84å¹¶å\8f\91访é\97®ï¼\8cå°\86导è\87´ç«\9e争条件产生。解决方法是使用原子 %
Compare-And-Swap (CAS),我们在 \ref{sec:chardev_c} 中提到了它,用它来强制执行%
独占访问。