Linux Driver Char Device 笔记

1.打開設備

  • 设备节点
         本质上和文件节点相同,为操作驱动的基本单位
  • 文件节点
        只用来做文件操作

打开设备文件的步骤:
        chrdev_open完成的主要工作是:首先根据设备号找到添加在内核中代表字符设备的cdev (cdev是放在cdev_map散列表中的,驱动加载时会构造相应的cdev并添加到这个散列表中,并且在构造这个cdev时还实现了一个操作方法集合,由cdev的ops成员指向它),找到对应的cdev对象后,用cdev关联的操作方法集合替代之前构造的file结构中的操作方法集合,然后调用cdev所关联的操作方法集合中的打开函数,完成设备真正的打开操作,这也标志着do_filp_open函数基本结束。


使用的时候是先用inode找到对应的cdev,然后cdev对象包含对应的操作集file_operations。

并且在以下情况时,驱动程序会根据入口函数 static int cdev_open ( struct inode* inodp, struct file* filp ) 将 和具体设备 相关的 驱动数据 与 file 结构建立关联:
  • 打开代表设备的inode
  • 初始化要作为file来操作的设备
这样 file 结构就包含对驱动操作的 操作集 及 与具体设备挂钩的 驱动私有数据 了,这样就能对 file 进行操作进而 控制相关设备 了。

打开一个 cdev 的步骤:
  1. 打开对应 inode
  2. 通过 indoe 找到对应 cdev
  3. 将与 cdev 相关的 设备数据驱动操作集要作为文件操作的代表驱动的 file 结构 相挂钩

2.IOCTL

ioctl 命令编码规则

31 30
----------(2 Byte)
00 00    -    命令不带参数
00 01    -    向驱动传递参数,写入
01 00    -    从驱动获取数据,读出
01 01    -    既写入又读出

29 28 27 ... 16
------------------------(14 Byte)
参数所占内存大小

15 14 13 ... 8
----------------------(8 Byte)
每个驱动全局唯一幻数

7 6 5 ... 0
----------------(8 Byte)
命令码

#define _IOC(dir, type, nr, size)

dir     -    写入读出方向
type  -    命令码
nr      -    驱动全局唯一幻数
size   -    参数所占内存大小

阻塞隊列聲明及操作方式
  • DECLARE_WAIT_QUEUE_HEAD( name )
            静态定义等待队列头
  • init_waitqueue_head( q )
            初始化等待队列头
  • wait_event( q, condition )
            入队条件:Y
            超时判断:N/A
            信号唤醒:N/A
            排他入队:N/A
            拿内部锁:N/A
            屏蔽中断:N/A
  • wait_event_timeout( q, condition, timeout )
            入队条件:Y
            超时判断:Y
            信号唤醒:N/A
            排他入队:N/A
            拿内部锁:N/A
            屏蔽中断:N/A
  • wake_up( q )
            唤醒队列:Y
            信号唤醒:N/A
            拿内部锁:N/A
  • wait_event_interruptible( qcondition )
            入队条件:Y
            超时判断:N/A
            信号唤醒:Y
            排他入队:N/A
            拿内部锁:N/A
            屏蔽中断:N/A
  • wait_event_interruptible_timeout( qconditiontimeout )
            入队条件:Y
            超时判断:Y
            信号唤醒:Y
            排他入队:N/A
            拿内部锁:N/A
            屏蔽中断:N/A
  • wait_event_interruptible_exclusive( qcondition )
            入队条件:Y
            超时判断:N/A
            信号唤醒:Y
            排他入队:Y
            拿内部锁:N/A
            屏蔽中断:N/A
  • wake_up_interruptible( q )
            唤醒队列:Y
            信号唤醒:Y
            拿内部锁:N/A
  • wait_event_interruptible_locked( qcondition )
            入队条件:Y
            超时判断:N/A
            信号唤醒:Y
            排他入队:N/A
            拿内部锁:Y
            屏蔽中断:N/A
  • wait_event_interruptible_locked_irq( qcondition )
            入队条件:Y
            超时判断:N/A
            信号唤醒:Y
            排他入队:N/A
            拿内部锁:Y
            屏蔽中断:Y
  • wait_event_interruptible_exclusive_locked( qcondition )
            入队条件:Y
            超时判断:N/A
            信号唤醒:Y
            排他入队:Y
            拿内部锁:Y
            屏蔽中断:N/A
  • wait_event_interruptible_exclusive_locked_irq( qcondition )
            入队条件:Y
            超时判断:N/A
            信号唤醒:Y
            排他入队:Y
            拿内部锁:Y
            屏蔽中断:Y
  • wake_up_locked( q )
            唤醒队列:Y
            信号唤醒:N/A
            拿内部锁:Y

I/O多路复用

int poll( struct pollfd * fds, nfds_t nfds, int timeout )

struct pollfd
{
        /* file descriptor */
        int fd;

        /* requested events */
        short events;

