linux字符设备驱动框架

字符设备:是指只能一个字节一个字节读写的设备,那些按字节流访问的设备,针对字符设备的驱动称为字符设备驱动。

        用户空间用open函数打开dev/pin4 下的设备后,可以用write向文件中写入数据,或者用read从里面读出数据,其中大致的流程是例如用户空间调用open会产生软中断,中断号是0x80,软中断为了响应快速用汇编实现了sys_call(系统调用),然后sys_call再调用相关的函数调用VFS(虚拟文件系统)里的sys_open,然后sys_open会去内核的驱动链表,根据设备名和设备号找到相关的驱动函数,去调用驱动函数里的open,这里的open同write,open我们可以根据需要修改。

        APP 打开文件时,可以得到一个整数,这个整数被称为文件句柄。对于 APP 的每一个文件句柄,在内核里面都有一个“struct file”与之对应。我们使用 open 打开文件时,传入的 flags、mode 等参数会被记录在内核中对应的 struct file 结构体里(f_flags、f_mode),去读写文件时,文件的当前偏移地址也会保存在 struct file 结构体的 f_pos 成员里。

                

 上图的struct file结构体中的结构体:struct file_operations 参数如下图,即指针函数集,其中owner一 般初始化为THIS_MODULE,让其归属与自己。上层对应底层,例如上层想要用read,底层就要有read的支持,还有ioctl函数是一个专用于设备输入输出操作的系统调用,可以去了解一下。

 字符设备驱动的编写步骤

1.确定主设备号,dev_t MKDEV(int major, int minor),也可以让内核分配

2. 定义自己的 file_operations 结构体,是字符设备驱动提供给VFS的接口函数

 

3. 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体

 4.把 file_operations 结构体告诉内核:register_chrdev

5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 cdev_init( ) ,为了建立cdev与 file_operations之间的连接

6.有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用 unregister_chrdev

7.其他完善:提供设备信息,自动创建设备节点:class_create, device_create

下面是很基本的字符驱动框架:

#include <linux/fs.h>		 //file_operations声明
#include <linux/module.h>    //module_init  module_exit声明
#include <linux/init.h>      //__init  __exit 宏定义声明
#include <linux/device.h>	 //class  devise声明
#include <linux/uaccess.h>   //copy_from_user 的头文件
#include <linux/types.h>     //设备号  dev_t 类型声明
#include <asm/io.h>          //ioremap iounmap的头文件


static struct class *pin4_class;  
static struct device *pin4_class_dev;

static dev_t devno;                //设备号
static int major =231;  		   //主设备号
static int minor =0;			   //次设备号
static char *module_name="pin4";   //模块名


//_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
    printk("pin4_open\n");  //内核的打印函数和printf类似
     
    return 0;
}

//_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{

    return 0;
}

static struct file_operations pin4_fops = {

    .owner = THIS_MODULE,
    .open  = pin4_open,
    .write = pin4_write,
};

int __init pin4_drv_init(void)   
{

    int ret;
    devno = MKDEV(major,minor);  //创建设备号
    ret   = register_chrdev(major, module_name,&pin4_fops);  //注册驱动  告诉内核,把这个驱动加入到内核驱动的链表中

    pin4_class=class_create(THIS_MODULE,"myfirstdemo");
    pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  //创建设备文件

 
    return 0;
}

void __exit pin4_drv_exit(void)
{

    device_destroy(pin4_class,devno);
    class_destroy(pin4_class);
    unregister_chrdev(major, module_name);  //卸载驱动

}

module_init(pin4_drv_init);  //入口
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");    //linux内核遵循GPL协议



struct file结构体中的read和write指针函数定义中,用户空间传下来的char __user参数是不能直接赋值的,我们需要用到两个函数。

针对read,我们用copy_to_user  ,把用户空间的数据读到内核空间的数据中

        copy_to_user(void * to, const void * from,unsigned long n);     

        第一参数用户空间的数据地址,

        第二参数是内核空间数据的地址,

        第三参数是复制的字节数,这里可以这样写

#define MIN(a,b) (a < b ? a : b)

static char kernel_buf[1024];

copy_to_user( buf, kernel_buf , MIN( 1024, size)); //第一个参数buf是char __user*

        返回值为实际copy的字节数

针对write,我们用copy_from_user ,把内核空间的数据写到用户空间里

        copy_from_user(void * to, const void __user *from,unsigned long n);

        第一个参数是内核空间数据的地址,kernel_buf

        第二个参数是用户空间的数据地址,即__user

        第三个参数是复制的字节数,参考上面的写法

        返回值为真实读到的字节数量。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值