{
int fd;
char buf[64] = {};
fd = open("/dev/hello\_misc",O_RDWR);
if (fd < 0)
{
printf("open error\n");
return fd;
}
buf[0] = atoi(argv[1]);
write(fd, buf, sizeof(buf));//向驱动层写入,驱动层调用misc\_write
close(fd);
return 0;
}
#### 驱动模块传参
1、什么是驱动传参
驱动传参就是传递参数给驱动。
举例:
insmod beep.ko a=1
2、驱动传参有什么作用?
(1)设置驱动相关参数,比如设置缓冲区的大小
(2)设置内核的安全校验,防止写的驱动被别人盗用
3、怎么给驱动传递参数?
(1)传递普通的参数,比如char,int类型
函数:
module\_param(name,type,perm);
参数:
name 要传递进去参数的名称
type:类型
perm:参数读写的权限
static int a;
module_param(a,int,S_IRUSR);
static int hello_init(void)
{
printk(“a=%d\n”,a);
return 0;
}
(2)传递数组
函数:
module\_param\_array(name,type,numo,perm);
参数:
name:需要传递的参数
type:传递的参数类型
numo:实际传入进入的个数
perm:参数读写的权限
static int b[5];
static int count;
module_param(a,int,S_IRUSR);
module_param_array(b,int,&count,S_IRUSR);
static int hello_init(void)
{
int i;
for (i=0;i<count;i++)
{
printk(“b[i]=%d\n”,i,b[i]);
}
printk("count = %d\n",count);
printk("a=%d\n",a);
return 0;
}
使用命令传入参数
##### 实验现象
实验结果如下图所示:打印出a的数值
![在这里插入图片描述](https://img-blog.csdnimg.cn/ee51d2cfa1504e269204ce8d3e27e085.png)
实验结果如下图所示:打印出b的数组的数值
insmod parameter.ko b=1,2,1,3,3
![在这里插入图片描述](https://img-blog.csdnimg.cn/e5860f543c424f478979afad99cf3677.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5qKF5bGx5YmR5a6i,size_20,color_FFFFFF,t_70,g_se,x_16)
(3)如果传递参数超过个数,会发生什么?
会报错,崩溃~~~!!!如图所示!!!
![在这里插入图片描述](https://img-blog.csdnimg.cn/a299173ad04c4f03945ee19a25168b0d.png)
#### 二、申请字符类设备号
1、字符设备和杂项设备的区别(复习)
杂项设备的主设备号是固定的,固定为10。
字符设备的主设备号不是固定的,那么需要自己或者系统来分配设备号。
杂项设备可以自动生成设备节点。
字符设备需要手动生成设备节点。
2、注册字符类设备号的两个方法。
第一种:静态分配一个设备号
在include/linux/fs.h函数:
int register_chrdev_region(dev_t, unsigned, const char *);
需要明确知道系统里面哪些设备号没有被使用。
参数:
dev\_t 设备号的起始值。类型是dev\_t。
unsigned:次设备号的个数。
const char\* : 设备号的名称。
返回值:成功返回0,失败返回非0。
dev\_t 用来保存设备号,是一个32位的数据类型。定义在linux/types.h中。
高12位用来保存主设备号。
低20位用来保存次设备号。
Linux提供的几个宏定义来操作设备号。
//次设备号的位数,一共是20位
#define MINORBITS 20
//次设备号的掩码
#define MINORMASK ((1U << MINORBITS) - 1)
//dev_t中获取主设备号
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
//dev_t中获取次设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
//将主设备号和次设备号组成dev_t类型,第一个参数是主设备号,第二个参数是次设备号。
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
第二种:动态分配
使用函数为:
int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
参数:
第一个:保存生成的设备号。
第二个:请求的第一个次设备号,通常为0。
第三个:连续申请的设备号的个数。
第四个:设备名称。
返回值:成功返回0,失败返回非0。
使用动态分配会优先使用255-234之间的号码。
3、注销设备号
参数1:分配设备号的起始地址。
参数2:申请设备号的数目。
void unregister_chrdev_region(dev_t,unsigned);
驱动代码编写如下所示:分为两种设备号分配方式(静态分配和动态分配)
#include<linux/init.h>
#include<linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
static int major_num,minor_num;
#define DEVICE_NUMBER 1 //分配设备号数目
#define DEVICE_SNAME “schrdev” //主设备号名称
#define DEVICE_ANAME “achrdev” //次设备号名称
#define DEVICE_MIN_NUMBER 0 //次设备号起始位置
module_param(major_num,int,S_IRUSR);
module_param(minor_num,int,S_IRUSR);
static int hello_init(void)
{
dev_t dev_num;
int ret;
printk("major\_num = %d\n",major_num);
printk("minor\_num = %d\n",minor_num);
if (major_num)//主设备号传递进来,静态方法
{
dev_num = MKDEV(major_num,minor_num);
ret = register\_chrdev\_region(dev_num,DEVICE_NUMBER,DEVICE_SNAME);
if (ret<0)
{
printk("register\_chrdev\_region error\n");
}
printk("register\_chrdev\_region ok\n");
}
else //主设备号没有传递进来,使用动态方法分配设备号
{
ret = alloc\_chrdev\_region(&dev_num,DEVICE_MIN_NUMBER,DEVICE_NUMBER,DEVICE_ANAME);
if (ret<0)
{
printk("register\_chrdev\_region error\n");
}
printk("register\_chrdev\_region ok\n");
major_num = MAJOR(dev_num);
minor_num = MINOR(dev_num);
printk("major\_num = %d\n",major_num);
printk("minor\_num = %d\n",minor_num);
}
return 0;
}
static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER);//注销设备号
printk(“bye bye”);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE(“GPL”);
建议使用动态分配的方式申请设备号。静态分配在多人使用容易冲突。
##### 实验现象
申请字符类设备号之后,打印主设备号和次设备号。之后在注册设备号成功之后,打印主设备号和次设备号。
![在这里插入图片描述](https://img-blog.csdnimg.cn/87f75e77ec0c4be1b8a0af064043374c.png)
#### 三、注册字符类设备
注册杂项设备
misc\_register(&misc\_dev);
注销杂项设备
misc\_deregister(&misc\_dev);
cdev结构体:描述字符设备的结构体
在include/linux/cdev.h中
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
步骤一:定义一个cdev结构体
步骤二:使用cdev\_init函数初始化cdev结构体成员
void cdev_init(struct cdev *, const struct file_operations *);
参数:
第一个:要初始化的cdev
第二个:文件操作集
cdev->ops = fops;//实际就是把文件操作集写给ops。
步骤三:使用cdev\_add函数注册到内核中
int cdev_add(struct cdev *, dev_t, unsigned);
参数1:cdev的结构体指针
参数2:设备号
参数3:次设备号的数量
注销字符设备:
void cdev_del(struct cdev *);
驱动代码编写
#include<linux/init.h>
#include<linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
struct cdev cdev;
static int hello_init(void)
{
上节代码添加如下内容
.
.
.
.
.
.
cdev.owner = THIS_MODULE;
cdev\_init(&cdev,&chrdev_ops);//字符设备初始化
cdev\_add(&cdev,dev_num,DEVICE_NUMBER);//字符设备的注册
return 0;
}
static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER);
cdev\_del(&cdev);
printk("bye bye");
}
应用代码编写
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int fd;
char buf[64] = {};
fd = open("/dev/test",O_RDWR);
if (fd < 0)
{
printf("open error\n");
return fd;
}
return 0;
}
##### 注意!!
字符设备注册完成之后,并不会自动生成设备节点,需要使用mknod命令创建一个设备节点。
格式:
mknod 名称 类型 主设备号 次设备号
举例:
mknod /dev/test c 247 0
创建设备节点:
创建完成之后,查看/dev/目录 可以看到test
![在这里插入图片描述](https://img-blog.csdnimg.cn/936154ef8b82425ebba5a1124213da8d.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5qKF5bGx5YmR5a6i,size_20,color_FFFFFF,t_70,g_se,x_16)运行app
![在这里插入图片描述](https://img-blog.csdnimg.cn/c5d02ef802214c9cb169bc19184d1382.png)
现象:运行app之后,打印驱动open调用结果。
#### 四、自动创建设备节点(推荐)
先前的实验使用insmod命令加载模块之后,通过mknod命令手动创建设备节点,为了方便操作,学习实现如何自动创建设备节点。当加载模块的时候,在/dev/目录下自动创建相应的设备文件。
1、怎么自动创建一个设备节点。
在嵌入式linux 中使用mdev来实现设备节点的自动创建和删除。
2、什么是mdev?
是udev的简化版本,是busybox中所带的程序,适合用在嵌入式系统。
3、什么是udev?
一种工具,能够根据系统中的硬件设备的状态更新设备文件,包括文件的创建和删除。设备文件通常存放在/dev目录下,使用udev后,在/dev目录下就只包含系统中真正存在的设备。udev一般在PC上使用。
4、怎么自动创建设备节点?
自动创建设备节点分为两步骤
步骤一:使用class_create函数创建一个class的类。
步骤二:使用device_create函数在我们创建的类下面创建一个设备。
5、创建和删除类函数
一般使用两个函数来完成设备节点的创建和删除。首先创建一个class类结构体,class类结构体定义在include/linux/device.h里面。class\_create是类创建函数,class\_create是个宏定义,内容如下:
#define class_create(owner, name)
({
static struct lock_class_key __key;
__class_create(owner, name, &__key);
})
一共有两个参数,参数owner一般为THIS\_MODULE,参数name是类名字。
返回值指向结构体class的指针,也就是创建的类。
卸载驱动程序的时候需要删除掉类,类删除函数为class\_destory。函数原型如下:
void class_destory(struct class *cls);
参数cls就是要删除的类。
驱动代码:
注册成功之后会在/sys/class下生成一个类
#define DEVICE_CLASS_NAME “chrdev_class”
static int hello_init(void)
{
.
.
.
.
.
.
class = class\_create(THIS_MODULE,DEVICE_CLASS_NAME);
return 0;
}
static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER);
cdev\_del(&cdev);
class\_destory(class);
printk("bye bye");
}
6、创建设备函数
当使用上节的函数创建完成一个类之后,使用device\_create函数在这个类下创建一个设备。device\_create函数原型如下:
struct device *device_create(struct class *cls,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, …);
device\_create是个可变参数函数,
参数1:在哪个设备下创建
参数2:父设备,一般为空
参数3:devt是设备号
参数4:drvdata是设备可能使用到的数据,一般为NULL;
参数5:fmt设备名字,如果设置fmt=xxx的话,就好生成/dev/xxx这个设备文件。
返回值为创建好的设备。
## 最后
**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**
**深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。**
**因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
![img](https://img-blog.csdnimg.cn/img_convert/3cd045e87c45057d5363112951f0ba37.png)
![img](https://img-blog.csdnimg.cn/img_convert/0887a5eee1e1cb92bd350f1e75c6e8c5.jpeg)
![img](https://img-blog.csdnimg.cn/img_convert/63129b70f5f0c8708e0b1ca445a91c40.png)
![img](https://img-blog.csdnimg.cn/img_convert/e32b622f81d1a84f8c34c34608af83eb.png)
![img](https://img-blog.csdnimg.cn/img_convert/17927fb4b16e126457ddfdf0080a4743.png)
![img](https://img-blog.csdnimg.cn/img_convert/f3c34c3bb34743ecd2aa49be39d15950.png)
![](https://img-blog.csdnimg.cn/img_convert/c03206588a40a0a8b7c3888eead27d8d.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!**
[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618654289)
**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**!!
提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。**
**因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
[外链图片转存中...(img-rek3pQEw-1715840687230)]
[外链图片转存中...(img-82MLVQAB-1715840687231)]
[外链图片转存中...(img-G20CEjmM-1715840687232)]
[外链图片转存中...(img-PCCt9NGM-1715840687232)]
[外链图片转存中...(img-cyWr0awV-1715840687232)]
[外链图片转存中...(img-SaSAqkRs-1715840687233)]
[外链图片转存中...(img-xhdt3iAs-1715840687233)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!**
[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618654289)
**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**!!