本文讲解如何利用内核提供的接口,在/sys下创建设备的属性节点,实现属性的读写接口。
1、主要数据结构
一、kobject --> 目录;kobj_type --> 属性文件
使用到的内核数据结构如下:
struct kobject {
const char *name;
/*当前kobj的父节点,在文件系统中的表现就是父目录*/
struct kobject *parent;
/*kobj属于的kset*/
struct kset *kset;
/*kobj的类型描述,最主要的是其中的属性描述,包含其读写方式*/
struct kobj_type *ktype;
/*当前kobj的引用,只有当引用为0时才能被删除*/
struct kref kref;
...
};
struct kobj_type {
void (*release)(struct kobject *kobj); 释放kobject和其占用资源的函数
const struct sysfs_ops *sysfs_ops; 操作下一个属性数组的方法
struct attribute **default_attrs; 属性数组
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
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
};
struct attribute_group {
const char *name;
umode_t (*is_visible)(struct kobject *,
struct attribute *, int);
struct attribute **attrs;
};
struct kobj_attribute {
struct attribute attr;
ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr,
char *buf);
ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count);
};
#define __ATTR(_name,_mode,_show,_store) { \
.attr = {.name = __stringify(_name), .mode = _mode }, \
.show = _show, \
.store = _store, \
}
二、kobj_type:是用户空间的法宝
为kobject 对象构建多个属性文件(这些属性文件都是共用kobj_attr_show和kobj_attr_store读写接口)。这两个函数接口内部最终会根据函数参数struct attribute调用到我们自己实现的函数。
为每个属性文件设置具体操作接口(在上面两个统一的操作接口里面,会进一步去调用每个attribute属性文件具体的操作接口)。具体操作接口需要我们自己去实现。
虚拟文件系统vfs的inode对象与sysfs的kernfs_node对象的绑定过程(虚拟文件系统不能识别 kernfs_node)。
2、源码解析
编写一个内核模块,当模块加载后可以在 /sys 下创建一个叫 ssj_kobject 的目录,该目录下拥有两个属性 name、led,应用层可以通过读写属性文件修改名字,和控制 led 状态。
sys_demo.c 源码如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/gpio.h>
#include <asm/uaccess.h>
#include <linux/uaccess.h>
static char *name = NULL;
static int led_status = 0;
static struct kobject *my_obj;
#define LED_PIN 14
/**
* @brief show 和 store函数的返回值为接收到的数据长度
*
*/
static ssize_t name_show(struct kobject* kodjs,struct kobj_attribute *attr,char *buf)
{
if(name != NULL){
return sprintf(buf,"my name is %s",name);
} else {
return sprintf(buf,"I don't have a name yet!\n");
}
}
static ssize_t name_store(struct kobject *kobj, struct kobj_attribute *attr,const char *buf, size_t count)
{
name = kmalloc(count,GFP_KERNEL);
strncpy(name,buf,count);
printk("have set name is %s",name);
return count;
}
/**
* @brief show 和 store函数的返回值为接收到的数据长度
*
*/
static ssize_t led_show(struct kobject* kodjs,struct kobj_attribute *attr,char *buf)
{
if(led_status==0){
return sprintf(buf,"led status is off\n");
} else if(led_status==1){
return sprintf(buf,"led status is on\n");
}
}
static ssize_t led_store(struct kobject *kobj, struct kobj_attribute *attr,const char *buf, size_t count)
{
if(0 == memcmp(buf,"on",2))
{
gpio_set_value(LED_PIN,1);
led_status = 1;
printk("open led!\n");
}
else if(0 == memcmp(buf,"off",3))
{
gpio_set_value(LED_PIN,0);
led_status = 0;
printk("close led!");
}
return count;
}
static struct kobj_attribute name_attr = __ATTR(name,0660,name_show,name_store);
static struct kobj_attribute led_attr = __ATTR(led,0660,led_show,led_store);
static struct attribute *led_attrs[] = {
&name_attr.attr,
&led_attr.attr,
NULL,
};
static struct attribute_group my_attr_group = {
.name = "ssj_kobj",
.attrs = led_attrs,
};
int create_kobject(void)
{
my_obj = kobject_create_and_add("ssj_kobj",kernel_kobj->parent);
return 0;
};
static void gpio_config(void)
{
if(!gpio_is_valid(LED_PIN)){
printk(KERN_ALERT "Error wrong gpio number\n");
return ;
}
gpio_request(LED_PIN,"led_ctr");
gpio_direction_output(LED_PIN,1);
gpio_set_value(LED_PIN,1);
led_status = 1;
}
static void gpio_deconfig(void)
{
gpio_free(LED_PIN);
}
static int __init sysfs_ctrl_init(void){
printk(KERN_INFO "ssj Kobject test!\n");
gpio_config();
create_kobject();
sysfs_create_group(my_obj, &my_attr_group);
return 0;
}
static void __exit sysfs_ctrl_exit(void){
gpio_deconfig();
kobject_put(my_obj);
printk(KERN_INFO "Have removed mode!\n");
}
module_init(sysfs_ctrl_init);
module_exit(sysfs_ctrl_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("SiShaojian");
MODULE_DESCRIPTION("Kobject demo!");
MODULE_VERSION("0.1");
关键代码解析:
创建 kobj_attribute 并初始化它们的名字和指定 show store 函数
static struct kobj_attribute name_attr = __ATTR(name,0660,name_show,name_store);
static struct kobj_attribute led_attr = __ATTR(led,0660,led_show,led_store);
创建及初始化 attribute_group
static struct attribute *led_attrs[] = {
&name_attr.attr,
&led_attr.attr,
NULL,
};
static struct attribute_group my_attr_group = {
.name = "ssj_kobj",
.attrs = led_attrs,
};
创建 kobject 起名为 ssj_kobj,在/sys 目录下产生 ssj_kobj目录
int create_kobject(void)
{
my_obj = kobject_create_and_add("ssj_kobj",kernel_kobj->parent);
return 0;
};
给 my_obj 添加属性文件,会在ssj_obj目录下出现 my_attr_group 中包含的属性
static int __init sysfs_ctrl_init(void){
...
sysfs_create_group(my_obj, &my_attr_group);
...
}
3、将源码编译成模块
1、在内核源码的 /drivers 下创建 mode 目录。
2、修改/drivers 下的makefile,添加
obj-y += mode/
3、mode目录下有 sys_demo.c 和 Makefile 两个文件,Makefile文件的内容如下:
obj-m += sys_demo.o
# KERNEL_PATH 是内核源码的根目录
KERNEL_PATH := /home/ssj/zmj1/linux-kernel/
PWD := $(shell pwd)
all:
make -C $(KERNEL_PATH) M=$(PWD) modules
clean:
make -C $(KERNEL_PATH) M=$(PWD) clean
设置好你的内核编译环境之后,在此目录下执行 make,将sys_mode.c 编译成 sys_demo.ko
4、测试模块的功能
将模块从ubuntu发送到开发板:
scp sys_demo.ko root@191.168.0.66:/home/root
在开发板上加载模块
测试 led 属性
测试 name 属性