> For the complete documentation index, see [llms.txt](https://book.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://book.bsdcn.org/4.3-bsd-unix-cao-zuo-xi-tong-she-ji-yu-shi-xian/di-san-bu-fen-io-xi-tong/8.4.-zi-fu-she-bei.md).

# 8.4 字符设备

字符设备由 `cdevsw` 表中的表项描述。这张表中的入口点（见表 8.1）既支持对面向块设备的裸访问，也支持经终端驱动对面向字符设备的正常访问。两类设备需求迥异，入口点集合实际是两个不相交集的并集：裸设备支持块设备驱动入口点的一个子集，字符设备则支持全集。以下分别描述。

表 8.1 字符设备与裸设备驱动的入口点

| 入口点        | 功能             |
| ---------- | -------------- |
| `open()`   | 打开设备           |
| `close()`  | 关闭设备           |
| `ioctl()`  | 执行 I/O 控制操作    |
| `mmap()`   | 把设备内容映射进内存     |
| `read()`   | 执行输入操作         |
| `reset()`  | 总线复位后重新初始化设备   |
| `select()` | 轮询设备的 I/O 就绪状态 |
| `stop()`   | 停止设备上的输出       |
| `write()`  | 执行输出操作         |

**裸设备与物理 I/O**

多数裸设备与块设备的差别只在 I/O 的做法。块设备对系统缓冲缓存读写数据，裸设备则对用户数据缓冲传输。绕过缓冲缓存省去了块设备必做的内存到内存复制，却也剥夺了应用享受数据缓存的好处。此外，同时支持裸、块两种访问的设备，应用必须小心保持缓冲缓存中的数据与直接写入设备的数据一致——裸设备应只在块设备空闲时使用。许多文件系统工具使用裸设备访问，如文件系统检查程序 **fsck**，以及读写磁带的程序，如 **tar**、**dump** 和 **restore**。

裸设备绕过缓冲缓存，就得自行管理缓冲结构。每个裸设备驱动维护一组静态的缓冲结构，支持裸 I/O 的每个部件一个。读写例程用 `physio()` 例程发起裸 I/O 操作（见图 8.1）。`strategy` 参数标识一个用来在设备上发起 I/O 操作的块设备 strategy 例程；`physio()` 用 `bp` 所指的缓冲构造给 strategy 例程的请求；设备、读写标志和 `uio` 参数完整规定应做的 I/O 操作。`physio()` 调用 `minphys()` 例程在传输交给 strategy 例程前调整每次 I/O 传输的大小，使传输可按设备支持的最大传输量分段进行。

`physio()` 的操作包括同步对做 I/O 所用缓冲结构的访问。同步是必需的，因为每个设备只维护一个做裸 I/O 的缓冲。有相应的检查，确保一次只有一个进程执行使用裸缓冲的代码段。除此之外并无别的理由把每设备的裸 I/O 限成单线程——后文会看到，设备驱动按需把 I/O 操作排队到控制器。4.3BSD Tahoe 发行版借用交换缓冲结构而不使用每设备缓冲，避免了裸 I/O 请求的这种单线程化。

```c
physio(strategy, bp, dev, flags, minphys, uio)
    int strategy();
    buffer *bp;
    device dev;
    int flags;
    int minphys();
    struct uio *uio;
{
    检查用户对数据缓冲的读写权限；
    把处理器优先级提升到 splbio；
    while (缓冲被标为忙) {
        标记缓冲有人等待；
        等缓冲可用；
    }
    降低优先级；
    为传输设置缓冲的固定部分；
    while (还有数据要传输且无 I/O 错误) {
        为物理 I/O 标记缓冲为忙；
        按最大传输量设置缓冲；
        调用 minphys 限制传输大小；
        锁住传输涉及的用户地址空间部分；
        调用 strategy 启动传输；
        把优先级提升到 splbio；
        等传输完成；
        解锁先前锁住的地址空间部分；
        降低优先级；
        从待传输总量中扣除本次传输量；
    }
    清理缓冲状态；
    if (另有进程在等裸 I/O 缓冲)
        唤醒等待做物理 I/O 的进程；
}
```

图 8.1 物理 I/O 的算法

裸设备的 I/O 操作请求硬件设备直接传入或传出用户程序地址空间中由 `uio` 参数描述的数据缓冲。因此，与从内核地址空间的缓冲做直接存储器访问（DMA）的 I/O 操作不同，裸 I/O 操作必须检查设备能否访问用户缓冲，并在传输期间把它锁进内存。

**面向字符的设备**

面向字符的 I/O 设备以终端多路复用器为典型，也包括打印机和其他面向字符或行的设备。这些设备通常经第 9 章描述的终端驱动访问。与终端驱动的紧密关系深刻影响了字符设备驱动的结构：例如 `cdevsw` 结构中的若干入口点专为通用终端处理程序与终端多路复用器硬件驱动之间的通信而设。这一接口在第 9 章完整描述。

**字符设备驱动的入口点**

字符设备的驱动由 `cdevsw` 表中的表项定义。该结构含许多与 `bdevsw` 表项相同的入口点。

`open`、`close`——与块设备驱动的同名入口点功能相仿。事实上，对只提供块设备裸访问的字符设备，这些入口点通常相同。但有些块设备没有这些入口点，而多数字符设备则有。

`read`——从设备读数据。裸设备的这一入口点通常只带设备特有参数调用 `physio()`。终端型设备的读请求立即交给终端驱动。其他设备的读请求则要求先把指定数据复制进内核地址空间（通常用 `uiomove()` 例程），再交给设备。

`write`——向设备写数据。这一入口点与 read 完全平行：裸设备用 `physio()`，终端型设备调终端驱动，其他设备内部处理请求。

`ioctl`——执行读写之外的操作。这一入口起初为终端设备提供获取和设置参数的机制，后来用途扩展到其他设备。历史上 `ioctl()` 操作因设备而差异悬殊；4.3BSD 则为所有磁带设备定义了一组操作：定位磁带、返回部件状态、写文件结束标记、使磁带机脱机。

`select`——检查设备有无数据可读和/或有空间可写。select 入口点供 `select` 系统调用检查设备特殊文件关联的文件描述符之用。裸设备的数据不经缓冲，select 操作无意义，入口点设为 `seltrue()`——对任何 select 请求都返回真的例程。配合终端驱动使用的设备，入口点设为 `ttselect()`，见第 9 章。

`stop`——停止设备上的输出。stop 例程只对配合终端驱动使用的设备有意义：终端驱动收到 **停止字符**（如 “S”）或准备清空输出队列时，用它停止一条线路上的传输。

`mmap`——把设备内容映射进内存。这一入口点为 `mmap` 系统调用预备，语义尚未定义。

`reset`——总线复位后重置设备状态。reset 例程在总线复位后由总线适配器支持例程调用。设备驱动应重新初始化硬件、重置与适配器相关的驱动状态。例如做 DMA 传输的设备必须分配把数据缓冲映射进 I/O 总线地址空间的资源；总线复位时，总线适配器例程撤销所有此类分配，设备驱动必须重新分配所需资源；传输若在进行中还必须重启。这一例程为 VAX 体系结构专有。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://book.bsdcn.org/4.3-bsd-unix-cao-zuo-xi-tong-she-ji-yu-shi-xian/di-san-bu-fen-io-xi-tong/8.4.-zi-fu-she-bei.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
