aboutsummaryrefslogtreecommitdiff
path: root/documentation/content/zh-cn/books/arch-handbook/driverbasics/_index.adoc
blob: cae4b14fbebab9043eaa3a34f255afd60f2ee439 (plain) (blame)
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
---
title: 第 9 章 编写 FreeBSD 设备驱动程序
prev: books/arch-handbook/partii
next: books/arch-handbook/isa
showBookMenu: true
weight: 11
path: "/books/arch-handbook/driverbasics/"
---

[[driverbasics]]
= 编写 FreeBSD 设备驱动程序
:doctype: book
:toc: macro
:toclevels: 1
:icons: font
:sectnums:
:sectnumlevels: 6
:sectnumoffset: 9
:partnums:
:source-highlighter: rouge
:experimental:
:images-path: books/arch-handbook/

ifdef::env-beastie[]
ifdef::backend-html5[]
:imagesdir: ../../../../images/{images-path}
endif::[]
ifndef::book[]
include::shared/authors.adoc[]
include::shared/mirrors.adoc[]
include::shared/releases.adoc[]
include::shared/attributes/attributes-{{% lang %}}.adoc[]
include::shared/{{% lang %}}/teams.adoc[]
include::shared/{{% lang %}}/mailing-lists.adoc[]
include::shared/{{% lang %}}/urls.adoc[]
toc::[]
endif::[]
ifdef::backend-pdf,backend-epub3[]
include::../../../../../shared/asciidoctor.adoc[]
endif::[]
endif::[]

ifndef::env-beastie[]
toc::[]
include::../../../../../shared/asciidoctor.adoc[]
endif::[]

[[driverbasics-intro]]
== 简介

本章简要介绍了如何为FreeBSD编写设备驱动程序。术语设备在 这儿的上下文中多用于指代系统中硬件相关的东西,如磁盘,打印机, 图形显式器及其键盘。设备驱动程序是操作系统中用于控制特定设备的 软件组件。也有所谓的伪设备,即设备驱动程序用软件模拟设备的行为, 而没有特定的底层硬件。设备驱动程序可以被静态地编译进系统,或者 通过动态内核链接工具'``kld``'在需要时加载。

类UNIX(R)操作系统中的大多数设备都是通过设备节点来访问的,有时也 被称为特殊文件。这些文件在文件系统的层次结构中通常位于 [.filename]##/dev##目录下。在FreeBSD 5.0-RELEASE以前的 发行版中, 对man:devfs[5]的支持还没有被集成到FreeBSD中,每个设备 节点必须要静态创建,并且独立于相关设备驱动程序的存在。系统中大 多数设备节点是通过运行``MAKEDEV``创建的。

设备驱动程序可以粗略地分为两类,字符和网络设备驱动程序。

[[driverbasics-kld]]
== 动态内核链接工具-KLD

kld接口允许系统管理员从运行的系统中动态地添加和删除功能。 这允许设备驱动程序的编写者将他们的新改动加载到运行的内核中, 而不用为了测试新改动而频繁地重启。

kld接口通过下面的特权命令使用: 

* `kldload` - 加载新内核模块 
* `kldunload` - 卸载内核模块 
* `kldstat` - 列举当前加载的模块 

内核模块的程序框架

[.programlisting]
....
/*
 * KLD程序框架
 * 受Andrew Reiter在Daemonnews上的文章所启发
 */

#include sys/types.h
#include sys/module.h
#include sys/systm.h  /* uprintf */
#include sys/errno.h
#include sys/param.h  /* kernel.h中用到的定义 */
#include sys/kernel.h /* 模块初始化中使用的类型 */

/*
 * 加载处理函数,负责处理KLD的加载和卸载。
 */

static int
skel_loader(struct module *m, int what, void *arg)
{
  int err = 0;

  switch (what) {
  case MOD_LOAD:                /* kldload */
    uprintf("Skeleton KLD loaded.\n");
    break;
  case MOD_UNLOAD:
    uprintf("Skeleton KLD unloaded.\n");
    break;
  default:
    err = EOPNOTSUPP;
    break;
  }
  return(err);
}

/* 向内核其余部分声明此模块 */

static moduledata_t skel_mod = {
  "skel",
  skel_loader,
  NULL
};

DECLARE_MODULE(skeleton, skel_mod, SI_SUB_KLD, SI_ORDER_ANY);
....

