火车头采集器网站被k,阜宁做网站公司,江苏网站建设方案,深圳网站建制作文章目录 注册杂项设备驱动模块传参注册字符设备 开发环境#xff1a; windows ubuntu18.04 迅为rk3568开发板 注册杂项设备
相较于字符设备#xff0c;杂项设备有以下两个优点:
节省主设备号:杂项设备的主设备号固定为 10#xff0c;在系统中注册多个 misc 设备驱动时 windows ubuntu18.04 迅为rk3568开发板 注册杂项设备
相较于字符设备杂项设备有以下两个优点:
节省主设备号:杂项设备的主设备号固定为 10在系统中注册多个 misc 设备驱动时只需使用子设备号进行区分即可。使用简单相比如普通的字符设备驱动 misc驱动只需要将基本信息通过结构体传递给相应处理函数即可。
在linxu系统中可使用 cat /proc/misc 命令查看系统中的杂项设备。注册杂项设备的步骤 1.填充设备操作集结构体struct file_operations 2.填充杂项设备结构体struct miscdevice; 3.使用函数misc_register注册杂项设备; 4.使用函数misc_deregister卸载杂项设备
上面三步可使用下面函数直观用表达即
static struct file_operations xxx_fops{.owner THIS_MODULE, .read xxx_read, ....
};
struct miscdevice xxx_dev{.minor MISC_DYNAMIC_MINOR, .name xxx, .fops xxx_fops
};
static int __init xxx_init(void) //驱动入口函数
{int ret;printk(KERN_EMERG xxx_init\r\n);ret misc_register(xxx_dev);//注册杂项设备if(ret0){printk( misc_register failed\r\n);return -1;}printk( misc_register ok\r\n);return 0;
}
static void __exit xxx_exit(void) //驱动出口函数
{printk(KERN_EMERG xxx_exit\r\n);misc_deregister(xxx_dev); //卸载杂项设备
}
module_init(xxx_init); //注册入口函数
module_exit(xxx_exit);
MODULE_LICENSE(GPL);
MODULE_AUTHOR(xxx);具体实现注册一个杂项设备的示例代码如下
#include linux/kernel.h
#include linux/init.h //初始化头文件
#include linux/module.h //最基本的文件支持动态添加和卸载模块。
#include linux/miscdevice.h //注册杂项设备头文件
#include linux/fs.h //注册设备节点的文件结构体
#include linux/uaccess.h// 打开杂项设备
int _open(struct inode *inode,struct file*file)
{printk(KERN_EMERGhello misc);return 0;
}// 关闭杂项设备
int close(struct inode * inode, struct file *file)
{printk(KERN_EMERGclose);return 0;
}// 读取杂项设备中的数据
ssize_t misc_read (struct file *file, char __user *buff, size_t size, loff_t *loff)
{char kbuff[32] kernel;if(copy_to_user(buff,kbuff,strlen(kbuff)) ! 0) // 将内核中的数据给应用{printk(copy_to_user error\r\n);return -1;} printk(KERN_EMERGcopy_to_user is successful\r\n);return size;
}// 写入数据到杂项设备中
ssize_t misc_write (struct file *file, const char __user *buff, size_t size, loff_t *loff)
{char kbuff[32] ;if(copy_from_user(kbuff,buff,size)! 0) // 从应用那儿获取数据{printk(copy_from_user error\r\n);return -1;} printk(KERN_EMERGcopy_from_user data:%s\r\n,kbuff);return size;
}// 设备文件描述集
struct file_operations misc_fops {.owner THIS_MODULE,.open misc_open,.release close,.read misc_read,.write misc_write
};struct miscdevice misc_dev {.minor MISC_DYNAMIC_MINOR,.name hello_misc, // 杂项设备名 注册成功后会在 /dev目录下显示.fops misc_fops
};// 驱动的入口函数
static int __init misc_init(void)
{int ret 0;ret misc_register(misc_dev);if(ret 0)printk(KERN_EMERGmisc register is error\r\n.);elseprintk(KERN_EMERGmisc register is seccussful\r\n.);return 0;
}//驱动的出口函数
static void __exit misc_exit(void)
{misc_deregister(misc_dev);printk(KERN_EMERGbaibai\r\n);
}module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE(GPL v2);
MODULE_AUTHOR(zhouxianjie0716qq.com);编译传送到开发板上后先试用insmod 驱动名.ko挂载驱动其结果为
上述驱动代码的测试代码如下
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.hint main(int argc,char *argv[])
{char filePath[] /dev/hello_misc;// 打开文件int fd open(filePath,O_RDWR);if(fd 0){printf(opening is failed.\n);return -1;}elseprintf(opening is successful.\n);// 读取char buff1[32],buff2[32] hello this is app;read(fd,buff1,sizeof(buff1));printf(buff1 is %s.\n,buff1);// 写入write(fd,buff2,sizeof(buff2)); close(fd);return 0;
}使用./程序名运行测试代码后得结果如下 驱动模块传参
总所周知应用程序传参是通过shell终端传只要将main函数按照下面格式书写即可完成传参操作
int main(int argc ,char *argv[])
{return 0;
}相比之下驱动模块传递参数需要借助其他函数完成传参操作
1. 传递单个参数给内核
module_param(name, type, perm)参数解释 name:参数名既是外部参数名又是内部参数名。 type:参数的数据类型可取int、charp等。 perm:访问权限。八进制如:0777。0表示该参数在文件系统中不可见。
注意传递字符作为参数时数据类为charp而不是char.
2.传递数组给内核
module_param_array(name, type, nump, perm)module_param_array(name,type,nump,perm)name:数组参数名既是外部参数又是内部参数type:参数的数据类型nump:终端传给数组的实际元素个数指针变量perm:访问权限0644。0表示该参数在文件系统中不可见
3.传递字符串给内核
module_param_string(name, string, len, perm)name:参数名外部参数名string:内部参数名内部字符数组名len:数组长度perm:访问权限0644。0表示该参数在文件系统中不可见
传递参给内核的作用
1.设置驱动的相关参数如设备数量、设备缓冲区大小等等。2.可进行安全校验放置驱动被他人盗用。
说明 参数传递的适用时机为 加载驱动到内核时命令形式为insmod 驱动名.ko 参数1名参数值 参数2名参数值 ……。例如下面示例中使用insmod file.ko date12传递参数
#include linux/module.h
#include linux/init.h// 保存参数
static int date;
static int date1;
static int data[5];
static int count;
static char str[32];
static char *strData;// 传递单个参数
module_param(date,int,S_IRUGO|S_IWUSR); // 可读可写
module_param(date1,int,S_IRUGO); // 可读可写
module_param(strData,charp,S_IRUGO); // 可读// 传递多个参数
module_param_array(data,int,count,S_IRUGO); // 可读
module_param_string(str,str,sizeof(str),S_IRUGO); // 可读// 驱动的入口函数
static int __init dev_init(void)
{int i0;if(strcmp(str,myTest)!0){printk(dev_init error\r\n);return -1;}printk(---------------------------------------\r\n);printk(KERN_EMERGdev_init is successful!\r\n);for(i0;icount;i)printk(data[%d] %d \r\n,i,data[i]);printk(str:%s strData:%s\r\n,str,strData);printk(date:%d count:%d\r\n,date,count);data[1] 111;date 10;date1 111;return 0;
}//驱动的出口函数
static void __exit dev_exit(void)
{int i0;for( i0;icount;i)printk(data[%d] %d \r\n,i,data[i]);printk(date:%d count:%d\r\n,date,count);printk(KERN_EMERGdev_exit is successful\r\n);
}module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE(GPL v2);
MODULE_AUTHOR(zhouxianjie0716qq.com);注册字符设备 步骤一驱动初始化需要申请设备号初始化并且注册cdev结构体初始化硬件
可使用动态申请或静态申请设备号其中动态申请设备号一般在235-255,静态申请则一般由用户手动输入。
静态申请的函数原型为 int register_chrdev_region(dev_t, unsigned, const char *);参数含义
from: 自定义的 dev_t 类型设备号。count: 申请设备的数量。name: 申请的设备名称。 函数返回值申请成功返回 0申请失败返回负数。
动态申请的函数原型为 int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);参数含义
dev : 会将申请完成的设备号保存在 dev 变量中。baseminor: 次设备号可申请的最小值。count: 申请设备的数量。name: 申请的设备名称。 函数返回值申请成功返回 0申请失败返回负
Linux 内核中将字符设备抽象成一个具体的数据结构 (struct cdev), 我们可以理解为字符设备对象cdev 记录了字符设备号、内核对象、文件操作 file_operations 结构体设备的打开、读写、关闭等操作接口等信息:
struct cdev {struct kobject kobj; //内嵌的内核对象.struct module *owner; //该字符设备所在的内核模块的对象指针. const struct file_operations *ops; //该结构描述了字符设备所能实现的方法是极为关键的一个结构体.struct list_head list; //用来将已经向内核注册的所有字符设备形成链表. dev_t dev; //字符设备的设备号由主设备号和次设备号构成. unsigned int count; //隶属于同一主设备号的次设备号的个数.
};初始化设备描述集cdev结构体的函数原型为
void cdev_init(struct cdev *, const struct file_operations *);参数含义
参数1表示是抽象设备结构体参数2表示文件操作集
注册设备到内驱使用下面函数
int cdev_add(struct cdev *, dev_t, unsigned);参数含义
参数1为要添加的 struct cdev 类型的结构体参数2为申请的字符设备号参数3为和该设备关联的设备编号的数量 若函数在内核中添加成功返回 0添加失败返回负数。
步骤二构建设备文件操作描述集file_operations
也就是read、write、open、close等函数。
步骤三生成并且添加设备节点
手动添加设备节点也就是在加载驱动到内核时添加即mknod 路径/设备名 设备类型 主设备号 次设备号自动添加设备节点初始化内核时使用函数自动添加。即先试用函数class_create创建一个类在使用函数device_create创建并且添加设备节点。
class_create函数说明
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \ __class_create(owner, name, __key); \
})函数作用 用于动态创建设备的逻辑类并完成部分字段的初始化然后将其添加进 Linux 内核系统。
参数含义
owner指向函数即将创建的这个 struct class 的模块。一般为 THIS_MODULE。name代表即将创建的 struct class 变量的名字。 返回值struct class * 类型的类。
device_create函数说明 struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt,...);函数作用 用来在 class 类中下创建一个设备属性文件udev 会自动识别从而进行设备节点的创建。
参数含义
cls指定所要创建的设备所从属的类。parent:指定该设备的父设备如果没有就指定为 NULL。devt:指定创建设备的设备号。drvdata:被添加到该设备回调的数据没有则指定为 NULL。fmt添加到系统的设备节点名称。
返回值struct device * 类型结构体的设备
步骤四注销字符设备驱动
步骤一使用函数unregister_chrdev_region释放设备号
void unregister_chrdev_region(dev_t, unsigned)该函数只有一个参数为要删除设备的设备号并且函数无返回值。
步骤二使用函数cdev_del步骤二删除设备操作集卸载cdev
void cdev_del(struct cdev *);该函数只有一个参数为要删除的 struct cdev 类型的结构体并且函数无返回值。
步骤三使用函数device_destroy卸载设备
void device_destroy(struct class *cls, dev_t devt);用来删除 cls 类中的devt设备属性文件udev 会自动识别从而进行设备节点的删除。
步骤四使用函数class_destroy删除类class void class_destroy(struct class *cls);该函数只有一个参数为要删除的类并且函数无返回值。
示例代码
#include linux/module.h
#include linux/init.h
#include linux/fs.h
#include linux/kdev_t.h
#include linux/cdev.h#define DEVICE_NUMBER 1 // 设备数量
#define DEVICE_SNAME schrdev // 静态申请时设备名
#define DEVICE_ANAME achrdev // 动态申请时设备名
#define DEVICE_MINOR_NUM 0 // 次设备号起始地址
#define DEVICE_CLASS_NAME myTestClass // 类名
#define DEVICE_MYNAME mytest// 打开设备
int chrdev_open(struct inode*inode,struct file*file)
{printk(chrdev_open is opened\r\n);return 0;
}// 保存设备号 其中前12位为主设备号 后20位为次设备号
static dev_t dev_num;// 定义主设备号 次设备号
static int major_num,minor_num; // 设备信息描述集
struct cdev cdev;// 类描述集
struct class *cls;// 设备描述集
struct device*device;// 传递单个参数
module_param(major_num,int,S_IRUGO); // 可读
module_param(minor_num,int,S_IRUGO); // 可读// 文件操作集
struct file_operations chrdev_opr {.owner THIS_MODULE,.open chrdev_open
};// 驱动的入口函数
static int __init dev_init(void)
{int ret;/* 步骤一申请设备号 */// 有传递主设备号就静态申请if(major_num){ dev_num MKDEV(major_num, minor_num);//将主设备号 次设备号合并成设备号//参数分别表示 设备号 设备数量 设备名称ret register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME); if(ret 0){printk(dev_init error\r\n);return -1;}} // 否则就动态申请else{// 参数设备号 次设备号起始地址 设备数量 设备名称ret alloc_chrdev_region(dev_num , DEVICE_MINOR_NUM, DEVICE_NUMBER, DEVICE_ANAME);if(ret 0){printk(dev_init error\r\n);return -1;}// 获取主设备号 次设备号major_num MAJOR(dev_num);minor_num MINOR(dev_num);}printk(---------------------------------------\r\n);printk(dev_num:%d major_num:%d minor_num:%d\r\n,dev_num,major_num,minor_num);/* 步骤二初始化设备 */// 初始化cdevcdev.owner THIS_MODULE;// 初始化设备 cdev_init(cdev, chrdev_opr);/* 步骤三注册设备到内核 *///添加(注册)到内核 参数 设备 设备号 设备数量cdev_add(cdev, dev_num, DEVICE_NUMBER);/* 步骤四先创建类再自动创建添加设备名称*/// 创建类 参数1 类的归属 参数2 类名cls class_create(THIS_MODULE,DEVICE_CLASS_NAME);// 创建设备 参数1 归属到类 参数2设备的父设备 参数3设备号 参数4添加到设备的回调数据 参数5设备名device device_create(cls,NULL,dev_num,NULL,DEVICE_MYNAME);printk(auto add device name\r\n);return 0;
}//驱动的出口函数
static void __exit dev_exit(void)
{/* 步骤一注销设备号 参数设备号 设备数量 */ unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);/* 步骤二删除设备操作集 */cdev_del(cdev);/* 步骤三删除设备*/device_destroy(cls,dev_num);/* 步骤四 删除类*/class_destroy( cls);printk(KERN_EMERGdev_exit is successful\r\n);
}module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE(GPL v2);
MODULE_AUTHOR(zhouxianjie0716qq.com);使用加载驱动到内核insmod file.ko命令得结果
在测试驱动前需要使用mknod /dev/mytest c 236 0创建设备文件命令格式为mknod /路径/设备名称 设备类型主设备号 次设备号。
然后就可写一个应用程序来测试我们的字符驱动是否成功注册测试源码如下
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.hint main(int argc,char* argv[])
{char name[] /dev/mytest;int fd open(name,O_RDONLY); // 仅读if(fd 0){printf(open is failed\n);return -1;}close(fd);return 0;
}执行测试程序后结果如下
注意在运行测试程序时一定要先创建驱动文件否则就会报段错误具体如下 杂项设备与字符设备的比较
杂项设备的主设备号固定为10而字符设备的主设备号需要创建。杂项设备的创建相对简单只需要填充设备操作集结构体、杂项设备结构体再注册即可。而字符设备需要经过 申请设备号、初始化并且注册cdev结构体、初始化硬件、构建设备文件操作描述集、生成并且添加设备节点。杂项设备与字符设备都需要构建文件操作描述集。(应用层调用驱动的核心)