字符设备驱动接口

本文详细介绍了Linux设备驱动中的关键函数,包括读写函数的实现原理与参数修正,文件指针的调整机制,以及标准ioctl接口的使用。通过具体的代码示例,深入解析了这些函数如何影响文件读写位置,以及如何通过命令控制硬件设备。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.读写函数
用户空间函数原型:

ssize_t write(int fd, const void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

驱动中对应的函数原型:

ssize_t (*write) (struct file *flp, const char __user *buf, size_t count , loff_t *poff);
ssize_t (*write) (struct file *flp, const char __user *buf, size_t count , loff_t *poff);

poff: 是指向存放文件当前读写位置变量的地址的指针;每次读写数据后poff会变化。但其用处需要自己在驱动函数write/read中使用;否则无作用。
比如:
read(fd,buf,0) 读写位置不变,驱动直接返回 0.
read(fd,buf,1) 读写位置 变成 1
read(fd,buf,1) 读写位置 变成 2
read(fd,buf,1) 读写位置 变成 3
read(fd,buf,1) 读写位置 变成 4,并且驱动只读取一个数据。
read(fd,buf,1) 读写位置 还是 4,没有读取任何一个数据。
代码例子:

static ssize_t xxx_read(struct file *pfile, char __user *buf, size_t count, loff_t *poff)
{
    int i;
    char kbuf[LED_NUM] = {0};
    loff_t cur_pos = *poff;   //取出当前读写位置值

    printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);

    //进行参数修正:1)count如是0,函数返回0.
    if(count == 0) {
        return 0;
    }

    //进行参数修正:2)   如果文件读写位置已经到末尾,不管count是多少,都不能读,返回0;
    if(cur_pos >= LED_NUM) {
        return 0;
    }

    //进行参数修正:3)   判断 count + *poff 是否大于文件大小
    if(cur_pos + count > LED_NUM) {
        count = LED_NUM - cur_pos;
    }

    //准备数据:读取4个led灯状态
    for(i = 0; i < LED_NUM; i++) {
        //灭:返回'0',使用正逻辑方式表,也要和write逻辑相同
        if(GPM4DAT & 1 << i) {
            kbuf[i] = '0' ;
        }
        //亮:返回'1',使用正逻辑方式表,也要和write逻辑相同
        else {
            kbuf[i] = '1' ;
        }
    }

    //复制数据给用户空间
    if (copy_to_user(buf, &kbuf[cur_pos], count) ) {
        printk(KERN_EMERG" error: copy_to_user\r\n");
        return -EFAULT;
    }

    //更新文件读写位置,不能少,少了得不到正确的结果
    *poff += count;

    return count;
}

static ssize_t xxx_write(struct file *pfile, const char __user *buf, size_t count, loff_t *poff)
{
    int i = 0;
    char kbuf[LED_NUM] = {0};
    loff_t cur_pos = *poff;   //取出当前读写位置值

    printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);


    //进行参数修正:1)count如是0,函数返回0.
    if(count == 0) {
        return 0;
    }

    //进行参数修正:2)   如果文件读写位置已经到末尾,不管count是多少,都不能写,返回0;
    if(cur_pos >= LED_NUM) {
        return 0;
    }

    //进行参数修正:3)   判断 count + *poff 是否大于文件大小
    if(cur_pos + count > LED_NUM) {
        count = LED_NUM - cur_pos;
    }


    //使用专用的函数进行复制,得到到对应 的元素位置
    if (copy_from_user(&kbuf[cur_pos], buf, count) ) {
        printk(KERN_EMERG" error: copy_from_user\r\n");
        return -EFAULT;
    }


    //根据用户空间传递下来的内容进行处理
    for(i = 0; i < count; i++) {
        if(kbuf[i + cur_pos] == '0') {
            //熄灭第i个灯:通过配置数据寄存器
            GPM4DAT  |=  1 << (i + cur_pos) ; //把第i个IO口配置为高电平,
            //当使用%d时候要注意cur_pos类型,否则输出会不正确
            printk(KERN_EMERG"第%d个灯灭\r\n", i + 1 + (int)cur_pos);
        }

        else if(kbuf[i + cur_pos ] == '1') {
            //点亮第i个灯:通过配置数据寄存器
            GPM4DAT  &= ~( 1 << (i + cur_pos)); //把第i个IO口配置为低电平,

            //当使用%d时候要注意cur_pos类型,否则输出会不正确
            printk(KERN_EMERG"第%d个灯亮\r\n", i + 1 + (int)cur_pos);
        }
    }

    //更新文件读写位置,不能少,少了得不到正确的结果
    *poff += count;  
    return count;
}