=== Makefile

FreeBSD提供了一个makefile包含文件,利用它你可以快速地编译 你附加到内核的东西。

[.programlisting]
....
SRCS=skeleton.c
KMOD=skeleton

.include bsd.kmod.mk
....

简单地用这个makefile运行``make``就能够创建文件 [.filename]#skeleton.ko#,键入如下命令可以把它加载到内核: 

[source,shell]
....
# kldload -v ./skeleton.ko
....

[[driverbasics-access]]
== 访问设备驱动程序

UNIX(R) 提供了一套公共的系统调用供用户的应用程序使用。当用户访问 设备节点时,内核的上层将这些调用分发到相应的设备驱动程序。脚本 ``/dev/MAKEDEV``为你的系统生成了大多数的设备节点, 但如果你正在开发你自己的驱动程序,可能需要用 ``mknod``创建你自己的设备节点。 

=== 创建静态设备节点

``mknod``命令需要四个参数来创建设备节点。 你必须指定设备节点的名字,设备的类型,设备的主号码和设备的从号码。 

=== 动态设备节点

设备文件系统,或者说devfs,在全局文件系统名字空间中提供对 内核设备名字空间的访问。这消除了由于有设备驱动程序而没有静态 设备节点,或者有设备节点而没有安装设备驱动程序而带来的潜在问题。 Devfs仍在进展中,但已经能够工作得相当好了。

[[driverbasics-char]]
== 字符设备

字符设备驱动程序直接从用户进程传输数据,或传输数据到用户进程。 这是最普通的一类设备驱动程序,源码树中有大量的简单例子。

这个简单的伪设备例子会记住你写给它的任何值,并且当你读取它的时候 会将这些值返回给你。下面显示了两个版本,一个适用于FreeBSD 4.X, 一个适用于FreeBSD 5.X。

.适用于FreeBSD 4.X的回显伪设备驱动程序实例
[example]
====
[.programlisting]
....
/*
 * 简单‘echo’伪设备KLD
 *
 * Murray Stokely
 */

#define MIN(a,b) (((a)  (b)) ? (a) : (b))

#include sys/types.h
#include sys/module.h
#include sys/systm.h  /* uprintf */
#include sys/errno.h
#include sys/param.h  /* kernel.h中用到的定义 */
#include sys/kernel.h /* 模块初始化中使用的类型 */
#include sys/conf.h   /* cdevsw结构 */
#include sys/uio.h    /* uio结构 */
#include sys/malloc.h

#define BUFFERSIZE 256

/* 函数原型 */
d_open_t	echo_open;
d_close_t	echo_close;
d_read_t	echo_read;
d_write_t	echo_write;

/* 字符设备入口点 */
static struct cdevsw echo_cdevsw = {
	echo_open,
	echo_close,
	echo_read,
	echo_write,
	noioctl,
	nopoll,
	nommap,
	nostrategy,
	"echo",
	33,              /* 为lkms保留 - /usr/src/sys/conf/majors */
	nodump,
	nopsize,
	D_TTY,
	-1
};

typedef struct s_echo {
	char msg[BUFFERSIZE];
	int len;
} t_echo;

/* 变量 */
static dev_t sdev;
static int count;
static t_echo *echomsg;

MALLOC_DECLARE(M_ECHOBUF);
MALLOC_DEFINE(M_ECHOBUF, "echobuffer", "buffer for echo module");

/*
 * 这个函数被kld[un]load(2)系统调用来调用,
 * 以决定加载和卸载模块时需要采取的动作。
 */

static int
echo_loader(struct module *m, int what, void *arg)
{
	int err = 0;

	switch (what) {
	case MOD_LOAD:                /* kldload */
		sdev = make_dev(echo_cdevsw,
		    0,
		    UID_ROOT,
		    GID_WHEEL,
		    0600,
		    "echo");
		/* kmalloc分配供驱动程序使用的内存 */
		MALLOC(echomsg, t_echo *, sizeof(t_echo), M_ECHOBUF, M_WAITOK);
		printf("Echo device loaded.\n");
		break;
	case MOD_UNLOAD:
		destroy_dev(sdev);
		FREE(echomsg,M_ECHOBUF);
		printf("Echo device unloaded.\n");
		break;
	default:
		err = EOPNOTSUPP;
		break;
	}
	return(err);
}

