您的位置:首页 > 运维架构 > Linux

Linux内核设计与实现 学习笔记(7)设备与模块

2015-01-02 15:08 661 查看
主要内容:

设备类型
内核模块
内核对象
sysfs
总结


1.设备类型

linux中主要由3种类型的设备,分别是:
设备类型
代表设备
特点
访问方式
块设备硬盘,光盘随机访问设备中的内容一般都是把设备挂载为文件系统后再访问
字符设备键盘,打印机只能顺序访问(一个一个字符或者一个一个字节)一般不挂载,直接和设备交互
网络设备网卡打破了Unix"所有东西都是文件"的设计原则通过套接字API来访问
除了以上3种典型的设备之外,其实Linux中还有一些其他的设备类型,其中见的较多的应该算是"伪设备"。

所谓"伪设备",其实就是一些虚拟的设备,仅提供访问内核功能而已,没有物理设备与之关联。

典型的"伪设备"就是/dev/random(内核随机数发生器),/dev/null(空设备),/dev/zero(零设备),/dev/full(满设备)


2.内核模块

Linux内核是模块化组成的,内核中的模块可以按需加载,从而保证内核启动时不用加载所有的模块,即减少了内核的大小,也提高了效率。

通过编写内核模块来给内核增加功能或者接口是个很好的方式(既不用重新编译内核,也方便调试和删除)。


2.1内核模块示例

内核模块可以带参数也可以不带参数,不带参数的内核模块比较简单。

我之前的几篇随笔中用于测试的例子都是用不带参数的内核模块来实验的。

2.1.1.无参数的内核模块

参考:

Linux内核设计与实现
读书笔记(6)内核数据结构

Linux内核设计与实现
读书笔记(8)中断处理下半部

Linux内核设计与实现
学习笔记(1)定时器和时间管理

2.1.2.带参数的内核模块

构造带参数的内核模块其实也不难,内核中已经提供了简单的框架来给我们声明参数。

1.module_param(name,type,perm):定义一个模块参数

+参数name::既是用户可见的参数名,也是模块中存放模块参数的变量名

+参数type::参数的类型(byte,short,int,uint,long,ulong,charp,bool...)byte型存放在char变量中,bool型存放在int变量中

+参数perm::指定模块在sysfs文件系统中对应的文件权限(关于sysfs的内容后面介绍)

staticintstu_id=0;//默认id
module_param(stu_id,int,0644);


2.module_param_named(name,variable,type,perm):定义一个模块参数,并且参数对内对外的名称不一样

+参数name::用户可见的参数名

+参数variable::模块中存放模块参数的变量名

+参数type和perm::同module_param中的type和perm

staticchar*stu_name_in="defaultname";//默认名字
module_param_named(stu_name_out,stu_name_in,charp,0644);
/*stu_name_out是对用户开放的名称
*stu_name_in是内核模块内部使用的名称
*/


3.module_param_string(name,string,len,perm):拷贝字符串到指定的字符数组

+参数name::用户可见的参数名

+参数string::模块中存放模块参数的变量名

+参数len::string参数的缓冲区长度

+参数perm::同module_param中的perm

staticcharstr_in[BUF_LEN];
module_param_string(str_out,str_in,BUF_LEN,0);
/*perm=0表示完全禁止sysfs项*/


4.module_param_array(name,type,nump,perm):定义数组类型的模块参数

+参数name::同module_param中的name

+参数type::同module_param中的type

+参数nump::整型指针,存放数组的长度

+参数perm::同module_param中的perm

#defineMAX_ARR_LEN5
staticintarr_len;
staticintarr_in[MAX_ARR_LEN];
module_param_array(arr_in,int,&arr_len,0644);


5.module_param_array_named(name,array,type,nump,perm):定义数组类型的模块参数,并且数组参数对内对外的名称不一样

+参数name::数组参数对外的名称

+参数array::数组参数对内的名称

+参数type,nump,perm::同module_param_array中的type,nump,perm

#defineMAX_ARR_LEN5
staticintarr_len;
staticintarr_in[MAX_ARR_LEN];
module_param_array_named(arr_out,arr_in,int,&arr_len,0644);


6.参数描述宏

可以通过MODULE_PARM_DESC()来给内核模块的参数添加一些描述信息。

这些描述信息在编译完内核模块后,可以通过modinfo命令查看。

staticintstu_id=0;//默认id
module_param(stu_id,int,0644);
MODULE_PARM_DESC(stu_id,"学生ID,默认为0");//这句就是描述内核模块参数stu_id的语句


7.带参数的内核模块的示例

示例代码:test_paramed_km.c

定义了3个内核模块参数,分别是int型,char*型,数组型的。

#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>

MODULE_LICENSE("DualBSD/GPL");

structstudent
{
intid;
char*name;
};
staticvoidprint_student(structstudent*);

staticintstu_id=0;//默认id module_param(stu_id,int,0644);
MODULE_PARM_DESC(stu_id,"学生ID,默认为0");

staticchar*stu_name_in="defaultname";//默认名字
module_param_named(stu_name_out,stu_name_in,charp,0644);
MODULE_PARM_DESC(stu_name,"学生姓名,默认为defaultname");

#defineMAX_ARR_LEN5 staticintarr_len; staticintarr_in[MAX_ARR_LEN]; module_param_array_named(arr_out,arr_in,int,&arr_len,0644);
MODULE_PARM_DESC(arr_in,"数组参数,默认为空");

staticinttest_paramed_km_init(void)
{
structstudent*stu1;
inti;

/*进入内核模块*/
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"test_paramed_kmisinited!\n");
printk(KERN_ALERT"*************************\n");
//根据参数生成structstudent信息
//如果没有参数就用默认参数
printk(KERN_ALERT"alloconestudent....\n");
stu1=kmalloc(sizeof(*stu1),GFP_KERNEL);
stu1->id=stu_id;
stu1->name=stu_name_in;
print_student(stu1);

//模块数组
for(i=0;i<arr_len;++i){
printk(KERN_ALERT"arr_value[%d]:%d\n",i,arr_in[i]);
}

return0;
}

staticvoidtest_paramed_km_exit(void)
{
/*退出内核模块*/
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"test_paramed_kmisexited!\n");
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"\n\n\n\n\n");
}

staticvoidprint_student(structstudent*stu)
{
if(stu!=NULL)
{
printk(KERN_ALERT"**********studentinfo***********\n");
printk(KERN_ALERT"studentidis:%d\n",stu->id);
printk(KERN_ALERT"studentnameis:%s\n",stu->name);
printk(KERN_ALERT"*********************************\n");
}
else
printk(KERN_ALERT"thestudentinfoisnull!!\n");
}

module_init(test_paramed_km_init);
module_exit(test_paramed_km_exit);


上面的示例对应的Makefile如下:

#mustcomplileoncustomizekernel
obj-m+=paramed_km.o
paramed_km-objs:=test_paramed_km.o

#generatethepath
CURRENT_PATH:=$(shellpwd)
#thecurrentkernelversionnumber
LINUX_KERNEL:=$(shelluname-r)
#theabsolutepath
LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
#complieobject
all:
make-C$(LINUX_KERNEL_PATH)M=$(CURRENT_PATH)modules
rm-rfmodules.orderModule.symvers.*.cmd*.o*.mod.c.tmp_versions*.unsigned
#clean
clean:
rm-rfmodules.orderModule.symvers.*.cmd*.o*.mod.c*.ko.tmp_versions*.unsigned




