系统调用-write
Linux 系统调用 write 深度解析:向内核提交数据的统一接口
在 Linux 应用开发中,write 是最基础、最重要的系统调用之一。无论是向终端输出文本、写日志文件、向串口发送字节、对 I2C/SPI 设备写寄存器,还是通过 socket 发送网络数据,都依赖 write。因此,理解 write 的工作机制,有助于全面掌握 Linux 的统一 I/O 体系。
1. write 的函数原型
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数说明如下:
| 参数 | 类型 | 说明 |
|---|---|---|
fd |
int | 文件描述符,由 open、socket 等函数返回 |
buf |
void* | 用户空间待写入的数据缓存区域 |
count |
size_t | 请求写入的字节数 |
返回值:
- 成功:实际写入的字节数(可能小于
count) - 失败:返回 -1,并设置 errno
2. write 的核心作用
调用 write 并不意味着直接把数据写入磁盘或设备,而是:
将用户态数据复制到内核态,再由对应的文件对象(file object)进行处理。
不同类型的文件对象(普通文件、设备文件、socket、管道等)对写入数据的处理方式完全不同。例如:
- 普通文件 → 写入页缓存(page cache)
- 终端 → 输出到终端驱动
- 串口
/dev/ttyS1→ 经 UART 驱动发送字节 - I2C
/dev/i2c-1→ 驱动将数据封装成 I2C 传输 - Socket → 进入网络协议栈
- 管道/FIFO → 写入内核缓冲区
由此可见,write 是 Linux I/O 抽象的统一入口。
3. 示例:向文件写入内容
以下示例展示如何向普通文件写数据:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("open");
return -1;
}
const char *msg = "Hello Linux write!\n";
ssize_t n = write(fd, msg, strlen(msg));
if (n < 0) {
perror("write");
return -1;
}
printf("write %zd bytes\n", n);
close(fd);
return 0;
}
4. 为什么实际写入字节可能少于 count?
write 的返回值不一定等于预期写入字节数,可能的原因包括:
● 被信号中断(EINTR)
写入尚未完成便被打断。
● 文件系统空间不足
导致部分写入失败。
● 写入 socket 或管道
缓冲区已满,只能写入部分数据。
● 写入设备文件
设备驱动自身可能只接收固定大小的数据包。
因此,对关键写操作通常需要循环调用 write:
ssize_t total = 0;
while (total < len) {
ssize_t n = write(fd, buf + total, len - total);
if (n <= 0) {
if (errno == EINTR) continue;
perror("write");
break;
}
total += n;
}
5. 内核执行流程简述
一次完整的 write 调用通常包含以下步骤:
用户态 buffer
↓ copy_from_user()
内核态临时 buffer
↓
file 对象的 file_operations->write() 回调
↓
文件系统 / 设备驱动 / 网络协议栈
↓
最终输出到存储介质或硬件设备
该流程说明:
- 写文件通常并非实时落盘,而是先进入 page cache
- 对设备文件的写入行为由驱动完全决定
- 对网络 socket 的写入将由协议栈处理
6. 与嵌入式系统的类比
在嵌入式开发中通常会看到类似:
uart_write();
i2c_write();
flash_write();
而在 Linux 中,上述操作全部统一抽象为:
write(fd, buf, len);
驱动开发者只需实现:
ssize_t mydriver_write(struct file *file, const char __user *buf, size_t count);
即可通过设备节点提供标准的写接口。
7. 常见错误与排查方法
● 权限错误(EACCES)
检查:
ls -l <file>
必要时使用 chmod 或 sudo。
● 写入设备失败
常见原因:
- 设备未初始化
- 驱动返回 -EIO
- 设备忙(EBUSY)
- 缓冲区溢出
建议利用 strace 观察行为:
strace -e write ./program
● 写入 /dev/video0 无效
V4L2 大部分操作依赖 ioctl 而非 write,属于正常情况。
8. 示例:向串口写数据
int fd = open("/dev/ttyS1", O_RDWR | O_NOCTTY);
char buf[] = "AT\r\n";
write(fd, buf, sizeof(buf));
9. 示例:向 socket 写数据
write(sockfd, "hello", 5);
此时数据将由 TCP/UDP 协议栈处理。
10. 小结
write 是 Linux I/O 框架中的核心系统调用。其主要特点包括:
- 提供向文件对象提交数据的统一接口
- 实际写入行为由文件系统或驱动决定
- 返回值可能小于请求写入字节数
- 对普通文件而言通常会先写入 cache
- 对设备文件和 socket 而言具有完全不同的语义
理解 write,能够为进一步掌握 Linux 关于设备驱动、文件系统、网络编程等领域打下坚实基础。

浙公网安备 33010602011771号