首页 Linux驱动学习(一)
文章
取消

Linux驱动学习(一)

一、前言

驱动程序是外界硬件设备、操作系统、用户之间通信的桥梁。

驱动程序基本上都是使用C语言完成,且处于内核空间。

现代Linux驱动程序基本都支持动态装载。

硬件设备主要分为:字符设备、块设备、网络设备,其驱动程序略有差别。

学习参考网址:

https://embetronicx.com/tutorials/linux/

二、第一个驱动程序

首先编写如下C程序。hello_world.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/module.h>
/*
** Module Init function
*/
static int __init hello_world_init(void)
{
    printk(KERN_INFO "Welcome to EmbeTronicX\n");
    printk(KERN_INFO "This is the Simple Module\n");
    printk(KERN_INFO "Kernel Module Inserted Successfully...\n");
    return 0;
}
/*
** Module Exit function
*/
static void __exit hello_world_exit(void)
{
    printk(KERN_INFO "Kernel Module Removed Successfully...\n");
}

module_init(hello_world_init);
module_exit(hello_world_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("author");
MODULE_DESCRIPTION("Hello world driver");
MODULE_VERSION("1:1.0");

然后进行编译:

1
2
3
4
5
6
7
8
9
10
11
obj-m += hello_world.o

KDIR = /lib/modules/$(shell uname -r)/build

all:

  make -C $(KDIR)  M=$(shell pwd) modules

clean:

  make -C $(KDIR)  M=$(shell pwd) clean

随后进行驱动程序的加载:

1
sudo insmod hello_world_module.ko

查看驱动详细信息:

1
modinfo hello_world_module.ko

卸载:

1
sudo rmmod hello_world_module

三、传递参数

在驱动程序中,如果需要进行参数的定义,不仅要先像C语言一样声明变量、赋值等,还需要用到如下一些函数进行初始化或回调,如:

1
2
3
module_param() //定义一个变量
module_param_array()  //定义一个数组
module_param_cb()  //如果变量被更改之后进行回调

例程为:hello_world.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/module.h>
#include<linux/moduleparam.h>
 
int valueETX, arr_valueETX[4];
char *nameETX;
int cb_valueETX = 0;
 
module_param(valueETX, int, S_IRUSR|S_IWUSR);                      //integer value
module_param(nameETX, charp, S_IRUSR|S_IWUSR);                     //String
module_param_array(arr_valueETX, int, NULL, S_IRUSR|S_IWUSR);      //Array of integers
 
/*----------------------Module_param_cb()--------------------------------*/
int notify_param(const char *val, const struct kernel_param *kp)
{
        int res = param_set_int(val, kp); // Use helper for write variable
        if(res==0) {
                printk(KERN_INFO "Call back function called...\n");
                printk(KERN_INFO "New value of cb_valueETX = %d\n", cb_valueETX);
                return 0;
        }
        return -1;
}
 
const struct kernel_param_ops my_param_ops = 
{
        .set = &notify_param, // Use our setter ...
        .get = &param_get_int, // .. and standard getter
};
 
module_param_cb(cb_valueETX, &my_param_ops, &cb_valueETX, S_IRUGO|S_IWUSR );
/*-------------------------------------------------------------------------*/
/*
** Module init function
*/
static int __init hello_world_init(void)
{
        int i;
        printk(KERN_INFO "ValueETX = %d  \n", valueETX);
        printk(KERN_INFO "cb_valueETX = %d  \n", cb_valueETX);
        printk(KERN_INFO "NameETX = %s \n", nameETX);
        for (i = 0; i < (sizeof arr_valueETX / sizeof (int)); i++) {
                printk(KERN_INFO "Arr_value[%d] = %d\n", i, arr_valueETX[i]);
        }
        printk(KERN_INFO "Kernel Module Inserted Successfully...\n");
    return 0;
}
/*
** Module Exit function
*/
static void __exit hello_world_exit(void)
{
    printk(KERN_INFO "Kernel Module Removed Successfully...\n");
}
 
module_init(hello_world_init);
module_exit(hello_world_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author");
MODULE_DESCRIPTION("Passing parameters to my driver");
MODULE_VERSION("1.0");

然后进行编译:

1
2
3
4
5
6
7
8
9
obj-m += hello_world_module.o

KDIR = /lib/modules/$(shell uname -r)/build

all:
    make -C $(KDIR)  M=$(shell pwd) modules

clean:
    make -C $(KDIR)  M=$(shell pwd) clean

并在之后进行驱动程序的加载和变量初始化赋值:

1
sudo insmod hello_world_module.ko valueETX=14 nameETX="EmbeTronicX" arr_valueETX=100,102,104,106

此时如果需要更改变量值,则需要输入如下命令:

1
sudo sh -c "echo 13 > /sys/module/hello_world_module/parameters/cb_valueETX"

因为我们定义了回到回调函数notify_param(),所以可以得到输出:

1
2
Call back function called...
New value of cb_valueETX = 13

最后进行驱动程序的卸载:

1
sudo rmmod hello_world_module

四、设备驱动程序的主次数字

主数字和次数字:Major Number and Minor Number

主数字代表什么类型的设备,次数字代表设备号。

其可分为静态分配和动态分配两种方式:

静态分配:

如定义一个主数字为235,次数字为0的设备。要求的连续设备编号的总数为1。设备描述为MY_Dev。

//函数原型
int register_chrdev_region(dev_t first, unsigned int count, char *name);//成功返回0
//下面是一个例子
dev_t dev = MKDEV(235, 0);
register_chrdev_region(dev, 1, "MY_Dev");

动态分配:

//函数原型
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);

由于我们基本上不需要事先了解主数字编号,故通常使用动态分配,这将由系统自动进行分配。

释放:

1
void unregister_chrdev_region(dev_t first, unsigned int count);

静态分配的例程如下:hello_major_minor_static.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/module.h>
#include <linux/fs.h>

//creating the dev with our custom major and minor number
dev_t dev = MKDEV(235, 0);
/*
** Module Init function
*/
static int __init hello_world_init(void)
{
    register_chrdev_region(dev, 1, "MY_Dev");
    printk(KERN_INFO "Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));
    printk(KERN_INFO "Kernel Module Inserted Successfully...\n");
    return 0;
}
/*
** Module exit function
*/
static void __exit hello_world_exit(void)
{
    unregister_chrdev_region(dev, 1);
    printk(KERN_INFO "Kernel Module Removed Successfully...\n");
}
 
module_init(hello_world_init);
module_exit(hello_world_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Autuor");
MODULE_DESCRIPTION("Major and Minor number test static");
MODULE_VERSION("1:1.0");

动态分配的例程如下:hello_major_minor_dynamic.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kdev_t.h>
#include<linux/fs.h>
dev_t dev = 0;
/*
** Module Init function
*/
static int __init hello_world_init(void)
{
        /*Allocating Major number*/
        if((alloc_chrdev_region(&dev, 0, 1, "MY_Dev")) <0){
                printk(KERN_INFO "Cannot allocate major number for device 1\n");
                return -1;
        }
        printk(KERN_INFO "Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));
        printk(KERN_INFO "Kernel Module Inserted Successfully...\n");
        
        return 0;
}
/*
** Module exit function
*/
static void __exit hello_world_exit(void)
{
        unregister_chrdev_region(dev, 1);
        printk(KERN_INFO "Kernel Module Removed Successfully...\n");
}
 
module_init(hello_world_init);
module_exit(hello_world_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Autuor");
MODULE_DESCRIPTION("Major and Minor number test dynamic");
MODULE_VERSION("1:1.0");

利用如下代码可以查看输出信息:

1
cat /proc/devices | grep "MY_Dev"
本文由作者按照 CC BY 4.0 进行授权