您的位置:首页 > 运维架构 > Linux

linux驱动学习记录(一)-字符设备框架

2017-10-08 14:59 435 查看
该系列是我在学习宋宝华老师的《Linux设备驱动开发详解》以及结合其他网上教程所做记录。



[b]1.     Linux设备[/b]

 

在Linux操作系统下的设备通常分为三类:

字符设备、块设备和网络设备。

 

字符设备是以字节为单位逐个进行I/O操作的设备,在对字符设备发出读写请求时,实际的硬件I/O紧接着就发生了,一般来说字符设备中的缓存是可有可无的,而且也不支持随机访问。

块设备则是利用一块系统内存作为缓冲区,当用户进程对设备进行读写请求时,驱动程序先查看缓冲区中的内容,如果缓冲区中的数据能满足用户的要求就返回相应的数据,否则就调用相应的请求函数来进行实际的I/O操作。块设备主要是针对磁盘等慢速设备设计的,其目的是避免耗费过多的CPU时间来等待操作的完成。

网络设备不同于字符设备和块设备,它是面向报文的而不是面向流的,它不支持随机访问,也没有请求缓冲区。在Linux里一个网络设备也可以叫做一个网络接口,如eth0,应用程序是通过Socket而不是设备节点来访问网络设备,在系统里根本就不存在网络设备节点。

一般说来,PCI卡属于字符设备。

 
 
[b]2.     字符设备驱动框架[/b]

2.1   设备驱动模块

 

在以模块方式编写驱动程序时,要实现两个必不可少的函数init_module()和cleanup_module(),而且至少要包含<linux/krernel.h>和<linux/module.h>两个头文件。

static int __init xxx_init_module (void)
{
...
}

static void __exit xxx_cleanup_module (void)
{
...
}
/* 驱动模块加载函数 */
module_init(xxx_init_module);
/* 驱动模块卸载函数 */
module_exit(xxx_cleanup_module);


2.2   字符设备驱动的两个重要结构体

 

cdev

在Linux内核中,使用cdev结构体描述一个字符设备,cdev结构体的定义如下

struct cdev {
struct kobject kobj; /* 内嵌的kobject对象 */
  struct module *owner; /* 所属模块*/
struct file_operations *ops; /*
文件操作结构体*/
struct list_head list;
dev_t dev; /* 设备号*/
unsigned int count;
};

cdev结构体的dev_t成员定义了设备号,为32位,其中12位为主设备号,20位为次设备号。使用下列宏可以从dev_t获得主设备号和次设备号:

MAJOR(dev_t dev)
MINOR(dev_t dev)
而使用下列宏则可以通过主设备号和次设备号生成dev_t:

MKDEV(int major, int minor)
 
file_operations

cdev结构体的另一个重要成员file_operations定义了字符设备驱动提供给虚拟文件系统的接口函数。

file_operations结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行Linux的open()、write()、read()、close()等系统调用时最终被内核调用。file_operations结构体的定义如下

struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
 long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
 int (*mmap) (struct file *, struct vm_area_struct *);
 int (*open) (struct inode *, struct file *);
 int (*flush) (struct file *, fl_owner_t id);
 int (*release) (struct inode *, struct file *);
 int (*fsync) (struct file *, loff_t, loff_t, int datasync);
 int (*aio_fsync) (struct kiocb *, int datasync);
 int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
 unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long,
unsigned long, unsigned long);
 int (*check_flags)(int);
 int (*flock) (struct file *, int, struct file_lock *);
 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
 ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
 int (*setlease)(struct file *, long, struct file_lock **);
 long (*fallocate)(struct file *file, int mode, loff_t offset,
 loff_t len);
 int (*show_fdinfo)(struct seq_file *m, struct file *f);
};
 
 
2.3   字符设备驱动模型






以下为一个简单字符驱动框架示例代码,没有任何功能

 
#include
#include
#include
#include
#include

//预定义主设备号
#define LS_MAJOR 150

static int ls_major = LS_MAJOR;
module_param(ls_major, int, S_IRUGO);

//自定义设备结构体
struct ls_dev {
struct cdev cdev;			//在Linux内核中,使用cdev结构体描述一个字符设备
};
struct ls_dev *ls_devp;

/* 字符设备file_operations open 成员函数 */
static int xxx_open(struct inode *inode, struct file *filp)
{
return 0;
}

/* 字符设备file_operations ioctl 成员函数 */
static long xxx_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
return 0;
}

/* 字符设备file_operations read 成员函数 */
static ssize_t xxx_read(struct file *filp, char __user * buf, size_t size, loff_t * ppos)
{
return 0;
}

/* 字符设备file_operations write成员函数 */
static ssize_t xxx_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos)
{
return 0;
}

/* 字符设备file_operations release成员函数 */
static int xxx_release(struct inode *inode, struct file *filp)
{
return 0;
}

/* 设备文件操作接口 */
static const struct file_operations xxx_fops = {
.owner = THIS_MODULE, /* xxx_fops 所属的设备模块 */
.read = xxx_read, /* 读设备操作*/
.write = xxx_write, /* 写设备操作*/
.unlocked_ioctl = xxx_ioctl, /* 控制设备操作*/
.open = xxx_open, /* 打开设备操作*/
.release = xxx_release, /* 释放设备操作*/
};

static int __init xxx_init_module (void)
{
//申请字符设备号
dev_t xxx_dev_no = MKDEV(ls_major, 0);
register_chrdev_region(xxx_dev_no, 1, "driver_test");
//为自定义设备结构体申请内存
ls_devp = kzalloc(sizeof(struct ls_dev), GFP_KERNEL);

//将自定义设备结构体内的cdev成员与file_operations设备文件操作接口绑定
cdev_init(&ls_devp->cdev,&xxx_fops);

//拥有该结构的模块的指针,一般为THIS_MODULES
ls_devp->cdev.owner = THIS_MODULE;

//注册设备
cdev_add(&ls_devp->cdev, xxx_dev_no, 1);
return 0;
}

static void __exit xxx_cleanup_module (void)
{
/* 注销字符设备 */
cdev_del(&ls_devp->cdev);
/* 释放占用的设备号 */
unregister_chrdev_region(MKDEV(ls_major, 0), 1);
kfree(ls_devp);
}

/* 驱动模块加载函数 */
module_init(xxx_init_module);
/* 驱动模块卸载函数 */
module_exit(xxx_cleanup_module);

MODULE_AUTHOR("LuoSheng");
MODULE_LICENSE("GPL v2");


3.      驱动加载

 

可以使用命令insmod载入Linux内核,从而成为内核的一个组成部分,此时内核会调用模块中的函数init_module( )。当不需要该模块时,可以使用rmmod命令进行卸载,此进内核会调用模块中的函数cleanup_module()。任何时候都可以使用命令来lsmod查看目前已经加载的模块以及正在使用该模块的用户数。 

所有已经注册(即已经加载了驱动程序)的硬件设备的主设备号可以从/proc/devices文件中得到。使用mknod命令可以创建指定类型的设备文件,同时为其分配相应的主设备号和次设备号。例如,下面的命令:

#insmod pcitest.ko //加载驱动
#mknod /dev/pcitest c 150 0 //创建设备文件,主设备号150,次设备号0
#rmmod pcitest  //卸载驱动
 
将建立一个主设备号为150,次设备号为0的字符设备文件/dev/ pcitest。

当应用程序对某个设备文件进行系统调用时,Linux内核会根据该设备文件的设备类型和主设备号调用相应的驱动程序,并从用户态进入到核心态,再由驱动程序判断该设备的次设备号,最终完成对相应硬件的操作。 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: