linux驱动开发学习日志

1. 驱动开发中的I/O模型

I/O模型是操作系统和设备进行交互的核心。通常,I/O操作分为三种常见模式:阻塞I/O非阻塞I/O、以及异步I/O。在驱动开发中,理解和使用这些I/O模型非常重要。

(1) 阻塞I/O(Blocking I/O)

在阻塞I/O中,I/O操作会一直等待硬件设备的响应,直到设备准备好完成数据的读写操作为止。在这种模型下,调用I/O函数的进程会被阻塞,直到I/O操作完成。

优点

  • 简单且易于实现。
  • 程序员可以按照顺序编写代码,符合直观的逻辑。

缺点

  • 效率较低,可能导致进程长时间阻塞,浪费系统资源。
(2) 非阻塞I/O(Non-blocking I/O)

在非阻塞I/O模型中,系统调用会立即返回,而不会等待设备准备好。通过轮询,程序可以持续检查设备是否就绪,并在设备就绪时再执行I/O操作。

优点

  • 提高了效率,不会让进程被I/O操作长时间阻塞。

缺点

  • 程序逻辑复杂化,开发者需要处理设备未准备好的情况,需要不断轮询设备状态,增加了CPU的开销。
(3) 异步I/O(Asynchronous I/O)

异步I/O模型允许应用程序发起一个I/O操作,并在操作完成后通过回调或信号通知应用程序结果。这种模型通常不需要进程主动轮询,进程可以继续执行其他任务,I/O操作的完成是异步通知的。

优点

  • 极大地提高了系统的并发性能。

缺点

  • 开发难度较高,编写复杂度增加,需要处理并发和回调逻辑。

2. I/O的不同层级

驱动程序开发涉及多个不同层级的I/O操作,从应用层到内核层,每一层次的I/O交互都有不同的机制和抽象。

(1) 用户态I/O(User Space I/O)

用户态I/O是应用程序通过系统调用与设备进行交互的方式。在C语言中,常见的I/O函数如 read()write()open()close() 等,都是在用户空间中执行的。

应用程序不能直接访问硬件,它只能通过系统调用请求内核来进行I/O操作。这也是操作系统保护硬件资源的一种方式,以避免应用程序直接操作设备造成问题。

(2) 内核态I/O(Kernel Space I/O)

内核态I/O是驱动程序直接与硬件设备打交道的部分。驱动程序在内核空间中运行,可以直接访问硬件寄存器、内存映射I/O等,完成具体的硬件控制。

  • 设备寄存器(Device Registers):设备寄存器是驱动程序与硬件交互的主要手段。驱动程序通过读写寄存器,控制设备的行为或者获取设备的状态。

  • 内存映射I/O(Memory Mapped I/O, MMIO):在某些硬件设备中,I/O操作可以通过访问内存地址来完成。这种内存映射的区域由设备硬件和系统内核配置,驱动程序通过对该区域的读写完成对设备的控制。

  • 端口映射I/O(Port-mapped I/O, PMIO):在某些系统中,I/O设备通过特定的I/O端口与CPU通信,而不是使用普通的内存地址。驱动程序通过访问这些I/O端口来控制设备。

(3) 中断处理(Interrupt Handling)

设备通常通过中断向CPU发出信号,通知它完成了某个操作(如数据准备好)。驱动程序需要处理这些中断,以确保系统能够及时响应设备状态的变化。

  • 中断上下文(Interrupt Context):中断处理是在中断上下文中完成的,驱动程序需要快速处理中断,以避免阻塞其他中断的发生。

  • 中断处理函数(Interrupt Service Routine, ISR):驱动程序中定义的ISR会在设备触发中断时被调用,完成对设备状态的检查和处理。

3. I/O调度与缓冲

I/O调度是操作系统通过优化I/O请求的处理顺序,来提高I/O性能的机制。在驱动开发中,了解I/O调度和缓冲区管理也是非常重要的。

  • I/O缓冲区:操作系统通常会使用缓冲区来存储I/O数据,避免直接的频繁硬件访问,减少硬件响应时间。

  • I/O调度算法:操作系统中有多种I/O调度算法(如FIFO、SSTF、CFQ等),驱动程序需要与这些调度算法协调工作,确保高效地处理I/O请求。

4. 字符设备与块设备

在驱动开发中,设备通常分为字符设备(Character Devices)和块设备(Block Devices)。

  • 字符设备:字符设备是以字符流的方式处理数据的设备。常见的字符设备包括串口、键盘等。字符设备的驱动程序通常通过 read()write() 函数与用户空间进行交互。

  • 块设备:块设备是以块为单位进行数据读写的设备,常见的块设备包括硬盘、USB存储设备等。块设备的驱动程序通常会实现 block_read()block_write(),以便系统可以一次读取或写入多个块的数据。

