字符设备驱动(2)-驱动框架及open、release

字符驱动框架

字符设备驱动加载函数

有了《内核模块编写及编译》《字符设备驱动(1)》的基础,并且字符设备驱动对上注册给内核,对下要操作硬件,所以分成2层:

                     

//加载函数(必须)
static int hello_init(void)//返回值是int类型,函数名自定义,参数是void
{
    printk(KERN_ALERT "Hello World.\n");
    //up kernel


    //down hardware

    return 0;
}

备注:上面只写了加载函数。

其中对上注册给内核,又分成3步:1、注册设备号,2、初始化字符设备结构体,3、添加字符设备结构体给内核。类似去体检,先要预约个体检号(第一步),去之前要准备一下,比如前一天饮食清淡点,早上空腹等等(第二步),最后在去体检(第三步)。

这三步分别有3个函数对应:

#include <linux/fs.h> 
//1、注册设备号,下面2个二选一
//1.1、提前规划好要用的设备号,向内核单独申请
int register_chrdev_region(dev_t from, unsigned count, const char *name)
//或者
//1.2、不知道使用哪些设备号,向内核动态申请
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
			const char *name)

#include <linux/cdev.h>
//2、初始化字符设备结构体
void cdev_init(struct cdev *cdev , const struct file_operations *fops)
//3、添加字符设备结构体给内核
int cdev_add(struct cdev *p, dev_t dev, unsigned count)

所以字符设备驱动加载函数如下:

//需要是全局变量
struct cdev cdevice;
struct file_operations cdev_ops;

//加载
static int hello_init(void)
{ 
    dev_t devno = MKDEV(MAJOR_CHAR,MINOR_CHAR);
    int ret = -1;
	printk(KERN_ALERT "Hello World.\n");
    //up kernel
        //1、注册设备号
        ret = register_chrdev_region(devno, 1, "hello");
        if (0 != ret)
        {
            printk("Register char device failed.\n");
            return ret;
        }

    
        //2、初始化字符设备结构体
        cdev_init(&cdevice, &cdev_ops);
    
        cdevice.owner = THIS_MODULE;
	
        //3、添加字符设备结构体给内核
        ret = cdev_add(&cdevice,devno , 1);
        if (0 != ret)
        {   
            //注意释放设备号
            unregister_chrdev_region(devno,1);
            printk("Unregister char device.\n");
            return ret;
        }

        printk("Register char device success.\n");

    //down hardware

    return 0;
} 

其中cdevice、cdev_ops不能定义为局部变量,因为这2个结构体是要在驱动(模块)加载完成后也需要长期存在,要挂载进内核,即要在hello_init函数执行完后也存在,若定义为hello_init函数中的局部变量,则函数执行完就释放掉了。

另外在cdev_add的异常分支注意需要释放设备号。

字符设备驱动卸载函数

字符设备驱动卸载函数也分成2层,和加载函数流程相反,先释放硬件资源,再从内核中注销,注销分成2部:1、从内核中删除字符设备结构体,2、注销设备号

其中从内核中删除字符设备结构体如下

#include <linux/cdev.h>
void cdev_del(struct cdev *cdevice)

卸载函数如下:

//卸载函数(必须)
static void hello_exit(void)//返回值是void类型,函数名自定义,参数是void
{
    dev_t devno = MKDEV(MAJOR_CHAR, MINOR_CHAR);

    printk(KERN_ALERT "Goodbye World.\n");
    // down hardware

    // up kernel
        //1、从内核中删除字符设备结构体
        cdev_del(&cdevice);

        //2、注销设备号
        unregister_chrdev_region(devno, 1);
}

字符设备操作

要对字符设备进行操作,可以像对文件操作,比如打开、关闭、读写等等,也就是对struct file_operations cdev_ops赋值,其成员是函数指针,赋值如下:

static int my_open(struct inode *pnode, struct file *pfile)
{
     printk("Open cdev.\n");
     return 0;
}
            
static int my_close(struct inode *pnode, struct file *pfile)
{
     printk("Close cdev.\n");
     return 0;
}
            