2.文件指针
用户空间函数原型:

off_t lseek(int fd, off_t offset, int whence);

驱动中对应的函数原型:

loff_t xxx_llseek(struct file *file, loff_t offset, int whence)

作用: 按照 whence 指定的方式把 fd 文件的读写指针调整 offset 偏移。
offset: 需要调整的偏移,值是可正数,可负数
whence: 调整方式,只有三种值: SEEK_SEK,SEEK_CUR,SEEK_END

whence
SEEK_SET:以文件开头( 0)为参照点移动到 offset 处。
SEEK_CUR:以文件指针 [当前位置 + offset ]做为新的 offset 指针位置。
SEEK_END:以文件[末尾位置 + offset] 做为新的 offset 指针位置。

返回值:
成功: 移动后文件指针位置fpos(以 0 为参照点);
失败: -1,并且设置全局变量 errno 值为对应的错误码(来源码驱动)
说明:f_pos需要自己在驱动函数中使用,并且返回;否则无作用。
例子代码:

static loff_t xxx_llseek(struct file *pfile, loff_t off, int whence)
{
    loff_t  temp;

    printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);

    switch(whence) {
        case SEEK_SET:
            temp = off;
            break;

        case SEEK_CUR:
            temp = pfile->f_pos + off;   //当前位置加上调整值
            break;


        case SEEK_END:
            temp = LED_NUM + off;       //文件大小加上调整值
            break;

        default:
            return -EINVAL;    //告诉应用程序具体错误原因是参数无效

    }

    //检测最后调整结果是否合法
    if((temp < 0)  || (temp > LED_NUM) ) {
        return  -EINVAL;
    }

    //更新调整后结果到文件读写位置变量中
    pfile->f_pos = temp;

    //返回调整后的结果
    return temp;

}

3.标准 ioctl 接口
用户空间函数原型:

int ioctl(int fd, int request, ...);

驱动中对应的函数原型:

  long (*unlocked_ioctl) (struct file *pfile, unsigned int cmd, unsigned long args);

作用: 通过命令形式来控制硬件设备,相当 linux 系统给我们提供扩展系统功能的一个接口,可以由用户自定义命令来让硬件执行不同的代码。
参数:
pfile: 文件结构指针,间接对应于系统调用的 fd 参数(文件描述符)
cmd:命令,对应于系统调用的 request
args: args 系统调用的…参数(可选参数)
返回值:
cmd 命令执行成功:>0 ,值是什么含义由驱动程序中决定。
cmd 命令执行失败:<0,返回失败错误码, 比如 args 参数非法,则返回-EFAULT,所返回的错误码会被系统
特别注意:这里解释了 cmd 值的组成格式:

bitsmeaning
31-30用户程序和驱动数据交换模式
29-16当用户程序和驱动程序有数据传递时候才有效,表示要传递的数据大小。
15-8魔数,幻数, 给每一个驱动分配一个惟一的 ASCII 值,用于区分一个驱动的cmd 和别的不一样。
7-0同一个设备驱动中所有 cmd 命令的编号,范围 0~255,一般情况同一个驱动值都是连续的。

用户程序和驱动数据交换模式:
00 – 用户程序和驱动没有数据传递 uses _IO macro
01 – 用户程序向驱动写数据: _IOW
10 – 用户程序从驱动读取数据: _IOR
11 - 先用户程序写数据到驱动,再从驱动中读取数据(先写数据然后读取数据回来) : _IOWR

内核在(Ioctl.h linux-3.5\include\Asm-generic)提供了相应的宏给我们使用,我们可以在应用层使用:
_IO(type,nr) :定义没有数据传递的命令
_IOR(type,nr,size) :定义从驱动中读取数据的命令
_IOW(type,nr,size) : 定义向驱动写入数据的命令
_IOWR(type,nr,size) : 定义数据交换类型的命令,先写入数据,再读取数据这类命令。
type: 表示命令组成的魔数,也就是 8~15 位
nr: 表示命令组成的编号,也就是 0~7 位
size: 表示命令组成的参数传递大小, 但是这里不传递数字,而是数据类型, 如要传递 4 字节,可以写 int,如要传递一个结构体数据给驱动,则把结构类型做 size 参数。

代码例子:

long chrdev_unlocked_ioctl (struct file *pfile,unsigned int cmd, unsigned long arg)
{
    switch ( cmd ) {
        case 0 :
            printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
            break;
        case 1 :
            printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
            break;
        case 2 :
            printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
            break;
        case 3 :
            printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
            break;
        case 4 :
            printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
            break;
        default:
            printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
            return -EINVAL;
    }

        return arg;

    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小坚学Linux

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值