嵌入式学习小组
直播中

lique

13年用户 884经验值
擅长:模拟与电源
私信 关注

如何实现简单字符设备驱动?

如何实现简单字符设备驱动?

回帖(1)

欧建杭

2021-12-23 14:49:11
  一、实验目的




  • 掌握简单字符设备驱动程序编写方法。
  • 编写应用程序对驱动程序进行测试,学习应用程序与驱动程序之间的调用过程。

二、实验环境

ubuntu 12.04 内核3.2.14
三、实验内容及实验原理

写一个简单的字符设备驱动程序,要求:



  • 定义一个全局结构指针,初始值为NULL,该数据结构中包含一个大小为1024的buffer和一个count整形变量
  • 在open中对该全局结构进行NULL判断,为NULL则为其分配内存,并将buffer初始化为0,将count自加
  • 在release中如果count为0,则释放,否则进行count自减
  • 在read里面对该buffer进行读取
  • 在write里面对该buffer进行赋值(任意赋值即可)
    写测试程序进行测试

四、实验结果及其分析

1.编译模块(设备驱动程序)

(1)创建模块文件xxx.c

gedit rwbuf.c
区分版本:这里不同,低版本内核2.6以下的是ioctl,高版本内核2.6及其以上的是unlocked_ioctl。


// 模块
#include
// 内核
#include
// struct file_operations
#include
// copy_to_user() & copy_from_user
#include
// 不行,用于memset和strcpy
#include
// kmalloc和kfree
#include


// rwbuf.c,  driver for virtual char-device
#define RW_CLEAR 0x123
// 设备名
#define DEVICE_NAME "rwbuf"


// buffer数组的最大长度
#define RWBUF_MAX_SIZE 1024
// 设备中存储信息的全局结构体指针
typedef struct Data
{
    // 用于打开和释放的验证,0表示未使用,1表示已使用
    int count;
    // 表示字符串
    char buffer[RWBUF_MAX_SIZE];
} Data, *DataPtr;


DataPtr myDataPtr = NULL;


// 设备的打开:只返回0表示成功无法表示报错
int rwbuf_open(struct inode *inode, struct file *file)
{
    // 如果未被使用
    if (myDataPtr == NULL)
    {
        // 分配堆
        myDataPtr = (DataPtr)kmalloc(sizeof(Data), GFP_KERNEL);
        // 字符数组清零
        memset(myDataPtr->buffer, 0, sizeof(myDataPtr->buffer));
        // 初始化为1,表示有人在使用了
        myDataPtr->count = 1;
        printk("[rwbuf_open-kmalloc-success]myDataPtr->count = %d.n", myDataPtr->count);
        return 0;
    }
    // 已被使用
    else
    {
        // 增加一个用户
        myDataPtr->count += 1;
        printk("[rwbuf_open-countup]myDataPtr->count = %d.n", myDataPtr->count);
        return 0;
    }
}


// 设备的关闭:返回-1表示错误;返回0表示成功
int rwbuf_release(struct inode *inode, struct file *file)
{
    // 当未打开时,报错
    if (myDataPtr == NULL)
    {
        printk("[rwbuf_release-error]n");
        return -1;
    }
    // 当有用户使用时,则自减
    else
    {
        myDataPtr->count -= 1;
        printk("[rwbuf_release-countdown]myDataPtr->count = %d.n", myDataPtr->count);


        // 当没用用户使用时,释放
        if (myDataPtr->count == 0)
        {
            // 释放堆区
            kfree(myDataPtr);
            // 还要让指针回归NULL,不要野指针
            myDataPtr = NULL;
            printk("[rwbuf_release-free-success]n");
            return 0;
        }
        return 0;
    }
}


// 设备的读操作:返回-1表示错误;返回(0, RWBUF_MAX_SIZE]表示成功
ssize_t rwbuf_read(struct file *file, char *buf, size_t count, loff_t *f_pos)
{
    // 判断读取的长度是否有效
    if (strlen(myDataPtr->buffer) > 0 && strlen(myDataPtr->buffer) <= RWBUF_MAX_SIZE)
    {
        // 从内核空间rwbuf复制到用户空间buf
        copy_to_user(buf, myDataPtr->buffer, count);
        printk("[rwbuf_read-success]the size of myDataPtr->buffer after read = %dn", strlen(myDataPtr->buffer));
        return count;
    }
    else
    {
        printk("[rwbuf_read-error] strlen(myDataPtr->buffer) = %dn", strlen(myDataPtr->buffer));
        return -1;
    }
}


// 设备的写操作接:返回-1表示错误;返回(0, RWBUF_MAX_SIZE]表示成功
ssize_t rwbuf_write(struct file *file, const char *buf, size_t count, loff_t *f_pos)
{
    // 判断写入的长度是否有效
    if (count > 0 && count <= RWBUF_MAX_SIZE)
    {
        // 从用户空间buf复制到内核空间rwbuf
        copy_from_user(myDataPtr->buffer, buf, count);
        printk("[rwbuf_write-success] the size of myDataPtr->buffer after write = %dn", strlen(myDataPtr->buffer));
        return count;
    }
    else
    {
        printk("[rwbuf_write-error] length of string = %dn", count);
        return -1;
    }
}


// 设备的ioctl:返回-1表示错误;返回0表示成功
long rwbuf_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    printk("[RW_CLEAR:%x],[cmd:%x]n", RW_CLEAR, cmd);
    // 命令
    if (cmd == RW_CLEAR)
    {
        // 表示清零
        memset(myDataPtr->buffer, 0, sizeof(myDataPtr->buffer));
        printk("[rwbuf_ioctl-success] the size of myDataPtr->buffer after ioctl = %dn", strlen(myDataPtr->buffer));
        return 0;
    }
    // 无此命令时
    else
    {
        printk("[rwbuf_ioctl-error] the size of myDataPtr->buffer after ioctl = %dn", strlen(myDataPtr->buffer));
        return -1;
    }
}


// rwbuf_fops要在rwf_buf_init()前面声明,因为register_chrdev()函数要使用到
static struct file_operations rwbuf_fops =
    {
        open : rwbuf_open,
        release : rwbuf_release,
        read : rwbuf_read,
        write : rwbuf_write,
        unlocked_ioctl : rwbuf_ioctl
    };


// module_init()内的初始化函数:返回-1表示错误;返回0表示成功
static int __init rwbuf_init()
{
    // 表示注册成功与否:-1表示失败,0表示成功(同register_chrdev返回值)。初始化为-1
    int ret = -1;
    /*
    参数1:设备的种类,即主设备号
    参数2:设备的名称
    参数3:和VFS对接的接口,即上面的结构体变量
    */
    ret = register_chrdev(60, DEVICE_NAME, &rwbuf_fops);
    // 注册失败
    if (ret == -1)
    {
        printk("[rwbuf_init-register-failed]n");
    }
    // 注册成功
    else
    {
        printk("[rwbuf_init-register-success]n");
    }
    // 返回ret(同register_chrdev返回值)
    return ret;
}


// module_exit()内的退出函数。
static void __exit rwbuf_exit()
{
    unregister_chrdev(60, DEVICE_NAME);
    printk("[rwbuf_exit-unregister-success]n");
}


// 内核模块入口,相当于main()函数,完成模块初始化
module_init(rwbuf_init);
// 卸载时调用的函数入口,完成模块卸载
module_exit(rwbuf_exit);
// GPL协议证书
MODULE_LICENSE("GPL");
内核模块入口,相当于main()函数,完成模块初始化module_init(rwbuf_init);// 卸载时调用的函数入口,完成模块卸载module_exit(rwbuf_exit);// GPL协议证书MODULE_LICENSE("GPL"); 意思:
结构体static struct file_operations rwbuf_fops是定义函数指针
比如将open指向rwbuf_open,我们调用的时候就打open就是调用rwbuf_open。其中ioctl特殊,调用的时候用ioctl才对,用unlocked_ioctl反而不对,而且定义成旧版本的ioctl,对高版本内核更不对(编译不报错,但读进入的数cmd就成一个莫名其妙的8位十六进制数)。
结构体rwbuf_fops在rwf_buf_init_module()中被用到,生效对结构体变量的操作。
if(register_chrdev(60, DEVICE_NAME, &rwbuf_fops))
(2)Makefile

gedit Makefile
内容:

obj-m := rwbuf.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
        rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order  Module.symvers
(3)编译

sudo make
2.创建设备文件(设备进入点)

(1)创建

sudo mknod /dev/rwbuf c 60 0
意思:
在/dev目录下创建一个名为rwbuf的设备文件
设备文件的类型是c(字符型设备)
主设备号是60
次设备号是0
(2)赋权

sudo chmod 777 /dev/rwbuf
更改设备的权限。没有读写权限的话,就会读出一堆无意义的乱码。
3.插入内核模块(加载设备驱动程序)

先清理一下缓存,不然一会就可能输出一大堆多余东西,影响到我们想要看到的输出东西
sudo dmesg -c
插入内核模块(加载设备驱动程序)
sudo insmod rwbuf.ko
查看是否成功
dmesg

4.测试用户应用程序(调用驱动程序)

(1)读写


gedit writeAndRead.c
#include
// 为了open()中的O_RDWR
#include


// 定义设备进入点(设备名)
#define DEVICE_NAME "/dev/rwbuf"


int main()
{
    // 声明文件描述符
    int fd;
    // 记录关闭设备的返回值
    int ret;
    // 写入到设备的内容
    char buff_write[10] = "volume";
    // 读取到设备的结果
    char buff_read[10] = "";


    // 调用打开设备函数。注意O_RDWR是字母O
    fd = open(DEVICE_NAME, O_RDWR);
    // 打开失败
    if (fd == -1)
    {
        printf("[open-error]n");
        return 0;
    }
    else
    {
        printf("[open-success]n");
    }


    // 调用驱动程序的写操作接口函数
    if (write(fd, buff_write, 5) == -1)
    {
        printf("[write-error]n");
        return 0;
    }
    // 写入成功
    else
    {
        printf("[write-success] %sn", buff_write);
    }


    // 调用驱动程序的读操作接口函数
    if (read(fd, buff_read, 5) == -1)
    {
        printf("[read-error]n");
        return 0;
    }
    // 读取成功
    else
    {
        buff_read[5] = '';
        printf("[read-success] %sn", buff_read);
    }


    // 设备关闭
    close(fd);
    printf("[close-success]n");


    return 0;
}



(2)清除程序

gedit ioctl.c

#include
// 为了open()中的O_RDWR
#include


// 定义设备进入点(设备名)
#define DEVICE_NAME "/dev/rwbuf"


int main()
{
    // 声明文件描述符
    int fd;
    // 记录关闭设备的返回值
    int ret;
    // 写入到设备的内容
    char buff_write[10] = "volume";
    // 读取到设备的结果
    char buff_read[10] = "";
    // 控制命令
    unsigned int cmd = 0x123;


    // 调用打开设备函数。注意O_RDWR是字母O
    fd = open(DEVICE_NAME, O_RDWR);
    // 打开失败
    if (fd == -1)
    {
        printf("[open-error]n");
        return 0;
    }
    else
    {
        printf("[open-success]n");
    }


    // 调用驱动程序的写操作接口函数
    if (write(fd, buff_write, 5) == -1)
    {
        printf("[write-error]n");
        return 0;
    }
    // 写入成功
    else
    {
        printf("[write-success] %sn", buff_write);
    }


    // 调用驱动程序的写操作接口函数
    if (ioctl(fd, cmd) == -1)
    {
        printf("[ioctl-error]n");
        return 0;
    }
    else
    {
        printf("[ioctl-success]n");
    }


    // 调用驱动程序的读操作接口函数
    if (read(fd, buff_read, 5) == -1)
    {
        printf("[read-error]n");
        return 0;
    }
    // 读取成功
    else
    {
        buff_read[5] = '';
        printf("[read-success] %sn", buff_read);
    }


    // 设备关闭
    close(fd);
    printf("[close-success]n");


    return 0;
}



5.野指针问题

这个属于个人书写代码的规范不好,

如果kfree(myDataPrt)后没有将myDataPrt=NULL;就会再次打开后判读此条件失败,从而count是乱值。
五、心得体会与建议

感觉还是遇到了不少的难处,并没有看着那么简单
1.隐式声明函数‘copy_from_user’

2.总结copy_from_user()和copy_to_user()

编译内核函数copy_from_user()和copy_to_user()
3.xffffffe7xffffffa4xffffffba问题

Linux驱动开发:xffffffe7xffffffa4xffffffba问题
4.调整代码结构

(1)问题

驱动程序

// buffer数组的最大长度
#define RWBUF_MAX_SIZE 1024
// 设备中存储信息的全局结构体指针
typedef struct Data
{
    // 用于打开和释放的验证,0表示未使用,1表示已使用
    int count;
    // 表示字符串
    char buffer[RWBUF_MAX_SIZE];
} Data, *DataPtr;


DataPtr myDataPtr = NULL;


// 设备的关闭:返回-1表示错误;返回0表示成功
int rwbuf_release(struct inode *inode, struct file *file)
{
    // 当未打开时,报错
    if (myDataPtr == NULL)
    {
        printk("[rwbuf_release-error]n");
        return -1;
    }
    // 当没用用户使用时,释放
    if (myDataPtr->count == 0)
    {
        kfree(myDataPtr);
        printk("[rwbuf_release-free-success]myDataPtr->count = %d.n", myDataPtr->count);
        return 0;
    }
    // 当有用户使用时,则自减
    else
    {
        myDataPtr->count -= 1;
        printk("[rwbuf_release-countdown]myDataPtr->count = %d.n", myDataPtr->count);
        return 0;
    }
}
用户测试程序
// 设备关闭close(fd);printf("[close-success]n");close(fd);printf("[close-release-success]n");
可以看到没出现对应printk("[rwbuf_release-free-success]myDataPtr->count = %d.n", myDataPtr->count);的结果,即没有释放成功。
(2)解决

既然close(fd)只能生效一次,那么移动到里面就行了。

// 设备的关闭:返回-1表示错误;返回0表示成功
int rwbuf_release(struct inode *inode, struct file *file)
{
    // 当未打开时,报错
    if (myDataPtr == NULL)
    {
        printk("[rwbuf_release-error]n");
        return -1;
    }
    // 当有用户使用时,则自减
    else
    {
        myDataPtr->count -= 1;
        printk("[rwbuf_release-countdown]myDataPtr->count = %d.n", myDataPtr->count);


        // 当没用用户使用时,释放
        if (myDataPtr->count == 0)
        {
            kfree(myDataPtr);
            printk("[rwbuf_release-free-success]n");
            return 0;
        }
        return 0;
    }
}
举报

更多回帖

发帖
×
20
完善资料,
赚取积分