        /* returned events */
        short revents;
};


  1. poll ( )
  2. sys_poll ( )
  3. do_sys_poll ( )
    • 构造 poll_list 
    • 记录监听文件个数
  4. poll_initwait ( )
    • 构造 poll_wqueues
    • 初始化成员
    • pt 指针指向 poll_table
  5. do_poll ( )
    • 遍历 poll_list  里的 struct pollfd 结构
    • 初始化和每一个 struct pollfd 结构对应的 poll_wqueues 里的 poll_table_entry
    • 指定 poll_table_entry 节点被唤醒后调用的函数为 poll_wake ( )
    • 根据 struct pollfd 结构的文件描述符找到对应的 file 结构体
    • 调用 file 结构体的 poll ( ) 接口来调用驱动中的 poll ( ) 接口函数
    • poll ( ) 接口函数调用 poll_wait ( ) 辅助函数
  6. poll_wait ( )
    • 调用 poll_wqueues 里,pt 指针指向的 poll_table __poll_wait ( )
    • 将构造好的 poll_table_entry 加入驱动的等待队列
  7. poll ( ) 
    • 驱动 poll ( ) 接口函数检查资源是否可用
    • 返回状态位,如果有相应事件发生,跳到 8. poll ( ) ,否则跳到 9. poll_schedule_timeout ( )
  8. poll ( )
    • 关心的事件发生,将事件记录在 struct pollfd 结构的 revents
    • 将 struct pollfd 结构复制到用户层,poll ( ) 调用返回
  9. poll_schedule_timeout ( )
    • 没有相应事件发生,将 poll ( ) 系统休眠
  10. xxx_isr ( ) 中断发生
    • 对应驱动的资源可用,唤醒驱动中的等待队列
    • poll_table_entry 出队,调用 poll_wake ( )
    • poll ( ) 系统被唤醒
  11. 回到 5. do_poll ( )

异步I/O

static ssize_t vser_aio_read ( 
    struct kiocb * , 
    const struct iovec * , 
    unsigned long
    loff_t
)

异步通知
  1. 注册信号处理函数
    • sigaction ( SIGIO, &act, &oldact )
  2. 打开设备文件,设置设备文件属主
    • fcntl ( fd, F_SETOWN, getpid () )
  3. 设备文件资源可用时,驱动向应用层发送信号
    • fcntl ( fd, F_SETSIG, SIGIO )
  4. 设置文件 FASYNC 标志 ( 使能异步通知机制 )
    • flag = fcntl ( fd, F_GETFL )
    • fcntl ( fd, F_SETFL, flag | FASYNC )
驱动
  1. 构造 struct fasync_struct 链表头( 指针
    • struct vser_dev {
      • unsigned int baud;
      • ...
      • struct fasync_struct * fapp;
    • }
  2. 实现 fasync 接口函数,调用 fasync_helper 函数来构造 struct fasync_struct 节点,并加入链表
    • static int vser_fasync ( int fd, struct file * flip, int on )
    • {
      • return fasync_helper ( fd, flip, on, &vsdev.fapp );
    • }
  3. 资源可用时,调用 kill_fasync 发送信号,设置资源可以类型( 可读,可写 )
    • static ssize_t vser_read ( struct file * flip, char ___user * buf, size_t count, loff_t * pos )
    • {
      • ...
      • if ( ! kfifo_is_full ( & vsfifo ) ) {
        • wake_up_interruptible ( & vsdev.wwqh );
        • kill_fasync ( & vsdev.fapp,  SIGIO, POLL_OUT );
      • }
      • ...
    • }
  4. 文件最后一次关闭,在 release 接口中,显式调用驱动实现的 fasync 接口,将节点从链表中删除
    • static int vser_release ( struct inode * inode, struct file * flip )
    • {
      • vser_fasync ( -1, flip, 0 ); /* -1 is useless. */
      • return 0;
    • }
mmap

int remap_pfn_range ( 
    struct vm_area_struct * vma
    unsigned long addr
    unsigned long pfn
    unsigned long size
    pgprot_t prot 
)

将连续的物理地址空间映射到由 vm_area_struct 代表的虚拟空间

vma    -    代表虚拟空间的结构指针( 虚拟空间的 起始地址,结束地址,访问权限... )
addr    -    虚拟空间的起始地址( 不指定的话,内核将自动指定 )
pfn      -    物理地址对应的页框号( 物理地址除以页大小的值 )
                 phy_addr >> PAGE_SHIFT
size     -    需要被映射的空间( 物理地址 )的大小
prot     -    此映射的保护标志位

定位操作

loff_t ( * llseek ) ( struct file *, loff_t, int );

file       -    文件的 file 结构
loff_t   -    偏移量
int        -    位置



评论

此博客中的热门博文

ISO 14229-1-2020

AUTOSAR_SWS_CANDriver

AUTOSAR_SWS_PWMDriver

AUTOSAR_SWS_PortDriver

AUTOSAR_SWS_ECUStateManager

EB - MCAL - MCU

AUTOSAR_SWS_ICUDriver

EB - MCAL - PWM