linux内核学习笔记之设备与模块

本文介绍了Linux内核中的设备类型,包括字符设备、块设备和网络设备,并详细讲解了设备驱动模块的加载和卸载,展示了简单的模块代码。此外,文章探讨了设备模型中的统一设备模型(device model),特别是sysfs在构建设备拓扑结构中的作用。最后,通过一个示例展示了如何在sysfs中创建设备文件及其相关操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

模块
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) 2004-2007 Greg Kroah-Hartman < greg@kroah.com >
* 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");
MODULE_AUTHOR("Greg Kroah-Hartman < greg@kroah.com >");
加载驱动后:在/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()              删除符号链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值