-
Notifications
You must be signed in to change notification settings - Fork 0
/
device_zh.tex
202 lines (183 loc) · 9.82 KB
/
device_zh.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
\section{设备管理}
\subsection{设备驱动模型}
计算机的CPU通过总线连接到设备,设备驱动程序通过总线与设备沟通,
并给其他部分暴露出友好的接口.
为总线、设备、驱动的相互操作而建立的程序框架在Linux内核中被称作设备驱动模型.
\cite{devicemodel}\cite{bovet2005understanding}
设备模型是由一系列相互联系的对象表示的.
表示总线、设备、驱动等的对象之间相互连接,构成层次关系,以便相互操作.
把它们连接起来的是 \lstinline{struct kobject},\index{k@\lstinline{kobject}}
一个嵌入其他结构体中提供功能的结构
(嵌入的机制与 \ref{process identifiers} 中的 \lstinline{list_head} 一样),
这其实是为了在C语言的简单结构下实现几个较复杂功能的一系列hack.
\lstinline{kobject} 接口提供的主要功能有\cite{kobject}:
\begin{itemize}
\item 引用计数. 因为设备驱动模型对象之间相互引用的关系较复杂,
需要引用计数来确定对象的生命周期.
\item 在需要释放对象时,根据对象的类型调用自定义的释放函数.
\item 连接其他 \lstinline{kobject}.
设备和设备等之间需要构成父子关系.
不同对象之间还可以利用 \lstinline{kset} 或 \lstinline{klist} 构成collection.
collection 和父子关系构成了 \lstinline{kobject} 的层次结构.
\item 向用户空间提供接口.
\lstinline{kobject} 在内核登记后就对应sysfs 虚拟文件系统
(Arch Linux下挂载在 \lstinline{/sys})下的一个目录.
用户态程序可以通过该虚拟文件系统的接口访问内核内部的对象.
\item 事件的发送. 对象状态更改(如设备插拔)时可以向用户空间发送事件.
\end{itemize}
利用这些功能,内核就能构建一个灵活的设备驱动模型.
\subsubsection{总线}
总线由 \lstinline{struct bus_type} 表示.
\index{d@\lstinline{struct bus_type}}
所有设备在逻辑上都连在某一个总线上,所有驱动都要依靠总线访问设备.
因此总线的对象中的 \lstinline{struct subsys_private *p}
有两个装着 \lstinline{kobject} 的 \lstinline{kset},
分别引用该总线上的\emph{设备}和\emph{驱动}.
总线对象定义了总线普遍支持的操作,初始化具体的总线对象时要注册这些操作的函数指针,
其中一些操作是做一些总线相关的工作再调用设备的回调函数.
主要的操作有:
\begin{itemize}
\item 在加入新的设备或驱动的时候调用的操作.
\begin{itemize}
\item 检查总线上的某个指定设备是否与指定驱动相匹配;
\item probe,调用驱动的probe来把设备加入到驱动的管理中;
\item ……
\end{itemize}
\item 设备从总线上移除后的操作.
\item 为设备准备DMA的操作.
\item 管理设备电源、功耗的操作.
\end{itemize}
总线下的层次结构可以在sysfs 中看到.
每一种总线都在 \lstinline{/sys/bus/} 下有自己的目录.
目录下的 \lstinline{devices} 子目录是总线设备,
为指向全局的 \lstinline{devices} 子系统中的设备对象的符号链接;
\lstinline{drivers} 子目录下是总线的各个驱动.
例如 Listing~\ref{lst: pcie sysfs} 所示.
\begin{lstlisting}[caption={PCIE总线在sysfs中的结构}, label={lst: pcie sysfs}]
/sys/bus/pci-express
|-- devices
| |-- 0000:00:07.0:pcie001 -> ../../../../devices/pci...
...
| `-- 0000:00:1c.0:pcie010 -> ...
|-- drivers
| |-- aer
| |-- dpc
| |-- pciehp
| `-- pcie_pme
|-- drivers_autoprobe
|-- drivers_probe
`-- uevent
\end{lstlisting}
\subsubsection{设备}
设备在Linux设备驱动模型中的对象为 \lstinline{struct device}.
\index{d@\lstinline{struct device}}
其中存储的一些信息有:
\begin{itemize}
\item 设备的名字;
\item 设备的“父设备”,例如总线控制器、hub控制器等;
\item 设备所在的总线类型;
\item 设备使用的驱动;
\end{itemize}
\begin{qbox}{总线也是设备吗?}
值得注意的是虽然已经有表示总线类型的 \lstinline{bus_type},
总线也还是作为设备在设备驱动模型中表示,
一种总线可能是另一种总线的子设备,
例如挂在 PCI-E 总线下的 USB 总线.
\end{qbox}
\subsubsection{驱动}
设备驱动的对象是 \lstinline{struct device_driver}.
\index{d@\lstinline{struct device_driver}}
一个驱动对象只能存在一个实例,
也就是说驱动只有一份,一个驱动要管理与其关联的所有设备.
所以 \lstinline{struct device_driver} 的 \lstinline{struct driver_private *p}
要存储当前驱动所管理的所有设备.
向驱动添加设备的机制是存储在 \lstinline{struct device_driver} 中的函数指针
\lstinline{probe()}.\index{p@\lstinline{probe()}}
这个函数以 \lstinline{struct device} 为参数,应该做这样几件事\cite{devicedriver}:
\begin{itemize}
\item 确定设备确实存在;
\item 确定设备的型号和版本能被驱动支持;
\item 为设备需要的数据结构分配内存空间;
\item 初始化设备的硬件;
\item 最后把设备驱动和设备绑定在一起,填上各自的引用.
\end{itemize}
\index{d@\lstinline{struct device}}
\begin{readsrcbox}{设备驱动模型}
总线的表示 \lstinline{struct bus_type}\index{d@\lstinline{struct bus_type}}
的定义在 \linuxsrc{include/linux/device/bus.h}.
实际的总线定义自己的 \lstinline{struct bus_type},
如PCI的 \lstinline{struct pci_bus_type} 在 \linuxsrc{drivers/pci/pci-driver.c}.
设备的 \lstinline{struct device}\index{d@\lstinline{struct device}}
定义在 \linuxsrc{include/linux/driver.h}.
往往 \lstinline{struct device} 不单独使用,
特定总线的设备会把 \lstinline{struct device} 嵌入在一个有更多信息的结构体中.
比如PCI的 \lstinline{struct pci_dev} (\linuxsrc{include/linux/pci.h})、
USB的 \lstinline{struct usb_dev} (\linuxsrc{include/linux/usb.h}).
驱动的 \lstinline{struct device_driver} \index{d@\lstinline{struct device_driver}}
在 \linuxsrc{include/linux/device/driver.h}.
同样特定总线的驱动会把 \lstinline{struct device_driver} 嵌入到自己定义的结构体中.
\end{readsrcbox}
\subsection{设备驱动过程}
在 \ref{loading modules} 中,我们提到内核启动时,
bootloader加载vmlinux镜像,内核代码开始运行.
在开始启动init进程和运行init程序之间,
内核首先做最核心的初始化,这其中就包括设备驱动模型的初始化.
系统的总线等重要的设备的内核模块是静态连接到 vmlinux 内的,
在设备驱动模型初始化完成后,
这些模块的初始化程序会运行,注册对应的设备和总线类型,对总线进行初始化并设置中断.
这样基本的驱动框架就建立好了,总线的设备驱动就可以对总线上的设备进行搜索.
每当找到一个新的设备,首先初始化相关的对象,
然后通过总线的 \lstinline{match()} 和驱动的 \lstinline{probe()}
来遍历该总线下的所有驱动,如果发现匹配的驱动,则停止遍历,
把设备和驱动绑定起来,这样设备模型的总线、设备、驱动就都联系起来了.
现在再把用户空间的操作考虑进去.
嵌有 \lstinline{kobject} 的设备、驱动对象
在创建、删除的时候都会向用户空间传递 \lstinline{uevent}.
用户空间的程序可以通过监听事件,并且通过 sysfs 对设备驱动模型进行操作,
从而支持更灵活的设备使用方式,如热插拔.
在用户态受限的环境下管理设备的好处还有稳定性和安全性的提高.
同样在 \ref{loading modules} 中提过,
包括 Arch Linux 在内的大多数x86发行版都使用systemd的udev\index{udev}
系统来在用户态管理设备.
udev负责监听 \lstinline{uevent} 事件,
根据udev自带的和用户自定义的规则来做出对应的动作,
管理 \lstinline{/dev} 下的 device node 文件,
使其他程序可以通过 \lstinline{/dev} 下的文件与设备驱动程序交互.
udev规则一般可以实现这样的一些功能\cite{WritingUdev}:
\begin{itemize}
\item 加载、卸载驱动所在的模块(见 \ref{loading modules}).
\item 在 \lstinline{/dev} 下为设备设置友好的名字.
\item 通知其他程序,传递设备的事件.
\item 进行系统管理,如挂载刚刚插入的硬盘.
\end{itemize}
使用udev的命令行工具 \lstinline{udevadm} 可以直观地看到设备状态变化产生的操作.
比如运行 \lstinline{udevadm monitor --property} 命令,插入一个USB键盘,就可以看到udev根据规则和事件创建的device node.
\begin{lstlisting}
UDEV [401515.997363] add /devices/pci0 ... /usb3/.../event12 (input)
ACTION=add
DEVPATH=/devices/ ... /usb3/ ... /event12
SUBSYSTEM=input
DEVNAME=/dev/input/event12
\end{lstlisting}
\begin{notebox}
这里描述的过程进行了大量的简化,许多重要的细节都忽略了.
例如驱动可能在 \lstinline{probe()} 时还为准备好,
这时它可以要求推迟 \lstinline{probe()} 的操作.
\end{notebox}
\begin{readsrcbox}{\lstinline{driver_init()}}
初始化设备模型的函数入口为 \lstinline{driver_init()},
位于 \linuxsrc{drivers/base/init.c},
由 \linuxsrc{init/main.c} 中的 \lstinline{do_basic_setup()} 调用.
\linuxsrc{drivers/base} 目录为内核驱动的“core”部分,
负责硬件驱动模型内部的初始化和实现模型内部的功能等.
\linuxsrc{drivers/base/core.c} 中的 \lstinline{device_add()}
处理添加设备时的一系列操作,
调用 \lstinline{bus_probe_device(dev);} 来匹配驱动.
\lstinline{bus_probe_device()} 位于 \linuxsrc{drivers/base/dd.c},
dd是“device/driver”的缩写,该文件中的函数处理设备和驱动之间的关系,
例如绑定、通信等.
\end{readsrcbox}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "linux_zh"
%%% End: