3.Linux 早期经典标准字符设备驱动模型

it2022-05-05  158

目录

1.设备号

2. 特征

3.注册函数

4. 注销函数

5.早期标准经典字符设备驱动模型编程模板

1.主设备号和name相同

2.主设备号同,name不同

3.主设备号不同,name不同

4.结论


早期经典标准字符设备描述方式

没有使用一个结构体进行封装,没有做一个整体描述。

1.设备号

主设备号: 0~255,(10 是杂项设备)

次设备号: 0~255

2. 特征

1. 安装后, 不会自动创建/dev/目录下自动创建设备文件节点, 需要手动使用 mknod 命令创建。

2. 调用一次 register_chrdev 注册函数后, 一个主设备号下面的 256 个次设备号就都被占用完了。也就是说一个主设备号只能使用 register_chrdev 注册函数注册一次。

3.注册函数

 

对比杂项设备必须的要素:

register_chrdev 注册函数并没有指明使用哪一个次设备,而杂项设备需要指明次设备号?

int register_chrdev(

unsigned int major, /* 主设备号 */

const char *name, /* 设备名,不需要和/dev 下对应节点名相同 */

const struct file_operations *fops) /* 文件操作方法结构指针 */

头文件: #include <linux/fs.h>

功能: 注册一个早期经典标准字符设备

参数: major:主设备号, 0~255(10 除外)。

这类驱动注册的主设备号不能和其他的驱动相同,否则会失败。所以,当不确定哪一个号可用时候,只能是由内核自动分配 。 当 major 传递 0 时候表示由内核自动分配一个可用的主设备号。

name:设备名,不需要和/dev 下对应节点名相同。 注册后, 这个名字是会出现在/proc/device 文件中的。

[root@ChenZhiFa home]# cat /proc/devices

fops:文件操作方法结构指针

返回值:

当 major == 0: 成功:返回所分配的主设备号。 失败:返回负数

当 major > 0: 成功:返回 0 失败:返回负数

 

register_chrdev 最简单的实现方法是:(register_chrdev 的作用)

4. 注销函数

void unregister_chrdev(unsigned int major, const char *name)

头文件: #include <linux/fs.h>

功能: 注销一个已经存在标准字符设备

参数:

major:主设备号

name:设备名,使用 register_chrdev 注册的设备名

返回值: 无

5.早期标准经典字符设备驱动模型编程模板

驱动源码:

#include <linux/module.h> #include <linux/init.h> //包含必须的头文件 #include <linux/fs.h> #include <linux/miscdevice.h> //本代码不是以杂项模型编写,可以不包含这个文件, //以下是文件操作方法的具体实现代码 static int xxx_open(struct inode *pinode, struct file *pfile ) { printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__); return 0; } static ssize_t xxx_read(struct file *pfile,char __user *buf, size_t count, loff_t *poff) { printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__); return count; } static ssize_t xxx_write(struct file *pfile,const char __user *buf, size_t count, loff_t *poff) { printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__); return count; } static loff_t xxx_llseek(struct file *pfile, loff_t off, int whence) { printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__); return off; } static int xxx_release (struct inode *pinode, struct file *pfile) { printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__); return 0; } static long xxx_unlocked_ioctl (struct file *pfile,unsigned int cmd, unsigned long args) { printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__); return 0; } //文件操作方法集合指针 static const struct file_operations mymisc_fops = { .open = xxx_open, .read = xxx_read, .write = xxx_write, .llseek = xxx_llseek, .release = xxx_release, .unlocked_ioctl = xxx_unlocked_ioctl, }; /* 本代码使用早期模型编程,不需要这个张雪松了 //定义核心结构 static struct miscdevice misc = { .minor = 255, .name = "mymisc", ///dev 目录下的设备名 .fops = &mymisc_fops, }; */ //定义设备名 #define MY_DEVICE_NAME "earlydev" static unsigned int major = 0; //设置设备主设备号 static int __init my_early_device_init(void) { int ret; /* //注册核心结构 ret = misc_register(&misc); if(ret < 0) { printk(KERN_EMERG"misc_register error\n"); return ret; } */ //使用早期经典注册,让内核自动分配 一个可用的主设备号 ret = register_chrdev(0, MY_DEVICE_NAME , &mymisc_fops); if(ret < 0) { printk(KERN_EMERG"register_chrdev error\n"); return ret; } major = ret; //保存主设备号,卸载函数需要使用到 printk(KERN_EMERG"register_chrdev ok\n"); printk(KERN_EMERG"major:%d \n", major); //输出主设备号 return 0; } static void __exit my_early_device_exit(void) { /* int ret; //注销核心结构 ret = misc_deregister(&misc); if(ret < 0) { printk(KERN_EMERG"misc_deregister error\n"); return ; } */ unregister_chrdev(major, MY_DEVICE_NAME); printk(KERN_EMERG"misc_deregister ok\n"); } module_init(my_early_device_init); module_exit(my_early_device_exit); MODULE_LICENSE("GPL");

应用程序源码:

#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> //lseek #include <sys/ioctl.h> //ioctl //定义设备名 #define MY_DEVICE_NAME "/dev/earlydev" int fd; //存放文件描述符号 char save_buf[1024]={0}; //存放数据使用 int main(void) { int ret; fd = open(MY_DEVICE_NAME,O_RDWR); //以读写方式进行打开 if(fd < 0){ printf("open error\r\n"); return -1; } printf("fd=%d\r\n",fd); //成功时候输出文件描述符 printf(MY_DEVICE_NAME" open success\r\n"); //成功时候输出文件描述符 //读操作 ret = read(fd, save_buf,1024); if(ret < 0){ printf("open error\r\n"); return -1; } //写操作 write(fd, "console out test\r\n", sizeof("console out test\r\n")); //移动文件指针操作 lseek(fd,0,SEEK_SET); //i/o 控制操作 ioctl(fd,0,0); //关闭文件 close(fd); return 0; }

1.主设备号和name相同

测试过程及说明:

[root@ChenZhiFa home]# insmod early_device_module.ko

[ 1761.980000] register_chrdev ok

[ 1761.980000] major:250 注册时候生成的主设备号是 250 ,这个号很重要

创建一个和驱动源码中定义的 name 相同的,并且主设备号也和生成的相同的设备文件:

[root@ChenZhiFa home]# ls /dev/earlydev -l

ls: /dev/earlydev: No such file or directory

[root@ChenZhiFa home]# mknod /dev/earlydev c 250 0

[root@ChenZhiFa home]# ls /dev/earlydev -l

crw-r--r-- 1 root root 250, 0 Sep 12 2016 /dev/earlydev

运行测试程序: 成功的调用的早期设备驱动模型中的程序

[root@ChenZhiFa home]# ./app [ 1909.555000] file:/mnt/hgfs/share/20160909device_drvier/03_early_device/early_device_module.c [ 1909.555000] line:13, xxx_open is call [ 1909.555000] file:/mnt/hgfs/share/20160909device_drvier/03_early_device/early_device_module.c [ 1909.555000] line:21, xxx_read is call [ 1909.555000] file:/mnt/hgfs/share/20160909device_drvier/03_early_device/early_device_module.c [ 1909.555000] line:28, xxx_write is call [ 1909.565000] file:/mnt/hgfs/share/20160909device_drvier/03_early_device/early_device_module.c [ 1909.565000] line:35, xxx_llseek is call [ 1909.580000] file:/mnt/hgfs/share/20160909device_drvier/03_early_device/early_device_module.c [ 1909.580000] line:52, xxx_unlocked_ioctl is call [ 1909.595000] file:/mnt/hgfs/share/20160909device_drvier/03_early_device/early_device_module.c [ 1909.595000] line:43, xxx_release is call fd=3 /dev/earlydev open success [root@ChenZhiFa home]#

2.主设备号同,name不同

修改 app.c 中的设备名为 abc ,编译,测试

[root@ChenZhiFa home]# ./app

open error  打不开,正常的,因为现在/dev/abc 目录下没有 abc 文件

创建一个 abc 的设备文件(名字和驱动源码中注册的名字不相同),并且主设备号和注册时候生成主设备号相同(次设备号随便):

[root@ChenZhiFa home]# mknod /dev/abc c 250 100

测试是否能调用到驱动程序: 结果是可以正确调用到驱动程序。

[root@ChenZhiFa home]# ./app [ 2028.510000] file:/mnt/hgfs/share/20160909device_drvier/03_early_device/early_device_module.c [ 2028.510000] line:13, xxx_open is call [ 2028.510000] file:/mnt/hgfs/share/20160909device_drvier/03_early_device/early_device_module.c [ 2028.510000] line:21, xxx_read is call [ 2028.510000] file:/mnt/hgfs/share/20160909device_drvier/03_early_device/early_device_module.c [ 2028.510000] line:28, xxx_write is call [ 2028.525000] file:/mnt/hgfs/share/20160909device_drvier/03_early_device/early_device_module.c [ 2028.525000] line:35, xxx_llseek is call [ 2028.535000] file:/mnt/hgfs/share/20160909device_drvier/03_early_device/early_device_module.c [ 2028.535000] line:52, xxx_unlocked_ioctl is call [ 2028.550000] file:/mnt/hgfs/share/20160909device_drvier/03_early_device/early_device_module.c [ 2028.550000] line:43, xxx_release is call fd=3 /dev/abc open success

3.主设备号不同,name不同

删除上面创建的 abc 文件, 然后重新创建一个 abc 文件,但是主设备号和注册驱动时候生成的主设备号不同:

[root@ChenZhiFa home]# rm /dev/abc

[root@ChenZhiFa home]# ./app

open error

[root@ChenZhiFa home]# mknod /dev/abc c 249 100

[root@ChenZhiFa home]# ./app

open error

测试结果:失败。

4.结论

结论—应用程序寻找不是通过设备名,而是通过设备号。

小结:

1. 字符设备找驱动程序流程:使用 open 函数打开/dev/xxx 文件,获得其中的主设备号和次设备号。

2. 根据上一步获得的主设备号和次设备号寻找在内核中已经注册的具有相同设备号的驱动程序。 (这个过程不需要你关注,内核自动行为。)

3. 早期模型注册函数的 name 并不要求和最终于 /dev/目录的设备名相同。

4. 早期模型注册进去后, 0~255 下的号都对应同一个驱动程序。

5. 早期模型注册函数的 name 在哪里有效?

AN:

[root@ChenZhiFa home]# cat /proc/devices

Character devices:

……

250 earlydev

……

[root@ChenZhiFa home]#

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


最新回复(0)