内核模块运行方法:(我的运行环境是CentOS6.3x86_64)

[root@vboxchap17]#uname-r
2.6.32-279.el6.x86_64
[root@vboxchap17]#ll
total8
-rw-r--r--1rootroot538Dec119:37Makefile
-rw-r--r--1rootroot2155Dec119:37test_paramed_km.c
[root@vboxchap17]#make<--编译内核
make-C/usr/src/kernels/2.6.32-279.el6.x86_64M=/root/chap17modules
make[1]:Enteringdirectory`/usr/src/kernels/2.6.32-279.el6.x86_64'
CC[M]/root/chap17/test_paramed_km.o
LD[M]/root/chap17/paramed_km.o
Buildingmodules,stage2.
MODPOST1modules
CC/root/chap17/paramed_km.mod.o
LD[M]/root/chap17/paramed_km.ko.unsigned
NOSIGN[M]/root/chap17/paramed_km.ko
make[1]:Leavingdirectory`/usr/src/kernels/2.6.32-279.el6.x86_64'
rm-rfmodules.orderModule.symvers.*.cmd*.o*.mod.c.tmp_versions*.unsigned
[root@vboxchap17]#ll<--编译内核后,多了paramed_km.ko文件
total124
-rw-r--r--1rootroot538Dec119:37Makefile
-rw-r--r--1rootroot118352Dec119:37paramed_km.ko
-rw-r--r--1rootroot2155Dec119:37test_paramed_km.c

<--通过modinfo命令可以查看对内核模块参数的注释
[root@vboxchap17]#modinfoparamed_km.ko
filename:paramed_km.ko
license:DualBSD/GPL
srcversion:C52F97687B033738742800D
depends:
vermagic:2.6.32-279.el6.x86_64SMPmod_unloadmodversions
parm:stu_id:学生ID,默认为0(int)
parm:stu_name_out:charp
parm:stu_name_in:学生姓名,默认为defaultname
parm:arr_out:arrayofint
parm:arr_in:数组参数,默认为空

<--3个参数都是默认的
[root@vboxchap17]#insmodparamed_km.ko
[root@vboxchap17]#rmmodparamed_km.ko
[root@vboxchap17]#dmesg|tail-16<--结果中显示2个默认参数,第3个数组参数默认为空,所以不显示
*************************
test_paramed_kmisinited!
*************************
alloconestudent....
**********studentinfo***********
studentidis:0
studentnameis:defaultname
*********************************
*************************
test_paramed_kmisexited!
*************************

<--3个参数都被设置
[root@vboxchap17]#insmodparamed_km.kostu_id=100stu_name_out=mynamearr_out=1,2,3,4,5
[root@vboxchap17]#rmmodparamed_km.ko
[root@vboxchap17]#dmesg|tail-21
*************************
test_paramed_kmisinited!
*************************
alloconestudent....
**********studentinfo***********
studentidis:100
studentnameis:myname
*********************************
arr_value[0]:1
arr_value[1]:2
arr_value[2]:3
arr_value[3]:4
arr_value[4]:5
*************************
test_paramed_kmisexited!
*************************



2.2内核模块的位置

2.2.1.内核代码外

上面的例子,以及之前博客中内核模块的例子都是把模块代码放在内核之外来运行的。

2.2.2.内核代码中

内核模块的代码也可以直接放到内核代码树中。

如果你开发了一种驱动,并且希望被加入到内核中,那么,可以在编写驱动的时候就将完成此驱动功能的内核模块加到内核代码树中driver的相应位置。

将内核模块加入内核代码树中之后,不需要另外写Makefile,修改内核代码树中的已有的Makefile就行。

比如,写了一个某种字符设备相关的驱动,可以把它加到内核代码的/drivers/char下,

同时修改/drivers/char下的Makefie,仿照里面已有的内容,增加新驱动的编译相关内容即可。

之后,在编译内核的时候会将新的驱动以内核模块的方式编译出来。


2.3内核模块相关操作

2.3.1.模块安装

makemodules_install<--把随内核编译出来的模块安装到合适的目录中(/lib/modules/version/kernel)


2.3.2.模块依赖性

linux中自动生产模块依赖性的命令:

depmod<--产生内核依赖关系信息
depmod-A<--只为新模块生成依赖信息(速度更快)


2.3.3.模块的载入

内核模块实验时已经用过:

insmodmodule.ko

<--推荐使用以下的命令,自动加载依赖的模块
modprobemodule[moduleparameters]


2.3.4.模块的卸载

内核模块实验时已经用过:

rmmodmodule.ko

<--推荐使用以下的命令,自动卸载依赖的模块
modprobe-rmodule


2.3.5.模块导出符号表

内核模块被载入后,就动态的加载到内核中,为了能让其他内核模块使用其功能,需要将其中函数导出。

内核模块中导出函数的方法:

EXPORT_SYMBOL(函数名)<--接在要导出的函数后面即可
EXPORT_SYMBOL_GPL(函数名)<--和EXPORT_SYMBOL一样,区别在于只对标记为GPL协议的模块可见


内核模块导出符号表示例:

+首先编写一个导出函数的模块module_A:test_module_A.c

#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>

MODULE_LICENSE("DualBSD/GPL");

staticinttest_export_A_init(void)
{
/*进入内核模块*/
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"ENTRYtest_export_A!\n");
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"\n\n\n\n\n");
return0;
}

staticvoidtest_export_A_exit(void)
{
/*退出内核模块*/
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"EXITtest_export_A!\n");
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"\n\n\n\n\n");
}

/*要导出的函数*/
intexport_add10(intparam)
{
printk(KERN_ALERT"paramfromothermoduleis:%d\n",param);
returnparam+10;
}
EXPORT_SYMBOL(export_add10);

module_init(test_export_A_init);
module_exit(test_export_A_exit);


test_module_A.c的Makefile

#mustcomplileoncustomizekernel
obj-m+=export_A.o
export_A-objs:=test_export_A.o

#generatethepath
CURRENT_PATH:=$(shellpwd)
#thecurrentkernelversionnumber
LINUX_KERNEL:=$(shelluname-r)
#theabsolutepath
LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
#complieobject
all:
make-C$(LINUX_KERNEL_PATH)M=$(CURRENT_PATH)modules
rm-rfmodules.order.*.cmd*.o*.mod.c.tmp_versions*.unsigned
#clean
clean:
rm-rfmodules.orderModule.symvers.*.cmd*.o*.mod.c*.ko.tmp_versions*.unsigned


+再编写一个内核模块module_B,使用module_A导出的函数:test_module_B.c

#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>

MODULE_LICENSE("DualBSD/GPL");

externintexport_add10(int);//这个函数是module_A中实现的

staticinttest_export_B_init(void)
{
/*进入内核模块*/
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"ENTRYtest_export_B!\n");
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"\n\n\n\n\n");

/*调用module_A导出的函数*/
printk(KERN_ALERT"resultfromtest_export_A:%d\n",export_add10(100));

return0;
}

staticvoidtest_export_B_exit(void)
{
/*退出内核模块*/
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"EXITtest_export_B!\n");
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"\n\n\n\n\n");
}

module_init(test_export_B_init);
module_exit(test_export_B_exit);


test_module_B.c的Makefile

#mustcomplileoncustomizekernel
obj-m+=export_B.o
export_B-objs:=test_export_B.o