struct file_operations cdev_ops = { 
     .open    = my_open,
     .release = my_close,
};

字符设备驱动完整框架

cdev.c

//head
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>

#define MAJOR_CHAR 100
#define MINOR_CHAR 0

static int my_open(struct inode *pnode, struct file *pfile)
{
    printk("Open cdev.\n");
    return 0;
}

static int my_close(struct inode *pnode, struct file *pfile)
{
    printk("Close cdev.\n");
    return 0;
}

struct cdev cdevice;

struct file_operations cdev_ops = {
    .open    = my_open,
    .release = my_close,
};

//加载
static int hello_init(void)
{ 
    dev_t devno = MKDEV(MAJOR_CHAR,MINOR_CHAR);
    int ret = -1;
	printk(KERN_ALERT "Hello World.\n");
    //up kernel
        //1、注册设备号
        ret = register_chrdev_region(devno, 1, "hello");
        if (0 != ret)
        {
            printk("Register char device failed.\n");
            return ret;
        }
    
        //2、初始化字符设备结构体
        cdev_init(&cdevice, &cdev_ops);
    
        cdevice.owner = THIS_MODULE;
	
        //3、添加字符设备结构体给内核
        ret = cdev_add(&cdevice,devno , 1);
        if (0 != ret)
        {   
            //注意释放设备号
            unregister_chrdev_region(devno,1);
            printk("Unregister char device.\n");
            return ret;
        }

        printk("Register char device success.\n");
    //down hardware

    return 0;
} 

//卸载函数(必须)
static void hello_exit(void)//返回值是void类型,函数名自定义,参数是void
{
    dev_t devno = MKDEV(MAJOR_CHAR, MINOR_CHAR);

    printk(KERN_ALERT "Goodbye World.\n");
    // down hardware

    // up kernel
        //1、从内核中删除字符设备结构体
        cdev_del(&cdevice);

        //2、注销设备号
        unregister_chrdev_region(devno, 1);
}

//注册(必须)
module_init(hello_init);
module_exit(hello_exit);

//license(必须)
MODULE_LICENSE("GPL");

//作者与描述(可选)
MODULE_AUTHOR("Ono Zhang");
MODULE_DESCRIPTION("A simple Hello World Module");

Makefile见《内核模块编写及编译》

字符设备使用

那怎么把应用(app)、设备文件(mydev)、字符设备(cdevice)、操作(cdev_ops)联系起来呢?或者说应用怎么调用到驱动中的操作方法呢?需要3步:

第一,加载驱动,假设上述驱动文件编译出位cdev.ko,则使用:

#insmode cdev.ko

第二,创建设备文件,其中100是主设备号(cdev.c里面的MAJOR_CHAR ),0是次设备号(cdev.c里面的MINOR_CHAR )

#mknod /dev/mydev c 100 0

第三,编写应用文件,如下:

app.c

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#define FILE_NAME "/dev/mydev"

int main(void)
{
     int fd; 

     fd = open(FILE_NAME, O_RDWR);
     if (0 > fd) 
     {   
        printf("Open failed.\n");
        return -1; 
     }   

     close(fd);
}

使用gcc编译

#gcc app.c

执行此文件

#./a.out
Open cdev.
Close cdev.

这样用户态应用的打开、关闭设备文件,就调动到了内核态的驱动的my_open和my_close。

这是为什么呢?简单来讲如下图:

第1步,加载驱动(insmod),把字符设备号(devno)、字符设备结构体(cdevice)、字符设备操作(cdev_ops),关联起来,并放到内核里面。

第2步,创建设备文件,把设备文件(/dev/mydev)和字符设备号关联起来。

第3步,应用程序调用open函数,会调用到系统调用函数sys_open,然后根据/dev/mydev在内核中找到devno,找到devno就能找到对应的cdevice结构体,找到cdevice结构体,就能找到其操作方法cdev_ops,最终open就调用到cdev_ops的my_open函数了。

tobecontinue

​ 每周三、周六更新

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值