模块
linux设备类型分为字符设备(cdev)、块设备(blkdev)、网络设备(netdev)。
字符设备是不可寻址的,仅提供对数据的流式访问,如键盘、鼠标等。通过称为“块设备节点”的特殊文件来进行访问,该设备通常被挂载为文件系统。
块设备是可寻址,支持对数据的随机访问,如硬盘、光碟等。可以直接通过文件接口访问设备节点来进行交互。
网络设备提供了对网络的访问,通过特定的协议(如IP),来进行通信。但该设备打破了“一切皆文件”的原则,通过套接字API来进行访问。
此外,其实还有一些伪设备,仅提供对内核功能的访问,设备驱动是虚拟的,如空设备/dev/null、内核随机数发生器/dev/random和/dev/urandom、零设备/dev/zeor等。
众所周知,linux内核是模块化组成的,它允许内存在运行时动态的向其中插入或删除代码。这些代码通过特定的格式组合在一个二进制中,称为模块,也就是我们常说的驱动模块。这是热插拔设备的基础,也给调试内核代码提供了便利。
先看一下最简单的模块代码:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int hello_init(void)
{
printk("hello module init\n");
return 0;
}
static void hello_exit(void)
{
printk("hello module exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
驱动的加载和卸载流程
[root@bogon drv_module]$ insmod hello.ko
[root@bogon drv_module]$ dmesg
hello module init
[root@bogon drv_module]$ rmmod hello
[root@bogon drv_module]$ dmesg
hello module init
hello module exit
[root@bogon drv_module]$
源码中
hello_init()和hello_exit()分别为驱动加载和卸载调用的钩子函数。
使用module_param(name,type,perm)给模块增加参数,如:
static int num;
module_param(num,int,0664);
加载驱动:insmod hello.ko num=10
name是变量量,也是参数名。
type是变量类型,可以是byte、short、ushort、int、uint、long、ulong、charp、bool,分别是字节型、短整型、无符号短整型、整型、无符号整型、长整型、无符号长整型、字符指针、布尔型。
perm指定了模块在sys文件系统下对应文件的权限。
类似的接口还有:
module_param_named(name, varible, type, perm) //name为外部可见的参数名称,varible为内部参数变量名
该宏用于外部参数名与内部变量名不一致的场景,如:
static int val;
module_param_named(num,val,int,0664)
加载命令:insmod hello.ko num=10
module_param_string(name, string, len, perm) //name为外部参数名称,string为内部数组名,len为数组长度
该宏用于将字符串参数拷贝到数组的场景,如:
char str[10];
module_param_string(name,str,10,0664);
加载命令:insmod hello.ko name="hello"
module_param_array(name, type, nump, perm) //name为外部参数名称及内部变量名,type为数据类型,nump是整型指针,用于存放数组项数
该宏用于参数是以逗号分开的列表,如:
static int arr[10];
static int len;
module_param_array(arr,int,&len,0664);
加载命令:insmod hello.ko arr=1,2,3,4
模块被加载后,只有被显式导出的外部函数才能被动态库调用。导出内核函数使用:EXPORT_SYMBOL()、EXPORT_SYMBOL_GPL()
void show(char *p)
{
printk(KERN_ERR "%s\n",p);
}
EXPORT_SYMBOL(show);
设备模型
2.6内核引入了一个新特性叫统一设备模型(device model),以树的形式构建了设备的拓朴结构,这便是sys文件系统。该设备树的核心三个结构体分别为:kobject、kobj_type、kset。
struct kobject {
const char *name; //对象名称,对应sys文件系统中一个目录
struct list_head entry; //kobject链表
struct kobject *parent; //父结点指针
struct kset *kset; //属于哪一个kset集合
struct kobj_type *ktype; //kobject的特性
struct sysfs_dirent *sd; //指向sysfs_dirent结构体,在sys中表示该kobject
struct kref kref; //引用计数
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
struct kobj_type {
void (*release)(struct kobject *kobj); //引用计数为0时的析构函数
const struct sysfs_ops *sysfs_ops; //包含sys文件的读写操作
struct attribute **default_attrs; //kobject的默认属性,每一个属性对应sys中的一个文件,属性名即文件名,属性值即文件内容
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
struct kset {
struct list_head list; //链表链接了所包含的kobject
spinlock_t list_lock;
struct kobject kobj; //表示该集合的基类
const struct kset_uevent_ops *uevent_ops; //用于处理集合中kobject对象的热插拔操作。
}
kobject类似于面向对象中的类,用于组织层次结构,实际对应sys文件系统中的一个目录或文件。提供了引用计数kref、父子关系和对象名称等。
kobj_type表示kobject所具有的普遍特性。
kset为kobject的集合。
一个OS自带的示例:
/*
* Sample kobject implementation
*
* Copyright (C) 2007 Novell Inc.
*
* Released under the GPL version 2 only.
*
*/
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/module.h>
#include <linux/init.h>
/*
* This module shows how to create a simple subdirectory in sysfs called
* /sys/kernel/kobject-example In that directory, 3 files are created:
* "foo", "baz", and "bar". If an integer is written to these files, it can be
* later read out of it.
*/
static int foo;
static int baz;
static int bar;
/*
* The "foo" file where a static variable is read from and written to.
*/
static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
return sprintf(buf, "%d\n", foo);
}
static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
sscanf(buf, "%du", &foo);
return count;
}
static struct kobj_attribute foo_attribute =
__ATTR(foo, 0666, foo_show, foo_store);
/*
* More complex function where we determine which variable is being accessed by
* looking at the attribute for the "baz" and "bar" files.
*/
static ssize_t b_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
int var;
if (strcmp(attr->attr.name, "baz") == 0)
var = baz;
else
var = bar;
return sprintf(buf, "%d\n", var); //将变量写入buf中,即会在cat文件内容时打印出来
}
static ssize_t b_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
int var;
sscanf(buf, "%du", &var); //将变量从buf中读出来,即表示读取用户输入的值
if (strcmp(attr->attr.name, "baz") == 0)
baz = var;
else
bar = var;
return count;
}
static struct kobj_attribute baz_attribute =
__ATTR(baz, 0666, b_show, b_store);
static struct kobj_attribute bar_attribute =
__ATTR(bar, 0666, b_show, b_store);
/*
* Create a group of attributes so that we can create and destroy them all
* at once.
*/
static struct attribute *attrs[] = {
&foo_attribute.attr,
&baz_attribute.attr,
&bar_attribute.attr,
NULL, /* need to NULL terminate the list of attributes */
};
/*
* An unnamed attribute group will put all of the attributes directly in
* the kobject directory. If we specify a name, a subdirectory will be
* created for the attributes with the directory being the name of the
* attribute group.
*/
static struct attribute_group attr_group = {
.attrs = attrs,
};
static struct kobject *example_kobj;
static int __init example_init(void)
{
int retval;
/*
* Create a simple kobject with the name of "kobject_example",
* located under /sys/kernel/
*
* As this is a simple directory, no uevent will be sent to
* userspace. That is why this function should not be used for
* any type of dynamic kobjects, where the name and number are
* not known ahead of time.
*/
example_kobj = kobject_create_and_add("kobject_example", kernel_kobj);
if (!example_kobj)
return -ENOMEM;
/* Create the files associated with this kobject */
retval = sysfs_create_group(example_kobj, &attr_group);
if (retval)
kobject_put(example_kobj);
return retval;
}
static void __exit example_exit(void)
{
kobject_put(example_kobj);
}
module_init(example_init);
module_exit(example_exit);
MODULE_LICENSE("GPL");
加载驱动后:在/sys/kenel目录下生成
kobject_example目录
[root@bogon kernel]$ find /sys/kernel/kobject_example/
/sys/kernel/kobject_example/
/sys/kernel/kobject_example/foo
/sys/kernel/kobject_example/baz
/sys/kernel/kobject_example/bar
总结以上代码,创建sys文件如下:
1、初始化ktype
初始化各属性值,定义属性名,实现其文件操作接口
2、初始化kobject
该步骤会将kobject的引用计数置1,所以释放的时候只需调用kobject_put()将kobject清0就可以了
3、将kobject导入sysfs
以上步骤涉及的接口有:
kobject_init() 初始化kobject
kobject_create() 创建kobject
kobject_add() 把kobject导入到sysfs
kobject_create_and_add() 相当于前面两个函数一起执行
kobject_get() 获取kobject的引用计数
kobject_put() 减少kobject的引用计数
kobject_del() 从sysfs删除一个kobject目录
sysfs_create_file() 创建新属性
sysfs_create_link() 创建符号链接
sysfs_create_group() 同时创建多个属性
sysfs_remove_file() 删除新属性
sysfs_remove_link() 删除符号链接