#generatethepath
CURRENT_PATH:=$(shellpwd)
#thecurrentkernelversionnumber
LINUX_KERNEL:=$(shelluname-r)
#theabsolutepath
LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
#complieobject
all:
make-C$(LINUX_KERNEL_PATH)M=$(CURRENT_PATH)modules
rm-rfmodules.orderModule.symvers.*.cmd*.o*.mod.c.tmp_versions*.unsigned
#clean
clean:
rm-rfmodules.orderModule.symvers.*.cmd*.o*.mod.c*.ko.tmp_versions*.unsigned


+测试方法

1.将test_export_A.c和对应的Makefile拷贝到module_A文件夹中

2.将test_export_B.c和对应的Makefile拷贝到module_B文件夹中

3.编译module_A中的test_export_A.c

4.将编译module_A后生成的Module.symvers拷贝到module_B文件夹中

5.编译module_B中的test_export_B.c

6.先安装模块A,再安装模块B

7.dmesg查看log

8.用rmmod卸载模块B和模块A(注意卸载顺序,先卸载B再卸载A)

[root@vboxchap17]#ll
total8
drwxrwxr-x2rootroot4096Dec722:14module_A
drwxrwxr-x2rootroot4096Dec722:14module_B
[root@vboxchap17]#llmodule_A
total8
-rw-r--r--1rootroot517Dec721:58Makefile
-rw-r--r--1rootroot893Dec721:58test_export_A.c
[root@vboxchap17]#llmodule_B
total8
-rw-r--r--1rootroot532Dec721:58Makefile
-rw-r--r--1rootroot830Dec721:58test_export_B.c

[root@vboxchap17]#cdmodule_A/
[root@vboxmodule_A]#ll
total8
-rw-r--r--1rootroot517Dec721:58Makefile
-rw-r--r--1rootroot893Dec721:58test_export_A.c
[root@vboxmodule_A]#make
make-C/usr/src/kernels/2.6.32-279.el6.x86_64M=/root/chap17/module_Amodules
make[1]:Enteringdirectory`/usr/src/kernels/2.6.32-279.el6.x86_64'
CC[M]/root/chap17/module_A/test_export_A.o
LD[M]/root/chap17/module_A/export_A.o
Buildingmodules,stage2.
MODPOST1modules
CC/root/chap17/module_A/export_A.mod.o
LD[M]/root/chap17/module_A/export_A.ko.unsigned
NOSIGN[M]/root/chap17/module_A/export_A.ko
make[1]:Leavingdirectory`/usr/src/kernels/2.6.32-279.el6.x86_64'
rm-rfmodules.order.*.cmd*.o*.mod.c.tmp_versions*.unsigned
[root@vboxmodule_A]#ll
total120
-rw-r--r--1rootroot110452Dec722:31export_A.ko
-rw-r--r--1rootroot517Dec721:58Makefile
-rw-r--r--1rootroot69Dec722:31Module.symvers
-rw-r--r--1rootroot893Dec721:58test_export_A.c

[root@vboxmodule_A]#cd../module_B
[root@vboxmodule_B]#ll
total8
-rw-r--r--1rootroot532Dec721:58Makefile
-rw-r--r--1rootroot830Dec721:58test_export_B.c
[root@vboxmodule_B]#cp../module_A/Module.symvers.
[root@vboxmodule_B]#ll
total12
-rw-r--r--1rootroot532Dec721:58Makefile
-rw-r--r--1rootroot69Dec722:32Module.symvers
-rw-r--r--1rootroot830Dec721:58test_export_B.c
[root@vboxmodule_B]#make
make-C/usr/src/kernels/2.6.32-279.el6.x86_64M=/root/chap17/module_Bmodules
make[1]:Enteringdirectory`/usr/src/kernels/2.6.32-279.el6.x86_64'
CC[M]/root/chap17/module_B/test_export_B.o
LD[M]/root/chap17/module_B/export_B.o
Buildingmodules,stage2.
MODPOST1modules
CC/root/chap17/module_B/export_B.mod.o
LD[M]/root/chap17/module_B/export_B.ko.unsigned
NOSIGN[M]/root/chap17/module_B/export_B.ko
make[1]:Leavingdirectory`/usr/src/kernels/2.6.32-279.el6.x86_64'
rm-rfmodules.orderModule.symvers.*.cmd*.o*.mod.c.tmp_versions*.unsigned
[root@vboxmodule_B]#ll
total116
-rw-r--r--1rootroot108596Dec722:32export_B.ko
-rw-r--r--1rootroot532Dec721:58Makefile
-rw-r--r--1rootroot830Dec721:58test_export_B.c

[root@vboxmodule_B]#insmod../module_A/export_A.ko
[root@vboxmodule_B]#insmodexport_B.ko
[root@vboxmodule_B]#dmesg|tail-18
*************************
ENTRYtest_export_A!
*************************

*************************
ENTRYtest_export_B!
*************************

paramfromothermoduleis:100
resultfromtest_export_A:110

[root@vboxmodule_B]#rmmodexport_B
[root@vboxmodule_B]#rmmodexport_A


注:

1.必须把编译模块A后生成的Module.symvers拷贝到module_B中再编译模块B,否在模块B找不到模块A导出的函数

2.先安装模块A,再安装模块B。

3.先卸载模块B,再卸载模块A。

4.安装卸载如果不按照上面的顺序,会有错误提示,大家可以试试看。

5.我实验的系统是CentOS6.3x86_64


3.内核对象

2.6内核中增加了一个引人注目的新特性--统一设备模型(devicemodel)。

统一设备模型的最初动机是为了实现智能的电源管理,linux内核为了实现智能电源管理,需要建立表示系统中所有设备拓扑关系的树结构,

这样在关闭电源时,可以从树的节点开始关闭。

实现了统一设备模型之后,还给内核带来了如下的好处:

1.代码重复最小化(统一处理的东西多了)

2.可以列举系统中所有设备,观察它们的状态,并查看它们连接的总线

3.可以将系统中的全部设备以树的形式完整,有效的展示出来--包括所有总线和内部连接

4.可以将设备和其对应的驱动联系起来,反之亦然

5.可以将设备按照类型加以归类,无需理解物理设备的拓扑结构

6.可以沿设备树的叶子向其根的反向依次遍历,以保证能以正确的顺序关闭设备电源


3.1kobject简介

统一设备模型的核心部分就是kobject,通过下面对kobject结构体的介绍,可以大致了解它是如何使得各个物理设备能够以树结构的形式组织起来的。

3.1.1.kobject

kobject的定义在<linux/kobject.h>中

structkobject{
constchar*name;/*kobject名称*/
structlist_headentry;/*kobject链表*/
structkobject*parent;/*kobject的父对象,说明kobject是有层次结构的*/
structkset*kset;/*kobject的集合,接下来有详细介绍*/
structkobj_type*ktype;/*kobject的类型,接下来有详细介绍*/
structsysfs_dirent*sd;/*在sysfs中,这个结构体表示kobject的一个inode结构体,sysfs之后也会介绍*/
structkrefkref;/*提供kobject的引用计数*/
/*一些标志位*/
unsignedintstate_initialized:1;
unsignedintstate_in_sysfs:1;
unsignedintstate_add_uevent_sent:1;
unsignedintstate_remove_uevent_sent:1;
unsignedintuevent_suppress:1;
};


