作业:在sysfs中建立一个属性,可以通过shell更改属性,可查询
参考:Linux内核宏DEVICE_ATTR使用 - Cqlismy - 博客园 (cnblogs.com)
思路:通过驱动建立属性?查询资料得知在include/linux.h/device.h中有添加非默认属性的接口:device_attribute。在linux驱动程序编写中使用DEVICE_ATTR宏,可以定义一个struct device_attribute设备属性,再使用sysfs的API函数就可以在设备目录下创建出属性文件,当我们在驱动程序中实现了show和store函数后就可以使用cat和echo命令对创建出来的设备属性文件进行读写从而达到控制设备的功能。
结论:在驱动中实现show和store功能就可以实现属性更改和查询
需掌握的知识点:struct attribute结构体(在linux/device.h中有定义)
struct device_attribute(在linux/device.h中有定义)
宏DEVICE_ATTR的实现(在linux/device.h和linux/sysfs.h中有定义)
模块加载函数和模块卸载函数
make之后的操作
know1:struct attribute结构体
struct attribute {
const char *name;
umode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
bool ignore_lockdep:1;
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};
name:属性名称 mode:属性的读写权限
know2:struct device_attribute结构体
/* interface for exporting device attributes */
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
show:读取设备属性文件 store:写设备的属性文件
*驱动中实现后可以用cat和echo读写
know3:DEVICE_ATTR
#define DEVICE_ATTR(_name,_mode,_show,_store) \
struct device_attribute dev_attribute dev_attr_##_name = __ATTR(_name,_mode,_show,_store)
宏的功能:定义一个struct device_attribute结构体变量dev_attr_name并对里面的成员进行初始化,包括struct attribute结构体里面的name和mode成员变量、实现属性文件读写的show和store函数赋值。
*代码中的"\"表示换行,无实际意义
know4:应用实例
static ssize_t mydevice_show(struct device *dev,struct device_attribute *attr,char *buf){
return sprintf(buf,"%s\n",mybuf);
}
static ssize_t mydevice_store(struct device *dev,struct devie_attribute *attr,const char *buf,size_t count){
sprintf(mybuf,"%s",buf);
return count;
}
static DEVICE_ATTR(mydevice,0644,mydevice_show,mydevice_store);
可以看出,要使用DEVICE_ATTR宏,先对_show和_store函数进行定义,再通过宏初始化。
上述代码定义了一个mydevice的属性文件,这个属性文件的读写权限为:拥有者可以读写,所属组和其他人只能读。cat和echo命令会调用到上面写的show和store函数。
*权限:0644:用户具有读写权限,组和其他用户具有只读权限
0755:用户具有读写执行权限,组和其他用户具有只读权限
一般,目录0755,文件0644权限
*ssize_t表示有符号整形,在32位机器上等同int,在64位机器上等同long int;size_t表示无符号整形,unsigned long/unsigned int
*const:定义一个在整个作用域值都不能被改变的变量
know5:加载模块
static int __init mydevice_init(void){
int ret;
struct device *mydevice;
major = register_chrdev(0,"mydevice",&myfops);
if(major<0){
ret = major;
return ret;
}
myclass = class_create(THIS_MODULE,"myclass");
if(IS_ERR(myclass)){
ret = -EBUSY;
goto fail;
}
mydevice = device_create(myclass,NULL,MKDEV(major,0),NULL,"mydevice");
if(IS_ERR(mydevice)){
class_destory(myclass);
ret = -EBUSY;
goto fail;
}
ret = sysfs_create_file(&mydevice->kobj,&dev_attr_mydevice.attr);
if(ret<0)
return ret;
return 0;
fail:
unregister_chrdev(major,"mydevice");
return ret;
}
register_chrdev()完成对主设备号的动态申请,且注册设备名称为mydevice,再用class_create()和device_create()在sysfs中动态创建出设备所属的类myclass和设备mydevice并对返回结果进行了错误检测,最后使用sysfs的API函数在sys中创建出设备的属性文件,完成模块加载。
*register_chrdev()是注册设备驱动程序的内核函数,需要的参数:
major,主设备号,当设置为0时内核会动态分配一个设备号
baseminor:次设备号,在一定范围内从0开始
count:次设备号的范围
name:设备名称
fops:文件系统的接口指针
在上述代码中主设备号是动态分配的,没有使用次设备号。因此baseminor和count都没有出现。当major为0时且正常注册后,返回分配的主设备号;注册失败则返回-EBUSY。当major不为0时,若指定的major值已有注册的设备,返回-EBUSY;注册成功则返回0.
*卸载模块使用device_destory(myclass,MKDEV(major,0)),class_destory(myclass),unregister_chrdev(major,"mydevice")
*MKDEV宏:MKDEV(MAJOR,MINOR)获取设备在设备表中的位置,major为主设备号,minor为次设备号,为0表示不能使用
*__函数代表内核级函数
know6:模块编译运行
将文件保存为test_cha.c
编写makefile,后在命令行:
make
sudo insmod test_cha.ko
sudo dmesg
cd /sys/device/virtual/myclass/mydevice/
ls
可以看到其中有我们创建的属性文件mydevice。
*sys/device/virtual中存放的是我们创建的东西
用cat和echo进行测试。
cat mydevice
echo "success">mydevice
这里如果使用su进入root用户模式后可以直接这么操作,但如果没有进入该模式,只用sudo是无法解决权限不足的情况的,需要使用sudo bash -c 'echo "success">mydevice',就不会报错了。
这之后再用cat获取mydevice的值,就可以看到改变了。