本帖最后由 andeyqi 于 2023-11-23 22:29 编辑
简介:
linux 的驱动程序一般是在内核态,用户在用户态通过虚拟文件系统VFS创建的文件节点的,open/read/write 等方法访问驱动程序,字符设备驱动程序是最基本的驱动模型,用户态通过系统调用访问内核态的驱动程序,那开始我们的主题字符驱动程序的学习。
1.内核模块代码编写
用户态调用内核态的驱动程序流程如下:
我们编写内核态测试代码通过模块加载的方式进行加载,open 函数只是打印log 用于确认代码已经被调用到,代码如下:
- #include <linux/module.h>
- #include <linux/fs.h>
- #include <linux/device.h>
- #include <linux/cdev.h>
- #include <linux/types.h>
- #include <linux/kdev_t.h>
- #include <linux/uaccess.h>
- #define DEV_MEM_SIZE 1024
- char device_buffer[DEV_MEM_SIZE];
- static int pseudo_chr_dev_open(struct inode *inode, struct file *filp)
- {
- pr_info("open was successful\n");
- return 0;
- }
复制代码
wirte 函数将用户态态的数据copy 至缓冲区域device_buffer保存
- static ssize_t pseudo_chr_dev_write(struct file *filp, const char __user *buff, size_t count, loff_t *f_pos)
- {
- pr_info("write requested for %zu bytes\n", count);
- if((*f_pos + count) > DEV_MEM_SIZE)
- {
- count = DEV_MEM_SIZE - *f_pos;
- }
- if(!count)
- {
- return -ENOMEM;
- }
- if(copy_from_user(&device_buffer[*f_pos], buff, count))
- {
- return -EFAULT;
- }
- pr_info("Number of bytes successfully written = %zu\n", count);
- return count;
- }
复制代码
read 函数将缓冲区device_buffer的数据返回至用户空间
- static ssize_t pseudo_chr_dev_read(struct file *filp, char __user *buff, size_t count, loff_t *f_pos)
- {
- pr_info("read requested for %zu bytes \n", count);
- if((*f_pos + count) > DEV_MEM_SIZE)
- {
- count = DEV_MEM_SIZE - *f_pos;
- }
- if(copy_to_user(buff, &device_buffer[*f_pos], count))
- {
- return -EFAULT;
- }
- pr_info("Number of bytes successfully read = %zu\n", count);
- return count;
- }
复制代码
模块加载及卸载代码
- static int pseudo_chr_dev_release(struct inode *inode, struct file *filp)
- {
- pr_info("close was successful\n");
- return 0;
- }
- struct file_operations device_fops={
- .owner = THIS_MODULE,
- .open = pseudo_chr_dev_open,
- .release = pseudo_chr_dev_release,
- .read = pseudo_chr_dev_read,
- .write = pseudo_chr_dev_write,
- };
- dev_t device_number;
- struct cdev chr_dev;
- struct class *pseudo_class;
- struct device *pseudo_char_device;
- static int __init pseudo_chrdev_init(void)
- {
- int ret = 0;
- ret = alloc_chrdev_region(&device_number, 0, 1, "pseudo_chr_dev");
- if(ret < 0)
- {
- goto fail_dev_num;
- }
- pr_info("Device number <major>:<minor> = %d:%d", MAJOR(device_number),MINOR(device_number));
- cdev_init(&chr_dev, &device_fops);
- chr_dev.owner = THIS_MODULE;
- cdev_add(&chr_dev, device_number, 1);
- pseudo_class = class_create(THIS_MODULE, "pseudo_class");
- pseudo_char_device = device_create(pseudo_class, NULL, device_number, NULL,"pseudo_chrdev");
- pr_info("Module init was successful\n");
- return 0;
- fail_dev_num:
- return ret;
- }
- static void __exit pseudo_chrdev_exit(void)
- {
- device_destroy(pseudo_class, device_number);
- class_destroy(pseudo_class);
- cdev_del(&chr_dev);
- unregister_chrdev_region(device_number, 1);
- pr_info("module unloaded\n");
- }
- module_init(pseudo_chrdev_init);
- module_exit(pseudo_chrdev_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("Andyqi");
- MODULE_DESCRIPTION("This is pseudo driver module of character device");
复制代码
内核态测试代码已经ok,我们编写makefile,KERN_DIR 指定内核代码路径,将驱动程序编译为模块。
- KERN_DIR = /home/rlk/ym625x/myd-ym62x-bsp/myir-ti-linux
- all:
- make -C $(KERN_DIR) M=`pwd` modules
- clean:
- make -C $(KERN_DIR) M=`pwd` modules clean
- rm -rf modules.order
- obj-m += char_drv.o
复制代码
执行make 命令编译,编译通过生成char_drv.ko 文件
2.命令行读取验证
我们已经编译好了ko 文件,模块加载函数内部包含了在文件系统中创建class 并在class 创建设备我们可以通过文件节点来访问驱动程序,对应的节点名称规则如下:
将编译好的ko 文件上传到开发板通过insmode 命令加载至内核,通过log可知模块加载成功分配了239:0的设备号
通过cat /sys/class/pseudo_class/pseudo_chrdev/dev 也可以读取到device 对应的设备号为239:0
先尝试使⽤echo命令写入数据到字符设备/dev/pseudo_chrdev echo "Hello world!" > /dev/pseudo_chrdev,以下log 可以看出我们的write 函数及open函数已经被调用,从上⾯的命令可以看到整个文件操作的过程,从open到close。
我们尝试通过cat 读取文件 cat /dev/pseudo_chrdev ,我们可以看到read 函数已经被调用并且读到了我们写入的"Hello world!"
3.App程序验证
通过echo,cat 已经验证了文件的基本操作,我们编写如下测试代码在app 空间访问字符驱动程序
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <errno.h>
- #include <stdio.h>
- #include <stdlib.h>
- #define MAX 1048
- char buffer[MAX];
- int main(int argc, char **argv)
- {
- int fd = 0;
- int ret = 0;
- int count = 0;
- /* 两个参数 */
- if(argc != 2)
- {
- printf("Wrong usage, Please try the way: <file> <number toread>\n");
- goto exit;
- }
- /* 字符转换整数, main函数的参数 */
- count = atoi(argv[1]);
- /* 打开字符设备文件 */
- fd = open("/dev/pseudo_chrdev", O_RDWR);
- if(fd < 0)
- {
- perror("open fail");
- goto exit;
- }
- printf("Open operation was successful\n");
- /* 从字符设备缓冲读取数据 */
- ret = read(fd, &buffer[MAX], count);
- if(!ret)
- {
- printf("read failure or end of file\n");
- goto exit;
- }
- else
- {
- printf("read %d bytes data from pseudo character device\n", ret);
- }
- /* 从用户空间写数据到字符设备 */
- ret = write(fd, &buffer[MAX], count);
- if(!ret)
- {
- printf("write failure or character device full\n");
- goto exit;
- }
- else
- {
- printf("write %d bytes date to pseudo character device\n", ret);
- }
- return 0;
- exit:
- close(fd);
- return 0;
- }
复制代码 将测试程序编译通过后,传送至开发板运行结果如下,可以看出内核态的open read write 也已经被调用到了
|