kobject本身不代表什么实际的内容,一般都是嵌在其他数据结构中来发挥作用。(感觉有点像内核数据结构链表的节点)

比如<linux/cdev.h>中的structcdev(表示字符设备的struct)

structcdev{
structkobjectkobj;/*嵌在cdev中的kobject*/
structmodule*owner;
conststructfile_operations*ops;
structlist_headlist;
dev_tdev;
unsignedintcount;
};


cdev中嵌入了kobject之后,就可以通过cdev->kboj.parent建立cdev之间的层次关系,通过cdev->kobj.entry获取关联的所有cdev设备等。

总之,嵌入了kobject之后,cdev设备之间就有了树结构关系,cdev设备和其他设备之间也有可层次关系。

3.1.2.ktype

ktype是为了描述一族的kobject所具有的普遍属性,也就是将这一族的kobject的属性统一定义一下,避免每个kobject分别定义。

(感觉有点像面向对象语言中的抽象类或者接口)

ktype的定义很简单,参见<linux/kobject.h>

structkobj_type{
void(*release)(structkobject*kobj);/*kobject的引用计数降到0时触发的析构函数,负责释放和清理内存的工作*/
structsysfs_ops*sysfs_ops;/*sysfs操作相关的函数*/
structattribute**default_attrs;/*kobject相关的默认属性*/
};


3.1.3.kset

kset是kobject对象的集合体,可以所有相关的kobject置于一个kset之中,比如所有“块设备”可以放在一个表示块设备的kset中。

kset的定义也不复杂,参见<linux/kobject.h>

structkset{
structlist_headlist;/*表示kset中所有kobject的链表*/
spinlock_tlist_lock;/*用于保护list的自旋锁*/
structkobjectkobj;/*kset中嵌入的一个kobject,使得kset也可以表现的像一样kobject一样*/
structkset_uevent_ops*uevent_ops;/*处理kset中kobject的热插拔事件提供了与用户空间热插拔进行通信的机制*/
};






3.1.4.kobject,ktype和kset之间的关系

这3个概念中,kobject是最基本的。kset和ktype是为了将kobject进行分类,以便将共通的处理集中处理,从而减少代码量,也增加维护性。

这里kset和ktype都是为了将kobject进行分类,为什么会有2中分类呢?

从整个内核的代码来看,其实kset的数量是多于ktype的数量的,同一种ktype的kobject可以位于不同的kset中。

做个不是很恰当的比喻,如果把kobject比作一个人的话,kset相当于一个一个国家,ktype则相当于人种(比如黄种人,白种人等等)。

人种的类型只有少数几个,但是国家确有很多,人种的目的是描述一群人的共通属性,而国家的目地则是为了管理一群人。

同样,ktype侧重于描述,kset侧重于管理。

3.1.5.kref

kref记录kobject被引用的次数,当引用计数降到0的时候,则执行release函数释放相关资源。

kref的定义参见:<linux/kref.h>

structkref{
atomic_trefcount;/*只有一个表示引用计数的属性,atomic_t类型表示对它的访问是原子操作*/
};

voidkref_set(structkref*kref,intnum);/*设置引用计数的值*/
voidkref_init(structkref*kref);/*初始化引用计数*/
voidkref_get(structkref*kref);/*增加引用计数+1*/
intkref_put(structkref*kref,void(*release)(structkref*kref));/*减少引用计数-1当减少到0时,释放相应资源*/


上面这些函数的具体实现可以参考内核代码lib/kref.c


3.2kobject操作

kobject的相关都在<linux/kobject.h>中定义了,主要由以下一些:

externvoidkobject_init(structkobject*kobj,structkobj_type*ktype);/*初始化一个kobject,设置它是哪种ktype*/
externint__must_checkkobject_add(structkobject*kobj,
structkobject*parent,
constchar*fmt,...);/*设置此kobject的parent,将此kobject加入到现有对象层次结构中*/
externint__must_checkkobject_init_and_add(structkobject*kobj,
structkobj_type*ktype,
structkobject*parent,
constchar*fmt,...);/*初始化kobject,完成kobject_add函数的功能*/

externvoidkobject_del(structkobject*kobj);/*将此kobject从现有对象层次结构中取消*/

externstructkobject*__must_checkkobject_create(void);/*创建一个kobject,比kobject_init更常用*/
externstructkobject*__must_checkkobject_create_and_add(constchar*name,
structkobject*parent);/*创建一个kobject,并将其加入到现有对象层次结构中*/

externint__must_checkkobject_rename(structkobject*,constchar*new_name);/*改变kobject的名称*/
externint__must_checkkobject_move(structkobject*,structkobject*);/*给kobject设置新的parent*/

externstructkobject*kobject_get(structkobject*kobj);/*增加kobject的引用计数+1*/
externvoidkobject_put(structkobject*kobj);/*减少kobject的引用计数-1*/

externchar*kobject_get_path(structkobject*kobj,gfp_tflag);/*生成并返回与给定的一个kobj和kset相关联的路径*/


上面这些函数的具体实现可以参考内核代码lib/kobject.c


4.sysfs

sysfs是一个处于内存中的虚拟文件系统,它提供了kobject对象层次结构的视图。

可以用下面这个命令来查看/sys的结构

tree/sys#显示所有目录和文件
或者
tree-L1/sys#只显示一层目录



4.1sysfs中的kobject

既然sysfs是kobject的视图,那么内核中肯定提供了在sysfs中操作kobject的API。

kobject结构体中与sysfs关联的字段就是「structsysfs_dirent*sd;」这是一个目录项结构,它表示kobject在sysfs中的位置。

关于目录项,可以参考:《Linux内核设计与实现》读书笔记(十三)-虚拟文件系统

4.1.1.sysfs中添加和删除kobject非常简单,就是上面介绍的kobject操作中提到的

externint__must_checkkobject_add(structkobject*kobj,
structkobject*parent,
constchar*fmt,...);/*设置此kobject的parent,将此kobject加入到现有对象层次结构中*/
externint__must_checkkobject_init_and_add(structkobject*kobj,
structkobj_type*ktype,
structkobject*parent,
constchar*fmt,...);/*初始化kobject,完成kobject_add函数的功能*/

externvoidkobject_del(structkobject*kobj);/*将此kobject从现有对象层次结构中取消*/
.....
3694c
.等等


添加了kobject之后,只会增加文件夹,不会增加文件。

因为kobject在sysfs中就是映射成一个文件夹。

添加删除kobject的示例代码如下:

/******************************************************************************
*@file:test_kobject.c
*@author:wangyubin
*@date:TueDec2409:49:532013
*
*@brief:测试kobject的创建和删除
*history:init
******************************************************************************/

#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/kobject.h>

MODULE_LICENSE("DualBSD/GPL");

structkobject*kobj=NULL;

staticinttest_kobject_init(void)
{
/*初始化kobject,并加入到sysfs中*/
kobj=kobject_create_and_add("test_kobject",NULL);

/*进入内核模块*/
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"test_kobjectisinited!\n");
printk(KERN_ALERT"*************************\n");

return0;
}

staticvoidtest_kobject_exit(void)
{
/*如果kobj不为空,则将其从sysfs中删除*/
if(kobj!=NULL)
kobject_del(kobj);
/*退出内核模块*/
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"test_kobjectisexited!\n");
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"\n\n\n\n\n");
}

module_init(test_kobject_init);
module_exit(test_kobject_exit);