5. 驱动程序中的I/O控制(ioctl)

驱动程序需要为用户空间提供一种灵活的控制接口来配置设备,ioctl 是一个非常常用的系统调用,用于执行设备的控制命令。用户空间可以通过 ioctl 请求对设备进行特定的操作,比如改变设备的工作模式或设置一些参数。

总结

驱动开发不仅仅是对C语言函数的扩展,更重要的是需要理解设备的工作原理和操作系统的I/O机制。驱动程序是操作系统和硬件设备之间的桥梁,通过I/O模型(如阻塞、非阻塞和异步I/O)、I/O层次结构(用户态、内核态)、中断处理、I/O调度等技术实现高效的数据传输和硬件控制。开发者需要深入理解这些原理,才能编写出高效、可靠的驱动程序。

阻塞I/O

默认情况下,许多I/O操作是阻塞的,即调用这些函数时,进程会被阻塞,直到I/O操作完成。在POSIX标准以及大多数操作系统(如Linux)中,文件描述符上的常见阻塞I/O函数包括:

  • read(fd, buf, count)
  • write(fd, buf, count)
  • recv(sockfd, buf, len, flags)
  • send(sockfd, buf, len, flags)

这些函数会一直等待,直到数据被成功读取或写入。

非阻塞I/O

为了实现非阻塞I/O,可以通过多种方法设置文件描述符,使得I/O操作变为非阻塞的。下面是几种常见的方式:

1. 使用 fcntl 设置文件描述符为非阻塞

你可以使用 fcntl 函数来修改文件描述符的属性,将其设置为非阻塞模式:

#include <fcntl.h>
#include <unistd.h>

// 设置文件描述符为非阻塞模式
int set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) {
        perror("fcntl F_GETFL");
        return -1;
    }
    
    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) == -1) {
        perror("fcntl F_SETFL");
        return -1;
    }
    return 0;
}

// 恢复为阻塞模式
int set_blocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) {
        perror("fcntl F_GETFL");
        return -1;
    }
    
    flags &= ~O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) == -1) {
        perror("fcntl F_SETFL");
        return -1;
    }
    return 0;
}

设置文件描述符为非阻塞后,调用 readwrite 等I/O函数时,如果没有数据可读或无法立即写入数据,这些函数会返回 -1,并将 errno 设置为 EAGAINEWOULDBLOCK

2. 使用 select 或 poll 多路复用机制

selectpoll 是两种常见的I/O多路复用机制,允许应用程序监视多个文件描述符,等待其中的一个或多个变为可读、可写或发生异常。这些机制通常与非阻塞I/O结合使用,可以提高程序的并发性能。

使用 select 示例

 
#include <sys/select.h>
#include <unistd.h>
#include <stdio.h>

void use_select_example(int fd) {
    fd_set readfds;
    struct timeval timeout;

    FD_ZERO(&readfds);
    FD_SET(fd, &readfds);

    timeout.tv_sec = 5; // 设置超时时间为 5 秒
    timeout.tv_usec = 0;

    int ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
    if (ret == -1) {
        perror("select");
    } else if (ret == 0) {
        printf("Timeout occurred! No data available.\n");
    } else {
        if (FD_ISSET(fd, &readfds)) {
            char buf[128];
            ssize_t n = read(fd, buf, sizeof(buf));
            if (n > 0) {
                printf("Read %zd bytes: %s\n", n, buf);
            } else if (n == 0) {
                printf("EOF reached.\n");
            } else {
                perror("read");
            }
        }
    }
}

使用 poll 示例

#include <poll.h>
#include <unistd.h>
#include <stdio.h>

void use_poll_example(int fd) {
    struct pollfd fds[1];
    fds[0].fd = fd;
    fds[0].events = POLLIN;

    int ret = poll(fds, 1, 5000); // 超时时间为 5000 毫秒(5 秒)
    if (ret == -1) {
        perror("poll");
    } else if (ret == 0) {
        printf("Timeout occurred! No data available.\n");
    } else {
        if (fds[0].revents & POLLIN) {
            char buf[128];
            ssize_t n = read(fd, buf, sizeof(buf));
            if (n > 0) {
                printf("Read %zd bytes: %s\n", n, buf);
            } else if (n == 0) {
                printf("EOF reached.\n");
            } else {
                perror("read");
            }
        }
    }
}

通过这些多路复用机制,你可以同时监视多个文件描述符,并处理它们的可读/可写事件,而不会因为一个文件描述符的阻塞I/O操作而阻塞整个程序的执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值