int
echo_open(dev_t dev, int oflags, int devtype, struct proc *p)
{
	int err = 0;

	uprintf("Opened device \"echo\" successfully.\n");
	return(err);
}

int
echo_close(dev_t dev, int fflag, int devtype, struct proc *p)
{
	uprintf("Closing device \"echo.\"\n");
	return(0);
}

/*
 * read函数接受由echo_write()存储的buf,并将其返回到用户空间,
 * 以供其他函数访问。
 * uio(9)
 */

int
echo_read(dev_t dev, struct uio *uio, int ioflag)
{
	int err = 0;
	int amt;

	/*
	 * 这个读操作有多大?
	 * 与用户请求的大小一样,或者等于剩余数据的大小。
	 */
	amt = MIN(uio-uio_resid, (echomsg-len - uio-uio_offset  0) ?
	    echomsg-len - uio-uio_offset : 0);
	if ((err = uiomove(echomsg-msg + uio-uio_offset,amt,uio)) != 0) {
		uprintf("uiomove failed!\n");
	}
	return(err);
}

/*
 * echo_write接受一个字符串并将它保存到缓冲区,用于以后的访问。
 */

int
echo_write(dev_t dev, struct uio *uio, int ioflag)
{
	int err = 0;

	/* 将字符串从用户空间的内存复制到内核空间 */
	err = copyin(uio-uio_iov-iov_base, echomsg-msg,
	    MIN(uio-uio_iov-iov_len, BUFFERSIZE - 1));

	/* 现在需要以null结束字符串,并记录长度 */
	*(echomsg-msg + MIN(uio-uio_iov-iov_len, BUFFERSIZE - 1)) = 0;
	echomsg-len = MIN(uio-uio_iov-iov_len, BUFFERSIZE);

	if (err != 0) {
		uprintf("Write failed: bad address!\n");
	}
	count++;
	return(err);
}

DEV_MODULE(echo,echo_loader,NULL);
....

====

.适用于FreeBSD 5.X回显伪设备驱动程序实例
[example]
====
[.programlisting]
....
/*
 * 简单‘echo’伪设备 KLD
 *
 * Murray Stokely
 *
 * 此代码由Søren (Xride) Straarup转换到5.X
 */

#include sys/types.h
#include sys/module.h
#include sys/systm.h  /* uprintf */
#include sys/errno.h
#include sys/param.h  /* kernel.h中用到的定义 */
#include sys/kernel.h /* 模块初始化中使用的类型 */
#include sys/conf.h   /* cdevsw结构 */
#include sys/uio.h    /* uio结构 */
#include sys/malloc.h

#define BUFFERSIZE 256

/* 函数原型 */
static d_open_t      echo_open;
static d_close_t     echo_close;
static d_read_t      echo_read;
static d_write_t     echo_write;

/* 字符设备入口点 */
static struct cdevsw echo_cdevsw = {
	.d_version = D_VERSION,
	.d_open = echo_open,
	.d_close = echo_close,
	.d_read = echo_read,
	.d_write = echo_write,
	.d_name = "echo",
};

typedef struct s_echo {
	char msg[BUFFERSIZE];
	int len;
} t_echo;

/* 变量 */
static struct cdev *echo_dev;
static int count;
static t_echo *echomsg;

MALLOC_DECLARE(M_ECHOBUF);
MALLOC_DEFINE(M_ECHOBUF, "echobuffer", "buffer for echo module");

/*
 * 这个函数被kld[un]load(2)系统调用来调用,
 * 以决定加载和卸载模块时需要采取的动作.
 */

static int
echo_loader(struct module *m, int what, void *arg)
{
	int err = 0;

	switch (what) {
	case MOD_LOAD:                /* kldload */
		echo_dev = make_dev(echo_cdevsw,
		    0,
		    UID_ROOT,
		    GID_WHEEL,
		    0600,
		    "echo");
		/* kmalloc分配供驱动程序使用的内存 */
		echomsg = malloc(sizeof(t_echo), M_ECHOBUF, M_WAITOK);
		printf("Echo device loaded.\n");
		break;
	case MOD_UNLOAD:
		destroy_dev(echo_dev);
		free(echomsg, M_ECHOBUF);
		printf("Echo device unloaded.\n");
		break;
	default:
		err = EOPNOTSUPP;
		break;
	}
	return(err);
}