对应的Makefile

#mustcomplileoncustomizekernel
obj-m+=mykobject.o
mykobject-objs:=test_kobject.o

#generatethepath
CURRENT_PATH:=$(shellpwd)
#thecurrentkernelversionnumber
LINUX_KERNEL:=$(shelluname-r)
#theabsolutepath
LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
#complieobject
all:
make-C$(LINUX_KERNEL_PATH)M=$(CURRENT_PATH)modules
rm-rfmodules.orderModule.symvers.*.cmd*.o*.mod.c.tmp_versions*.unsigned
#clean
clean:
rm-rfmodules.orderModule.symvers.*.cmd*.o*.mod.c*.ko.tmp_versions*.unsigned


测试方法:(我使用的测试系统是:Centos6.5x86)

[root@localhosttest_kobject]#ll<--开始时只有2个文件,一个测试代码,一个Makefile
total8
-rw-r--r--1rootroot533Dec2409:44Makefile
-rw-r--r--1rootroot908Dec2409:44test_kobject.c
[root@localhosttest_kobject]#make<--编译用于测试的内核模块
make-C/usr/src/kernels/2.6.32-431.el6.i686M=/home/wyb/chap17/test_kobjectmodules
make[1]:Enteringdirectory`/usr/src/kernels/2.6.32-431.el6.i686'
CC[M]/home/wyb/chap17/test_kobject/test_kobject.o
LD[M]/home/wyb/chap17/test_kobject/mykobject.o
Buildingmodules,stage2.
MODPOST1modules
CC/home/wyb/chap17/test_kobject/mykobject.mod.o
LD[M]/home/wyb/chap17/test_kobject/mykobject.ko.unsigned
NOSIGN[M]/home/wyb/chap17/test_kobject/mykobject.ko
make[1]:Leavingdirectory`/usr/src/kernels/2.6.32-431.el6.i686'
rm-rfmodules.orderModule.symvers.*.cmd*.o*.mod.c.tmp_versions*.unsigned
[root@localhosttest_kobject]#ll<--编译后多出来的一个内核模块***.ko
total100
-rw-r--r--1rootroot533Dec2409:44Makefile
-rw-r--r--1rootroot91902Dec2409:54mykobject.ko
-rw-r--r--1rootroot908Dec2409:44test_kobject.c
[root@localhosttest_kobject]#ll/sys/<--安装内核模块mykobject.ko之前的sysfs结构
total0
drwxr-xr-x2rootroot0Dec2409:28block
drwxr-xr-x17rootroot0Dec2409:28bus
drwxr-xr-x40rootroot0Dec2409:28class
drwxr-xr-x4rootroot0Dec2409:28dev
drwxr-xr-x12rootroot0Dec2409:28devices
drwxr-xr-x4rootroot0Dec2409:28firmware
drwxr-xr-x3rootroot0Dec2409:28fs
drwxr-xr-x2rootroot0Dec2409:44hypervisor
drwxr-xr-x5rootroot0Dec2409:28kernel
drwxr-xr-x84rootroot0Dec2409:46module
drwxr-xr-x2rootroot0Dec2409:44power
[root@localhosttest_kobject]#insmodmykobject.ko<--安装内核模块
[root@localhosttest_kobject]#ll/sys/<--安装后,sysfs中多了一个文件夹test_kobject
total0
drwxr-xr-x2rootroot0Dec2409:28block
drwxr-xr-x17rootroot0Dec2409:28bus
drwxr-xr-x40rootroot0Dec2409:28class
drwxr-xr-x4rootroot0Dec2409:28dev
drwxr-xr-x12rootroot0Dec2409:28devices
drwxr-xr-x4rootroot0Dec2409:28firmware
drwxr-xr-x3rootroot0Dec2409:28fs
drwxr-xr-x2rootroot0Dec2409:44hypervisor
drwxr-xr-x5rootroot0Dec2409:28kernel
drwxr-xr-x85rootroot0Dec2409:54module
drwxr-xr-x2rootroot0Dec2409:44power
drwxr-xr-x2rootroot0Dec2409:55test_kobject
[root@localhosttest_kobject]#ll/sys/test_kobject/<--追加kobject只能增加文件夹,文件夹中是没有文件的
total0
[root@localhosttest_kobject]#rmmodmykobject.ko<--卸载内核模块
[root@localhosttest_kobject]#ll/sys/<--卸载后,sysfs中的文件夹test_kobject也消失了
total0
drwxr-xr-x2rootroot0Dec2409:28block
drwxr-xr-x17rootroot0Dec2409:28bus
drwxr-xr-x40rootroot0Dec2409:28class
drwxr-xr-x4rootroot0Dec2409:28dev
drwxr-xr-x12rootroot0Dec2409:28devices
drwxr-xr-x4rootroot0Dec2409:28firmware
drwxr-xr-x3rootroot0Dec2409:28fs
drwxr-xr-x2rootroot0Dec2409:44hypervisor
drwxr-xr-x5rootroot0Dec2409:28kernel
drwxr-xr-x84rootroot0Dec2409:55module
drwxr-xr-x2rootroot0Dec2409:44power


4.1.2.sysfs中添加文件

kobject是映射成sysfs中的目录,那sysfs中的文件是什么呢?

其实sysfs中的文件就是kobject的属性,属性的来源有2个:

+默认属性::kobject所关联的ktype中的default_attrs字段

默认属性default_attrs的类型是结构体structattribute,定义在<linux/sysfs.h>

structattribute{
constchar*name;/*sysfs文件树中的文件名*/
structmodule*owner;/*x86体系结构中已经不再继续使用了,可能在其他体系结构中还会使用*/
mode_tmode;/*sysfs中该文件的权限*/
};


ktype中的default_attrs字段(即默认属性)描述了sysfs中的文件,还有一个字段sysfs_ops则描述了如何使用默认属性。

structsysfs_ops的定义也在<linux/sysfs.h>

structsysfs_ops{
/*在读sysfs文件时该方法被调用*/
ssize_t(*show)(structkobject*kobj,structattribute*attr,char*buffer);
/*在写sysfs文件时该方法被调用*/
ssize_t(*store)(structkobject*kobj,structattribute*attr,constchar*buffer,size_tsize);
};


show方法在读取sysfs中文件时调用,它会拷贝attr提供的属性到buffer指定的缓冲区

store方法在写sysfs中文件时调用,它会从buffer中读取size字节的数据到attr提供的属性中

增加默认属性的示例代码

/******************************************************************************
*@file:test_kobject_default_attr.c
*@author:wangyubin
*@date:TueDec2410:28:092013
*
*@brief:测试kobject的默认属性的创建和删除
*history:init
******************************************************************************/

#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/kobject.h>
#include<linux/sysfs.h>

MODULE_LICENSE("DualBSD/GPL");

staticvoidmyobj_release(structkobject*);
staticssize_tmy_show(structkobject*,structattribute*,char*);
staticssize_tmy_store(structkobject*,structattribute*,constchar*,size_t);

/*自定义的结构体,2个属性,并且嵌入了kobject*/
structmy_kobj
{
intival;
char*cname;
structkobjectkobj;
};

staticstructmy_kobj*myobj=NULL;

/*my_kobj的属性ival所对应的sysfs中的文件,文件名val*/
staticstructattributeval_attr={
.name="val",
.owner=NULL,
.mode=0666,
};

