C+Linux 标准IO 系统IO 硬件操作

分类: C语言

在 Linux 环境下,“万物皆文件”(Everything is a file)是核心哲学。访问硬件本质上就是通过 C 语言对 /dev/ 目录下的设备文件进行读写操作。

通常有两种主要路径:系统调用(System IO)和标准库(Standard IO)

Linux 的做法是: 把这些差异全部封装在内核的驱动程序里。驱动程序负责把硬件的电平信号、协议转换成“流”。 对于 C 程序员来说,只需要学习一套 open/read/write/close,就能控制几乎所有的硬件。


1. 系统调用 IO (低级 IO)

这是最常用、最直接的方式。它直接与内核交互,没有应用层缓冲区,适合对实时性要求高或需要特殊控制(如控制引脚、设置波特率)的硬件。

核心函数

  • open(): 打开设备文件。
  • read() / write(): 接收/发送数据。
  • ioctl(): 硬件控制的关键。用于执行非标准的文件操作(如设置串口参数、控制电机转速、读取传感器寄存器)。
  • close(): 关闭设备。

示例代码:操作 GPIO (以伪代码形式)

C

#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdio.h>

int main() {
    // 1. 打开设备文件
    int fd = open("/dev/my_hardware_device", O_RDWR);
    if (fd < 0) {
        perror("打开设备失败");
        return -1;
    }

    // 2. 写入数据(例如控制 LED 亮)
    char buf[] = {0x01};
    write(fd, buf, sizeof(buf));

    // 3. IOCTL 控制(例如设置硬件模式)
    // IOCTL_SET_MODE 是驱动程序定义的宏
    // ioctl(fd, IOCTL_SET_MODE, arg);

    // 4. 关闭设备
    close(fd);
    return 0;
}

2. 标准库 IO (高级 IO)

使用 stdio.h 中的函数。它在系统调用之上增加了一个用户态缓冲区

核心函数

  • fopen() / fclose()
  • fread() / fwrite()
  • fprintf() / fscanf() (常用于访问 Sysfs 接口)

适用场景:Sysfs 接口

在现代 Linux(如树莓派、嵌入式开发)中,很多硬件被映射为 /sys/class/ 下的文本文件。由于这些接口是基于文本的,使用标准 IO 会非常方便。

C

// 示例:通过 sysfs 修改 LED 亮度
FILE *fp = fopen("/sys/class/leds/led0/brightness", "w");
if (fp) {
    fprintf(fp, "%d", 255); // 写入亮度值
    fclose(fp);
}

3. 两者的对比选择

特性 系统 IO (open/read) 标准 IO (fopen/fread)
缓冲区 无(直接进内核) 有(减少系统调用次数)
实时性 较高,响应快 较低,受缓存影响
功能性 支持 ioctl 等底层控制 仅限流式读写
硬件场景 字符设备、原始数据流、驱动交互 配置文件、Sysfs 虚拟文件

4. 关键点:内存映射 (mmap)

对于需要高速访问硬件(如显存、高速采样、大块 DMA 缓冲区)的情况,读写文件的效率可能不够。此时会使用 mmap()

mmap 将设备文件直接映射到进程的地址空间。你可以像操作普通 C 语言数组/指针一样直接操作硬件寄存器,跳过了数据在内核态和用户态之间的多次拷贝。


5. 注意事项

  1. 权限问题:访问 /dev/ 下的文件通常需要 root 权限或特定的用户组(如 dialout 组用于串口)。
  2. 阻塞与非阻塞:在 open 时可以使用 O_NONBLOCK 标志。如果硬件数据没准备好,读取操作会立即返回而不是等待。
  3. 驱动支持:所有的 IO 操作前提是内核已经加载了对应的硬件驱动,并生成了对应的设备节点。

您是想针对具体的硬件(如串口 UART、I2C 传感器、还是 GPIO)进行编程吗? 如果是的话,我可以为您提供更具针对性的 ioctl 参数说明或代码模板。