static int
echo_open(struct cdev *dev, int oflags, int devtype, struct thread *p)
{
	int err = 0;

	uprintf("Opened device \"echo\" successfully.\n");
	return(err);
}

static int
echo_close(struct cdev *dev, int fflag, int devtype, struct thread *p)
{
	uprintf("Closing device \"echo.\"\n");
	return(0);
}

/*
 * read函数接受由echo_write()存储的buf,并将其返回到用户空间,
 * 以供其他函数访问。
 * uio(9)
 */

static int
echo_read(struct cdev *dev, struct uio *uio, int ioflag)
{
	int err = 0;
	int amt;

	/*
	 * 这个读操作有多大?
	 * 等于用户请求的大小,或者等于剩余数据的大小。
	 */
	amt = MIN(uio-uio_resid, (echomsg-len - uio-uio_offset  0) ?
	     echomsg-len - uio-uio_offset : 0);
	if ((err = uiomove(echomsg-msg + uio-uio_offset, amt, uio)) != 0) {
		uprintf("uiomove failed!\n");
	}
	return(err);
}

/*
 * echo_write接受一个字符串并将它保存到缓冲区, 用于以后的访问.
 */

static int
echo_write(struct cdev *dev, struct uio *uio, int ioflag)
{
	int err = 0;

	/* 将字符串从用户空间的内存复制到内核空间 */
	err = copyin(uio-uio_iov-iov_base, echomsg-msg,
	    MIN(uio-uio_iov-iov_len, BUFFERSIZE - 1));

	/* 现在需要以null结束字符串,并记录长度 */
	*(echomsg-msg + MIN(uio-uio_iov-iov_len, BUFFERSIZE - 1)) = 0;
	echomsg-len = MIN(uio-uio_iov-iov_len, BUFFERSIZE);

	if (err != 0) {
		uprintf("Write failed: bad address!\n");
	}
	count++;
	return(err);
}

DEV_MODULE(echo,echo_loader,NULL);
....

====

在FreeBSD 4.X上安装此驱动程序,你将首先需要用如下命令在 你的文件系统上创建一个节点:

[source,shell]
....
# mknod /dev/echo c 33 0
....

驱动程序被加载后,你应该能够键入一些东西,如:

[source,shell]
....
# echo -n "Test Data" > /dev/echo
# cat /dev/echo
Test Data
....

真正的硬件设备在下一章描述。

补充资源 

* http://ezine.daemonnews.org/200010/blueprints.html[Dynamic Kernel Linker (KLD) Facility Programming Tutorial] - http://www.daemonnews.org/[Daemonnews] October 2000
* http://ezine.daemonnews.org/200007/newbus-intro.html[How to Write Kernel Drivers with NEWBUS] - http://www.daemonnews.org/[Daemonnews] July 2000

[[driverbasics-block]]
== 块设备(消亡中)

其他UNIX(R)系统支持另一类型的磁盘设备,称为块设备。块设备是内核 为它们提供缓冲的磁盘设备。这种缓冲使得块设备几乎没有用,或者说非常 不可靠。缓冲会重新安排写操作的次序,使得应用程序丧失了在任何时刻及时 知道准确的磁盘内容的能力。这导致对磁盘数据结构(文件系统,数据库等)的 可预测的和可靠的崩溃恢复成为不可能。由于写操作被延迟,内核无法向应用 程序报告哪个特定的写操作遇到了写错误,这又进一步增加了一致性问题。 由于这个原因,真正的应用程序从不依赖于块设备,事实上,几乎所有访问 磁盘的应用程序都尽力指定总是使用字符(或"raw")设备。 由于实现将每个磁盘(分区)同具有不同语义的两个设备混为一谈,从而致使 相关内核代码极大地复杂化,作为推进磁盘I/O基础结构现代化的一部分,FreeBSD 抛弃了对带缓冲的磁盘设备的支持。

[[driverbasics-net]]
== 网络设备驱动程序

访问网络设备的驱动程序不需要使用设备节点。选择哪个驱动程序是 基于内核内部的其他决定而不是调用open(),对网络设备的使用通常由 系统调用socket(2)引入。

更多细节, 请参见 ifnet(9) 联机手册、 回环设备的源代码, 以及 Bill Paul 撰写的网络驱动程序。