/*my_kobj的属性cname所对应的sysfs中的文件,文件名name*/
staticstructattributename_attr={
.name="name",
.owner=NULL,
.mode=0666,
};

staticinttest_kobject_default_attr_init(void)
{
structattribute*myattrs[]={NULL,NULL,NULL};
structsysfs_ops*myops=NULL;
structkobj_type*mytype=NULL;

/*初始化myobj*/
myobj=kmalloc(sizeof(structmy_kobj),GFP_KERNEL);
if(myobj==NULL)
return-ENOMEM;

/*配置文件val的默认值*/
myobj->ival=100;
myobj->cname="test";

/*初始化ktype*/
mytype=kmalloc(sizeof(structkobj_type),GFP_KERNEL);
if(mytype==NULL)
return-ENOMEM;

/*增加2个默认属性文件*/
myattrs[0]=&val_attr;
myattrs[1]=&name_attr;

/*初始化ktype的默认属性和析构函数*/
mytype->release=myobj_release;
mytype->default_attrs=myattrs;

/*初始化ktype中的sysfs*/
myops=kmalloc(sizeof(structsysfs_ops),GFP_KERNEL);
if(myops==NULL)
return-ENOMEM;

myops->show=my_show;
myops->store=my_store;
mytype->sysfs_ops=myops;

/*初始化kobject,并加入到sysfs中*/
memset(&myobj->kobj,0,sizeof(structkobject));/*这一步非常重要,没有这一步initkobject会失败*/
if(kobject_init_and_add(&myobj->kobj,mytype,NULL,"test_kobj_default_attr"))
kobject_put(&myobj->kobj);

printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"test_kobject_default_attrisinited!\n");
printk(KERN_ALERT"*************************\n");

return0;
}

staticvoidtest_kobject_default_attr_exit(void)
{
kobject_del(&myobj->kobj);
kfree(myobj);

/*退出内核模块*/
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"test_kobject_default_attrisexited!\n");
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"\n\n\n\n\n");
}

staticvoidmyobj_release(structkobject*kobj)
{
printk(KERN_ALERT,"releasekobject");
kobject_del(kobj);
}

/*读取属性文件val或者name时会执行此函数*/
staticssize_tmy_show(structkobject*kboj,structattribute*attr,char*buf)
{
printk(KERN_ALERT"SHOW--attr-name:[%s]\n",attr->name);
if(strcmp(attr->name,"val")==0)
returnsprintf(buf,"%d\n",myobj->ival);
else
returnsprintf(buf,"%s\n",myobj->cname);
}

/*写入属性文件val或者name时会执行此函数*/
staticssize_tmy_store(structkobject*kobj,structattribute*attr,constchar*buf,size_tlen)
{
printk(KERN_ALERT"STORE--attr-name:[%s]\n",attr->name);
if(strcmp(attr->name,"val")==0)
sscanf(buf,"%d\n",&myobj->ival);
else
sscanf(buf,"%s\n",myobj->cname);
returnlen;
}

module_init(test_kobject_default_attr_init);
module_exit(test_kobject_default_attr_exit);


对应的Makefile如下:

#mustcomplileoncustomizekernel
obj-m+=mykobject_with_default_attr.o
mykobject_with_default_attr-objs:=test_kobject_default_attr.o

#generatethepath
CURRENT_PATH:=$(shellpwd)
#thecurrentkernelversionnumber
LINUX_KERNEL:=$(shelluname-r)
#theabsolutepath
LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
#complieobject
all:
make-C$(LINUX_KERNEL_PATH)M=$(CURRENT_PATH)modules
rm-rfmodules.orderModule.symvers.*.cmd*.o*.mod.c.tmp_versions*.unsigned
#clean
clean:
rm-rfmodules.orderModule.symvers.*.cmd*.o*.mod.c*.ko.tmp_versions*.unsigned


测试方法:(我使用的测试系统是:Centos6.5x86)

############################编译########################################################
[root@localhosttest_kobject_defalt_attr]#ll
total8
-rw-r--r--1rootroot582Dec2415:02Makefile
-rw-r--r--1rootroot4032Dec2416:58test_kobject_default_attr.c
[root@localhosttest_kobject_defalt_attr]#make
make-C/usr/src/kernels/2.6.32-431.el6.i686M=/home/wyb/chap17/test_kobject_defalt_attrmodules
make[1]:Enteringdirectory`/usr/src/kernels/2.6.32-431.el6.i686'
CC[M]/home/wyb/chap17/test_kobject_defalt_attr/test_kobject_default_attr.o
/home/wyb/chap17/test_kobject_defalt_attr/test_kobject_default_attr.c:Infunction‘myobj_release’:
/home/wyb/chap17/test_kobject_defalt_attr/test_kobject_default_attr.c:109:warning:toomanyargumentsforformat
LD[M]/home/wyb/chap17/test_kobject_defalt_attr/mykobject_with_default_attr.o
Buildingmodules,stage2.
MODPOST1modules
CC/home/wyb/chap17/test_kobject_defalt_attr/mykobject_with_default_attr.mod.o
LD[M]/home/wyb/chap17/test_kobject_defalt_attr/mykobject_with_default_attr.ko.unsigned
NOSIGN[M]/home/wyb/chap17/test_kobject_defalt_attr/mykobject_with_default_attr.ko
make[1]:Leavingdirectory`/usr/src/kernels/2.6.32-431.el6.i686'
rm-rfmodules.orderModule.symvers.*.cmd*.o*.mod.c.tmp_versions*.unsigned
[root@localhosttest_kobject_defalt_attr]#ll
total104
-rw-r--r--1rootroot582Dec2415:02Makefile
-rw-r--r--1rootroot96805Dec2416:58mykobject_with_default_attr.ko
-rw-r--r--1rootroot4032Dec2416:58test_kobject_default_attr.c

############################安装########################################################
[root@localhosttest_kobject_defalt_attr]#insmodmykobject_with_default_attr.ko
[root@localhosttest_kobject_defalt_attr]#ll/sys/<--kobject对应的文件夹
total0
drwxr-xr-x2rootroot0Dec2415:50block
drwxr-xr-x17rootroot0Dec2415:50bus
drwxr-xr-x40rootroot0Dec2415:50class
drwxr-xr-x4rootroot0Dec2415:50dev
drwxr-xr-x12rootroot0Dec2415:50devices
drwxr-xr-x4rootroot0Dec2415:50firmware
drwxr-xr-x3rootroot0Dec2415:50fs
drwxr-xr-x2rootroot0Dec2416:06hypervisor
drwxr-xr-x5rootroot0Dec2415:50kernel
drwxr-xr-x85rootroot0Dec2416:59module
drwxr-xr-x2rootroot0Dec2416:06power
drwxr-xr-x2rootroot0Dec2416:59test_kobj_default_attr
[root@localhosttest_kobject_defalt_attr]#ll/sys/test_kobj_default_attr/<--kobject的2个属性文件
total0
-rw-rw-rw-1rootroot4096Dec2416:59name
-rw-rw-rw-1rootroot4096Dec2416:59val
[root@localhosttest_kobject_defalt_attr]#dmesg<--dmesg中只有初始化的信息
*************************
test_kobject_default_attrisinited!
*************************

