一个Linux2.6版内核字符驱动的例子

时间:2021-11-19 17:56:51

看了《Linux设备驱动程序》的前几章,我结合这篇教程中给出的一个2.4版内核的字符驱动,自己编写了一个2.6版内核的驱动程序,并且加上了详细的注释。这个程序很简单,但是对初学者把握2.6版内核的字符驱动的脉络应该有一定的帮助,也可以算作我对《Linux设备驱动程序》前几章学习的一个小结。

#globalvar.c

#include <linux/module.h>   //模块所需的大量符号和函数定义

#include <linux/init.h>     //指定初始化和清楚函数

#include <linux/fs.h>       //文件系统相关的函数和头文件

#include <linux/cdev.h>     //cdev结构的头文件

#include <asm/uaccess.h>    //在内核和用户空间中移动数据的函数

 

MODULE_LICENSE("GPL");      //指定代码使用的许可证

 

//文件操作函数的声明

int globalvar_open(struct inode *, struct file *);

int globalvar_release(struct inode *, struct file *);

ssize_t globalvar_read(struct file *, char *, size_t, loff_t *);

ssize_t globalvar_write(struct file *, const char *, size_t, loff_t *);

 

int dev_major = 50;     //指定主设备号

int dev_minor = 0;      //指定次设备号

 

struct file_operations globalvar_fops=  //将文件操作与分配的设备号相连

{

    owner: THIS_MODULE,                 //指向拥有该模块结构的指针

    open: globalvar_open,

    release: globalvar_release,

    read: globalvar_read,

    write: globalvar_write,

};

 

struct globalvar_dev                //用来表示我们定义设备的结构

{

    int global_var;                 //这个变量代表要操作的设备

    struct cdev cdev;               //内核中表示字符设备的结构

};

 

struct globalvar_dev *my_dev;       //设备结构的指针

 

static void __exit globalvar_exit(void)         //退出模块时的操作

{

    dev_t devno=MKDEV(dev_major, dev_minor);    //dev_t是用来表示设备编号的结构

   

    cdev_del(&my_dev->cdev);                    //从系统中移除一个字符设备

    kfree(my_dev);                              //释放自定义的设备结构

    unregister_chrdev_region(devno, 1);         //注销已注册的驱动程序

   

    printk("globalvar unregister success/n");

}

 

static int __init globalvar_init(void)          //初始化模块的操作

{

    int ret, err;

    dev_t devno=MKDEV(dev_major, dev_minor);

    //动态分配设备号,次设备号已经指定

 

    ret=alloc_chrdev_region(&devno, dev_minor, 1, "globalvar");

    //保存动态分配的主设备号

    dev_major=MAJOR(devno);

    //根据期望值分配设备号

    //ret=register_chrdev_region(devno, 1, "globalvar");

    if(ret<0)

    {

        printk("globalvar register failure/n");

        globalvar_exit();                      //如果注册设备号失败就退出系统

        return ret;

    }

    else

    {

        printk("globalvar register success/n");

    }

    //为设备在内核空间分配空间

 

    my_dev=kmalloc(sizeof(struct globalvar_dev), GFP_KERNEL);

    if(!my_dev)

    {

        ret=-ENOMEM;                           //如果分配失败返回错误信息

        printk("create device failed/n");

    }

    else                                       //如果分配成功就可以完成设备的初始化

    {

        my_dev->global_var=0;                        //设备变量初始化为0

        cdev_init(&my_dev->cdev, &globalvar_fops);   //初始化设备中的cdev结构

        my_dev->cdev.owner=THIS_MODULE;              //初始化cdev中的所有者字段

        //my_dev->cdev.ops=&globalvar_fops;

        err=cdev_add(&my_dev->cdev, devno, 1);       //向内核添加这个cdev结构的信息

        if(err<0)

            printk("add device failure/n");          //如果添加失败打印错误消息

    }

    return ret;

}

//打开设备文件系统调用对应的操作

int globalvar_open(struct inode *inode, struct file *filp)

{

    struct globalvar_dev *dev;

    //根据inode结构的cdev字段,获得整个设备结构的指针

    dev=container_of(inode->i_cdev, struct globalvar_dev, cdev);

    //将file结构中的private_data字段指向已分配的设备结构

    filp->private_data=dev;

    return 0;

}

//关闭设备文件系统调用对应的操作

 

int globalvar_release(struct inode *inode, struct file *filp)

{

    return 0;

}

//读设备文件系统调用对应的操作

ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)

{

    //获取指向已分配数据的指针

    struct globalvar_dev *dev=filp->private_data;

    //将设备变量值复制到用户空间

    if(copy_to_user(buf, &dev->global_var, sizeof(int)))

    {

        return -EFAULT;

    }

    return sizeof(int);    //返回读取数据的大小

}

//写设备文件系统调用对应的操作

ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off)

{

    //获取指向已分配数据的指针

    struct globalvar_dev *dev=filp->private_data;

    //从用户空间复制数据到内核中的设备变量

    if(copy_from_user(&dev->global_var, buf, sizeof(int)))

    {

        return -EFAULT;

    }

    return sizeof(int);    //返回写数据的大小

}

 

module_init(globalvar_init);        //模块被装载时调用globalvar_init

module_exit(globalvar_exit);        //模块被卸载时调用globalvar_exit

 

按如下内容编写一个Makefile文件,然后输入make就可以开始自动编译了。编译之后得到了一个名为globalvar.ko的模块文件,这就是我们需要的设备驱动文件。

#Makefile

ifneq ($(KERNELRELEASE), )

    obj-m := globalvar.o

else

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

    PWD := $(shell pwd)

all:

    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:

    $(MAKE) -C $(KERNELDIR) M=$(PWD) clean

endif

 

接下来运行如下代码,将驱动加入内核。

insmod globalvar.ko

此时可以用dmesg或lsmod命令检查一下模块加载是否成功。如果没有问题,就可以使用mknod构造一个设备文件:

mknod /dev/globalvar c

 

最后,按照下面内容编写一个简单的测试文件并用gcc编译。

#test.c

#include <sys/types.h>

#include <sys/stat.h>

#include <stdio.h>

#include <fcntl.h>

 

main()

{

    int fd, num;

    fd=open("/dev/globalvar", O_RDWR, S_IRUSR|S_IWUSR);     //可读写方式打开设备文件

    if(fd!=-1)

    {

        read(fd, &num, sizeof(int));                        //读取设备变量

        printf("The globalvar is %d/n", num);

 

        printf("Please input the num written to globalvar/n");

        scanf("%d", &num);

        write(fd, &num, sizeof(int));                       //写设备变量

 

        read(fd, &num, sizeof(int));                        //再次读取刚才写的值

        printf("The globalvar is %d/n", num);

 

        close(fd);                                          //关闭设备文件

    }

    else

    {

        printf("Device open failure/n");

    }

}

如果前面的内容都无误,就可以使用上面的测试程序读写刚才编写的设备文件了。