############################读取属性文件###############################################
[root@localhosttest_kobject_defalt_attr]#cat/sys/test_kobj_default_attr/val<--属性值就是我们在测试代码中输入的值
100
[root@localhosttest_kobject_defalt_attr]#cat/sys/test_kobj_default_attr/name<--属性值就是我们在测试代码中输入的值
test
[root@localhosttest_kobject_defalt_attr]#dmesg<--dmesg中多了2条读取属性文件的log
SHOW--attr-name:[val]
SHOW--attr-name:[name]

############################写入属性文件################################################
[root@localhosttest_kobject_defalt_attr]#echo"200">/sys/test_kobj_default_attr/val<--val文件中写入200
[root@localhosttest_kobject_defalt_attr]#echo"abcdefg">/sys/test_kobj_default_attr/name<--name文件中写入adcdefg
[root@localhosttest_kobject_defalt_attr]#dmesg<--dmesg中又多了2条写入属性文件的log
STORE--attr-name:[val]
STORE--attr-name:[name]
[root@localhosttest_kobject_defalt_attr]#cat/sys/test_kobj_default_attr/val<--再次查看val文件中的值,已变为200
200
[root@localhosttest_kobject_defalt_attr]#cat/sys/test_kobj_default_attr/name<--再次查看name文件中的值,已变为abcdefg
abcdefg

############################卸载########################################################
[root@localhosttest_kobject_defalt_attr]#rmmodmykobject_with_default_attr.ko


注:参考博客Linux设备模型
(2)

+新属性::kobject自己定义的属性

一般来说,使用默认属性就足够了。因为具有共同ktype的kobject在本质上区别都不大,比如都是块设备的kobject,

它们使用默认的属性集合不但可以让事情简单,有助于代码合并,还可以使类似的对象在sysfs中的外观一致。

在一些特殊的情况下,kobject可能会需要自己特有的属性。内核也充分考虑到了这些情况,提供了创建或者删除新属性的方法。

在sysfs中文件的操作方法参见:fs/sysfs/file.c

/*文件的相关操作非常多,这里只列出创建文件和删除文件的方法*/

/**
*给kobj增加一个新的属性attr
*kobj对应sysfs中的一个文件夹,attr对应sysfs中的一个文件
*/
intsysfs_create_file(structkobject*kobj,conststructattribute*attr)
{
BUG_ON(!kobj||!kobj->sd||!attr);

returnsysfs_add_file(kobj->sd,attr,SYSFS_KOBJ_ATTR);

}

/**
*给kobj删除一个新的属性attr
*kobj对应sysfs中的一个文件夹,attr对应sysfs中的一个文件
*/
voidsysfs_remove_file(structkobject*kobj,conststructattribute*attr)
{
sysfs_hash_and_remove(kobj->sd,attr->name);
}


除了可以在sysfs中增加/删除的文件,还可以在sysfs中增加或者删除一个符号链接。

具体实现参见:fs/sysfs/symlink.c

/*下面只列出了创建和删除符号链接的方法,其他方法请参考symlink.c文件*/

/**
*在kobj对应的文件夹中创建一个符号链接指向target
*符号链接的名称就是name
*/
intsysfs_create_link(structkobject*kobj,structkobject*target,
constchar*name)
{
returnsysfs_do_create_link(kobj,target,name,1);
}

voidsysfs_remove_link(structkobject*kobj,constchar*name)
{
structsysfs_dirent*parent_sd=NULL;

if(!kobj)
parent_sd=&sysfs_root;
else
parent_sd=kobj->sd;

sysfs_hash_and_remove(parent_sd,name);
}


增加新的属性的示例代码(这里只演示了增加文件的方法,增加符号链接的方法与之类似)

/******************************************************************************
*@file:test_kobject_new_attr.c
*@author:wangyubin
*@date:TueDec2417:10:312013
*
*@brief:测试kobject中增加和删除新属性
*history:init
******************************************************************************/

#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/kobject.h>
#include<linux/sysfs.h>

MODULE_LICENSE("DualBSD/GPL");

staticvoidmyobj_release(structkobject*);
staticssize_tmy_show(structkobject*,structattribute*,char*);
staticssize_tmy_store(structkobject*,structattribute*,constchar*,size_t);

/*自定义的结构体,其中嵌入了kobject,通过属性c_attr来控制增加或者删除新属性*/
structmy_kobj
{
intc_attr;/*值为0:删除新属性,值为1:增加新属性*/
intnew_attr;
structkobjectkobj;
};

staticstructmy_kobj*myobj=NULL;

/*my_kobj的属性c_attr所对应的sysfs中的文件,文件名c_attr*/
staticstructattributec_attr={
.name="c_attr",
.owner=NULL,
.mode=0666,
};

/*用于动态增加或者删除的新属性*/
staticstructattributenew_attr={
.name="new_attr",
.owner=NULL,
.mode=0666,
};

staticinttest_kobject_new_attr_init(void)
{
structattribute*myattrs[]={NULL,NULL};
structsysfs_ops*myops=NULL;
structkobj_type*mytype=NULL;

/*初始化myobj*/
myobj=kmalloc(sizeof(structmy_kobj),GFP_KERNEL);
if(myobj==NULL)
return-ENOMEM;

/*配置文件val的默认值*/
myobj->c_attr=0;

/*初始化ktype*/
mytype=kmalloc(sizeof(structkobj_type),GFP_KERNEL);
if(mytype==NULL)
return-ENOMEM;

/*增加1个默认属性文件*/
myattrs[0]=&c_attr;

/*初始化ktype的默认属性和析构函数*/
mytype->release=myobj_release;
mytype->default_attrs=myattrs;

/*初始化ktype中的sysfs*/
myops=kmalloc(sizeof(structsysfs_ops),GFP_KERNEL);
if(myops==NULL)
return-ENOMEM;

myops->show=my_show;
myops->store=my_store;
mytype->sysfs_ops=myops;

/*初始化kobject,并加入到sysfs中*/
memset(&myobj->kobj,0,sizeof(structkobject));/*这一步非常重要,没有这一步initkobject会失败*/
if(kobject_init_and_add(&myobj->kobj,mytype,NULL,"test_kobj_new_attr"))
kobject_put(&myobj->kobj);

printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"test_kobject_new_attrisinited!\n");
printk(KERN_ALERT"*************************\n");

return0;
}

staticvoidtest_kobject_new_attr_exit(void)
{
kobject_del(&myobj->kobj);
kfree(myobj);

/*退出内核模块*/
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"test_kobject_new_attrisexited!\n");
printk(KERN_ALERT"*************************\n");
printk(KERN_ALERT"\n\n\n\n\n");
}

staticvoidmyobj_release(structkobject*kobj)
{
printk(KERN_ALERT"releasekobject");
kobject_del(kobj);
}

/*读取属性文件c_attr或者new_attr时会执行此函数*/
staticssize_tmy_show(structkobject*kboj,structattribute*attr,char*buf)
{
printk(KERN_ALERT"SHOW--attr-name:[%s]\n",attr->name);
if(strcmp(attr->name,"c_attr")==0)
returnsprintf(buf,"%d\n",myobj->c_attr);
elseif(strcmp(attr->name,"new_attr")==0)
returnsprintf(buf,"%d\n",myobj->new_attr);

return0;
}

/*写入属性文件c_attr或者new_attr时会执行此函数*/
staticssize_tmy_store(structkobject*kobj,structattribute*attr,constchar*buf,size_tlen)
{
printk(KERN_ALERT"STORE--attr-name:[%s]\n",attr->name);
if(strcmp(attr->name,"c_attr")==0)
sscanf(buf,"%d\n",&myobj->c_attr);
elseif(strcmp(attr->name,"new_attr")==0)
sscanf(buf,"%d\n",&myobj->new_attr);

if(myobj->c_attr==1)/*创建新的属性文件*/
{
if(sysfs_create_file(kobj,&new_attr))
return-1;
else
myobj->new_attr=100;/*新属性文件的值默认设置为100*/
}

if(myobj->c_attr==0)/*删除新的属性文件*/
sysfs_remove_file(kobj,&new_attr);

returnlen;
}

module_init(test_kobject_new_attr_init);
module_exit(test_kobject_new_attr_exit);


对应的Makefile如下:

#mustcomplileoncustomizekernel
obj-m+=mykobject_with_new_attr.o
mykobject_with_new_attr-objs:=test_kobject_new_attr.o

#generatethepath
CURRENT_PATH:=$(shellpwd)
#thecurrentkernelversionnumber
LINUX_KERNEL:=$(shelluname-r)
#theabsolutepath
LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
#complieobject
all:
make-C$(LINUX_KERNEL_PATH)M=$(CURRENT_PATH)modules
rm-rfmodules.orderModule.symvers.*.cmd*.o*.mod.c.tmp_versions*.unsigned
#clean
clean:
rm-rfmodules.orderModule.symvers.*.cmd*.o*.mod.c*.ko.tmp_versions*.unsigned


测试方法:(我使用的测试系统是:Centos6.5x86)

根据测试代码,增加/删除新属性是根据默认属性c_attr的值来决定的。

c_attr设置为0时,删除新属性new_attr

c_attr设置为1时,新增新属性new_attr

############################编译,安装,卸载同测试默认属性时一样#######################
...省略...
############################动态增加新属性文件#########################################
[root@localhosttest_kobject_new_attr]#ll/sys/test_kobj_new_attr/<--默认没有新属性new_attr
total0
-rw-rw-rw-1rootroot4096Dec2418:47c_attr
[root@localhosttest_kobject_new_attr]#cat/sys/test_kobj_new_attr/c_attr<--c_attr的值为0
0
[root@localhosttest_kobject_new_attr]#echo"1">/sys/test_kobj_new_attr/c_attr<--c_attr的值设为1
[root@localhosttest_kobject_new_attr]#ll/sys/test_kobj_new_attr/<--增加了新属性new_attr
total0
-rw-rw-rw-1rootroot4096Dec2419:02c_attr
-rw-rw-rw-1rootroot4096Dec2419:02new_attr

############################动态删除属性文件###########################################
[root@localhosttest_kobject_new_attr]#echo"0">/sys/test_kobj_new_attr/c_attr<--c_attr的值为0
[root@localhosttest_kobject_new_attr]#ll/sys/test_kobj_new_attr/<--删除了新属性new_attr
total0
-rw-rw-rw-1rootroot4096Dec2419:03c_attr


4.1.3.sysfs相关约定

为了保持sysfs的干净和直观,在内核开发中涉及到sysfs相关内容时,需要注意以下几点:

+sysfs属性保证每个文件只导出一个值,该值为文本形式并且可以映射为简单的C类型

+sysfs中要以一个清晰的层次组织数据

+sysfs提供内核到用户空间的服务


4.2基于sysfs的内核事件

内核事件层也是利用kobject和sysfs来实现的,用户空间通过监控sysfs中kobject的属性的变化来异步的捕获内核中kobject发出的信号。

用户空间可以通过一种netlink的机制来获取内核事件。

内核空间向用户空间发送信号使用kobject_uevent()函数,具体参见:<linux/kobject.h>

intkobject_uevent(structkobject*kobj,enumkobject_actionaction);


下面用个小例子演示一些内核事件的实现原理:

4.2.1.内核模块安装或者删除时,会发送KOBJ_ADD或者KOBJ_REMOVE的消息

内核模块的代码就用上面最简单的那个例子test_kobject.c的代码即可

4.2.2.用户态程序:通过netlink机制来接收kobject的事件通知

/******************************************************************************
*@file:test_netlink_client.c
*@author:wangyubin
*@date:TueDec2419:48:542013
*
*@brief:通过netlink机制接收kobject发出的信号
*history:init
******************************************************************************/

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<asm/types.h>
#include<sys/socket.h>
#include<linux/netlink.h>

voidMonitorNetlinkUevent()
{
intsockfd;
structsockaddr_nlsa;
intlen;
charbuf[4096];
structioveciov;
structmsghdrmsg;
inti;

memset(&sa,0,sizeof(sa));
sa.nl_family=AF_NETLINK;
sa.nl_groups=NETLINK_KOBJECT_UEVENT;
sa.nl_pid=0;//getpid();bothisok
memset(&msg,0,sizeof(msg));
iov.iov_base=(void*)buf;
iov.iov_len=sizeof(buf);
msg.msg_name=(void*)&sa;
msg.msg_namelen=sizeof(sa);
msg.msg_iov=&iov;
msg.msg_iovlen=1;

sockfd=socket(AF_NETLINK,SOCK_RAW,NETLINK_KOBJECT_UEVENT);
if(sockfd==-1)
printf("socketcreatingfailed:%s\n",strerror(errno));
if(bind(sockfd,(structsockaddr*)&sa,sizeof(sa))==-1)
printf("binderror:%s\n",strerror(errno));
while(1){
memset(buf,0,sizeof(buf));
len=recvmsg(sockfd,&msg,0);
if(len<0){}
//printf("receiveerror\n");
elseif(len<32||len>sizeof(buf))
printf("invalidmessage");
for(i=0;i<len;i++)
if(*(buf+i)=='\0')
buf[i]='\n';
printf("received%dbytes\n%s\n",len,buf);
}
}

intmain(intargc,char*argv[])
{
MonitorNetlinkUevent();
return0;
}


注:代码是拷贝的《Linux设备节点创建》内核kobject上报uevent过滤规则中的用户态程序部分

4.2.3.测试方法:(我使用的测试系统是:Centos6.5x86)

############################编译并启动用户态程序(窗口1)###################################
[root@localhosttest_kobject_event]#ll
total4
-rw-r--r--1rootroot1846Dec2420:36test_netlink_client.c
[root@localhosttest_kobject_event]#gcc-otest_netlink_clienttest_netlink_client.c<--编译用户态程序
[root@localhosttest_kobject_event]#./test_netlink_client<--启动后等待内核kobject的事件到来

############################安装内核模块并查看用户态程序输出(窗口2)#######################
[root@localhosttest_kobject]#insmodmykobject.ko<--在窗口2中安装内核模块,窗口1中会接收到KOBJ_ADD信号
############################卸载内核模块并查看用户态程序输出(窗口2)#######################
[root@localhosttest_kobject]#rmmodmykobject.ko<--在窗口2中安装内核模块,窗口1中会接收到KOBJ_REMOVE信号



5.总结

kobject加sysfs给内核带来的好处不是三言两语能够说得清楚的,上面的例子也只是通过简单的使用来直观的感受一下kobject,例子本身没有什么实际意义。

最后一个例子中使用了netlink机制,我在之前的项目中使用过netlink来监控系统进程的I/O信息,对netlink有一些初步的了解。

但是例子中只是用来获取一下kobject的事件而已,这里主要为了说明kobject,netlink的相关内容以后有机会再补充。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: