文章目录
作者: baron
字符设备是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等,而字符设备用到的核心数据结构有file、inode、cdev、file_operations。
1、struct file 数据结构
struct file 结构与用户空间程序中的FILE结构没有任何关联,FILE结构在 C 库中定义不会出现在内核代码中,struct file 是一个内核结构,它不会出现在用户程序中。struct file 结构代表一个打开的文件(它不仅仅限定于设备驱动程序,系统中每个打开的文件在内核空间都有一个对应的 file 结构)。它由内核在open时创建,并且传递给在该文件上操作的所有函数,直到最后的close函数,在文件的所有实例都被关闭后,内核会释放这个数据结构。在内核和驱动源代码中,struct file 的指针通常被命名为 file 或 filp ,为了不和这个结构本身混淆,我们一致将指向该结构的指针称为 filp,file 则为结构本身。
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
struct inode *f_inode; /* cached value */
const struct file_operations *f_op; /* 和文件关联的操作 */
/*
* Protects f_ep_links, f_flags.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
atomic_long_t f_count;
unsigned int f_flags; /* 文件标志,如O_RDONLY、O_NONBLOCK、O_SYNC */
fmode_t f_mode; /* 文件读/写模式,FMODE_READ和FMODE_WRITE ,文件打开是已经做了判断,基本用不着 */
struct mutex f_pos_lock;
loff_t f_pos; /* 当前读写位置 */
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFiG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data; /* 文件私有数据 */
#ifdef CONFiG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;
#endif /* #ifdef CONFiG_EPOLL */
struct address_space *f_mapping;
} __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
这个数据结构中驱动相关的几个重要成员变量如下表所示:
重要成员 | 说明 |
---|---|
loff_t f_pos | 当前读/写位置。loff_t有64位,驱动程序要知道文件中的当前位置,可以读取这个值,但不要去修改它。read/write会使用他们接收到的最后那个指针参数来更新这一位置,而不是直接针对filp->f_pos进行操作。这一规则的一个例外是llseek方法,该方法的目的本身就是为了修改文件位置 |
unsigned int f_flags | 文件标志,如O_RDONLY、O_NONBLOCK、O_SYNC,检查用户的请求是否是非阻塞式的操作,驱动程序需要检查O_NONBLOCK标志,而其他标志很少用到。注意:检测读写权限应该使用f_mode而不是f_flags。所有这些标志都被定义在<linux/fcntl.h>中 |
struct file_operations *f_op; | 与文件相关的操作。内核在执行open操作时对这个指针赋值,以后需要处理这个操作时就读取这个指针。 |
void *private_data; | file 结构的私有数据,被初始化为NULL |
2、struct inode 结构体
内核用 inode 结构在内部表示文件,因此它和 file 结构不同,后者表示打开的文件描述。对单个文件可能有多个打开的 file 文件描述(上层可以多次 open 一个文件),但他们都指向同一个 inode 文件。inode 包含文件访问权限、属主、组、大小、生成时间、访问时间、最后修改时间等大量文件信息。部分数据结构如下:
struct inode {
...
umode_t i_mode; /* inode 的权限 */
uid_t i_uid; /* inode 拥有者的 id */
gid_t i_gid; /* inode 所属的群组 id */
dev_t i_rdev; /* 若是设备文件,此字段将记录设备的设备号 */
loff_t i_size; /* inode 所代表的文件大小 */
struct timespec i_atime; /* inode 最近一次的存取时间 */
struct timespec i_mtime; /* inode 最近一次的修改时间 */
struct timespec i_ctime; /* inode 的产生时间 */
unsigned int i_blkbits;
blkcnt_t i_blocks; /* inode 所使用的 bloc k数,一个block为 512 字节 */
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev; /* 若是字符设备,为其对应的 cdev 结构体指针。 若是块设备,为其对应的 block_device 结构体指针*/
}
...
};
其中驱动对驱动编程有用的成员变量只有两个如下:
成员变量 | 含义 |
---|---|
dev_t i_rdev | 当 inode 结构描述的文件为设备文件时,表示它的设备号 |
struct cdev *i_cdev | 当 inode 指向一个字符设备文件时,i_cdev 为其对应的 cdev 结构体指针 |
在驱动中也可通过i_rdev获取设备号,内核提供了下面两个函数来获取 inode 结构中 i_rdev 字段中的设备号:
unsigned int imajor(struct inode* inode); //获取主设备号
unsigned int iminor(struct inode* inode); //获取次设备号
3、struct file_operations 结构体
该结构体是系统调用与驱动连接的桥梁,当我们在应用层使用 open 函数打开一个设备的时候,内核会创建一个 file 结构并关联 file_operations 中的一组函数,最终会调用到驱动中关联的 file_operations 结构体实例中 open 函数。而 file_operations 定义了一组操作函数,我们不一定全部用到,通常用到什么函数就关联什么函数。
struct file_operations {
struct module *owner; // 一个指向拥有这个结构的模块的指针,内核使用这个字段以避免在模块的操作正在被使用时卸载该模块,几乎所有情况被初始化为THIS_MODULE。
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 *);
......
};
1)open() 函数
对设备文件进行的第一个操作,如果这个函数没有实现,当用户调用 open() 时,一直显示成功,但是你的驱动不会得到通知。open 函数提供给驱动程序以初始化的能力,从而为以后的操作完成初始化做准备。大部分驱动程序中应当完成下面工作。
- 检测设备特定的错误(注入设备未就绪或类似的硬件问题)
- 如果设备是首次打开,则对其进行初始化。
- 如果有必要,更新 fop 指针
- 分配并填写置于 filp->private_date 里的数据结构
函数接口 | int (*open) (struct inode *inode , struct file *filp); |
---|---|
函数参数
|
参数含义
|
inode | 为文件节点(详细见前面inode结构) |
filp | 指向内核创建的文件结构(详细见前面file结构) |
3)read() 函数
用来从设备读取数据,成功时函数返回读取的字节数,返回值是一个 “signed size” 类型, 常常是目标平台本地的整数类型,出错时返回一个负值,用户调用 read() 时如果此函数未实现,将得到 -EINVAL 的返回值,它与用户空间的 fread() 函数对应。
函数接口 | ssize_t (*read) (struct file *filp, char __user *buffer, size_t size , loff_t *ppos); |
---|---|
函数参数
|
参数含义
|
filp | 指向内核创建的文件结构 |
buffer | 数据返回给用户空间的内存地址 |
size | 为要读取的信息长度,以字节为单位 |
ppos | 为读的位置相对于文件开头的偏移,在读取信息后,这个指针一般都会移动,移动的值为要读取信息的长度值 |
4)write() 函数
向设备发送数据,成功时返回写入的字节数,如果此函数未实现,当用户调用 write() 时,将得到 -EINVAL 的返回值,它与用户空间的 fwrite() 函数对应
函数接口 | ssize_t (*write) (struct file * filp, const char __user *buffer, size_t size, loff_t * ppos); |
---|---|
函数参数
|
参数含义
|
filp | 指向系统open时内核创建的文件结构 |
buffe | 用户要写入文件的信息缓冲区 |
size | 要写入信息的长度 |
ppos | 当前的读/写位置,这个值通常是用来判断写文件是否越界 |
注:这个操作和上面的对文件进行读的操作均为阻塞操作
4)ioctl() 函数
提供设备相关控制命令的实现(既不是读操作也不是写操作),当调用成功时,返回给调用程序一个非负值。如果设备不提供 ioctl 入口点,则对于任何内核未预先定义的请求,ioctl 系统调用将返回错误。(-ENOTTY,“No such ioctl for device, 该设备无此iotcl命令”)。
函数接口 | int (*ioctl) (struct inode *inode, struct file *flip, unsigned int cmd, unsigned long arg); |
---|---|
函数参数
|
参数含义
|
inode | 为文件节点 |
filp | 指向系统open时内核创建的文件结构 |
cmd | 从用户那里不改变地传下来的命令 |
arg | 对应命令的参数 |
为了保证用户传递下来的命令安全可靠,内核定义了iotcl命令的组成方式,当使用这个函数是应当遵循这个规则。
a. 命令的组成
设备类型由 8 位组成,可以是 0 ~ 0xFF 之间的值.
方向由2位组成,表示数据传输的方向,下表给出可选方向。
参数 | 参数含义 |
---|---|
_IOC_NONE | 无数据传输 |
_IOC_READ | 读操作 |
_IOC_WRITE | 写操作 |
_IOC_READ | _IOC_WRITE | 双向操作 |
数据长度字段表示涉及的用户数据的大小,这个成员的宽度依赖于体系结构,通常是 13 位或者14 位。
b. 命令生成方式
_IO(): 用于生成不涉及数据传输的简单命令
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
_IOR(): 用于生成读操作命令
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),\
(_IOC_TYPECHECK(size)))
_IOW(): 用于生成写操作命令
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),\
(_IOC_TYPECHECK(size)))
_IOWR(): 用于生成即可读也可写的操作命令
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr), \
(_IOC_TYPECHECK(size)))
_IO、_IOR 等使用的_IOC 宏:
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
c. 简单使用
下面的代码为伪代码,主要用于说明使用方式
#define DEV_NAME 'D'
#define DEV_CMD _IO( DEV_NAME, 0) //只向内核传递一条DEV_CMD命令
#define DEV_CMD_READ_SOMTHING _IOR(DEV_NAME, 1, unsigned int) //从内核读取一些数据(数据类型 unsigned int)
#define DEV_CMD_WRITE_SOMTHING _IOW(DEV_NAME, 2, unsigned int) //向内核写入一些数据(数据类型 int)
5)llseek() 函数
用来修改一个文件当前读写位置,并将新位置返回,出错时这个函数返回一个负值。如果这个函数指针是NULL,对 llseek 的调用将会以某种不可预期的方式修改 file 结构中的位置计数器。
函数接口 | loff_t (*llseek) (struct file *filp , loff_t p, int orig); |
---|---|
filp | 指向系统open时内核创建的文件结构 |
p | 当前的读/写位置,这个值通常是用来判断写文件是否越界 |
orig | 文件定位的地址,文件开头(SEEK_SET、0),当前位置(SEEK_CUR、1),文件末尾(SEEK_END、2) |
更多请参考博客Ph_one以及书籍linux设备驱动开发详解
4、struct cdev 结构体
在当前的 liux 内核中每一个字符设备都有一个 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 的方式有两种,第一种是直接利用同 struct cdev 类型创建,除此之外还可以使用运行时使用动态创建以及释放,函数接口如下表所示
函数接口 | 函数功能 |
---|---|
struct cdev *cdev_alloc(void) | 动态申请一个 cdev 内存。 |
void cdev_put(struct cdev *p) | 释放 cdev_alloc 申请的内存 |
5、字符设备的注册与卸载
获得cdev这个结构之后我们还需将其初始化以及注册到内核。
1) cdev_init() 函数
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev); // 将整个结构体清零;
INIT_LIST_HEAD(&cdev->list); // 初始化 list 成员
cdev->kobj.ktype = &ktype_cdev_default; // 初始 ktype
kobject_init(&cdev->kobj); // 初始化 kobj 成员
cdev->ops = fops // 初始化 cdev->ops,即建立cdev和file_operation 之间的连接
}
2) cdev_add() 函数
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev; // 初始化 cdev 结构的dev设备号
p->count = count; // 初始化 cdev 的设备数目
// 向系统添加一个cdev,完成字符设备的注册
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
3) cdev_del() 函数
void cdev_del(struct cdev *p)
{
cdev_unmap(p->dev, p->count);
kobject_put(&p->kobj);
}
向系统删除一个cdev,完成字符设备的卸载,对 cdev_del 的调用通常发生在字符设备驱动模块卸载函数中,当 cdev_del 被调用后对应的cdev结构就不应被访问了。
4) register_chrdev() 函数
函数接口 | int register_chrdev(unsigned int major, const char* name, struct file_operations* fops) |
---|---|
函数参数
|
参数含义
|
major | 设备的主设备号 |
name | 驱动程序的名称 |
fops | file_operations 结构 |
调用这个函数将为给定的主设备号注册 0 ~ 255 作为次设备号,并为每个设备建立一个对应默认 cdev 结构。使用这一接口的驱动程序必须能够处理所有 256 个次设备号的 open 调用,而且也不能使用大于 255 的主设备号和次设备号。
5) unregister_chrdev() 函数
函数接口 | int unregister_chrdev(unsigned int major, const char* name) |
---|---|
函数参数
|
参数含义
|
major | 要卸载的设备的主设备号 |
name | 要卸载驱动程序的名称 |
6、设备号
字符设备在应用空间的表示形式为 /dev/*** ,/dev 下的文件被称为设备节点,设备节点怎么和字符设备建立联系呢?答案就在设备号,通过设备号来建立设备节点与字符设备的联系。设备号由主设备号和次设备号组成
// include/linux/kdev_t.h
#include <uapi/linux/kdev_t.h>
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) // 从设备号中获取主设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) // 从设备号中获取次设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) // 将主设备号和次设备号合成完整的设备号
设备号由 unsingd int 表示,其中低 20 位表示次设备号,剩下的 12 位表示主设备号。实际上并没用到所有的 12 位,在老式内核中主设备号的范围为 0-255。后来不够用了于是对其进行了扩展。
/* fs/char_dev.c */
#define CHRDEV_MAJOR_MAX 512 // 主设备号最大值
/* Marks the bottom of the first segment of free char majors */
#define CHRDEV_MAJOR_DYN_END 234
/* Marks the top and bottom of the second segment of free char majors */
#define CHRDEV_MAJOR_DYN_EXT_START 511
#define CHRDEV_MAJOR_DYN_EXT_END 384
范围 | 分配方式 |
---|---|
0-233 | 静态分配 |
234-255 | 动态分配 |
256-383 | 静态分配 |
384-511 | 动态分配 |
动态分配优先使用 256-383 之间的设备号,如果没有了再使用 384-511 之间的设备号。对于这两种设备号的分配内核提供了下面接口
// 静态申请设备号
// from:要申请的设备号
// cont: 要申请的设备数量
// name:名称
int register_chrdev_region(dev_t from, unsigned count, const char *name)
// 动态申请设备号
// dev: 动态获取的设备号
// baseminor: 次设备号的起始地址
// cont: 要申请的设备数量
// name: 名称
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
应用程序到内核空间的调用流程如下
- 应用层使用 open、write、read 函数操作 dev 目录下的设备文件
- 设备文件通过内核中对应设备的设备号,通过设备号找到对应的 file_operations 结构
- 调用 file_operations 结构中对应的 open、write、read 函 数驱动相关硬件进行操作
有了设备号怎么创建设备节点呢,有两种方式一种是手动创建
// 创建设备节点
mknod /dev/xyz c(表示是字符设备) 主设备号 次设备号
//查看设备信息:
ls -l /dev/xyz
除此之外还可以在驱动中自动创建设备文件,该方式依赖于设备模型,字符设备和设备模型的通过设备号关联在一起,只要有设备号就会在 /dev/ 下创建设备节点,参考设备驱动模型,在这篇文章中搜索 devtmpfs_create_node。,一般情况下我们通常按照下面步骤创建
- 调用class_create()函数,可以用它来创建一个类,这个类存放于sys/class/下面,
- 再调用 device_create() 函数来在/dev目录下创建相应的设备节点,同时也会在sys/class/下创建出对应的设备文件
// owner: 模块的拥有着,一般为"THIS_MODULE"
// name : 类名,创建成功后显示 "/sys/class/xxx"
struct class *class_create(struct module *owner, const char *name);
// class: 设备模型中设备所属的类
// parent: 要创建的设备的父设备
// devt: 添加该设备的设备号 dev_t,用于关联字符设备
// drvdata: 该设备的私有数据
// fmt: 备名的名称,创建成功后,将出现 "dev/fmt" 已经 "/sys/class/xxx/fmt"
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
当卸载设备的时候使用函数 device_destroy() 从 linux 内核系统设备驱动程序模型中移除一个设备,并删除 /sys/devices/virtual 目录下对应的设备目录及 /dev/ 目录下对应的设备文
// dev: 创建的设备类
// devt: 对应的设备号
void device_destroy(struct class *dev, dev_t devt);
7、内核与用户空间数据交互
用户空间与内核空间之间的数据不能直接进行简单的赋值交互,linux 内核提供了两者交互的函数通过 copy_from_user 获取用户空间的数据,通过 copy_to_user 将内核空间的数据传给用户空间,当操作成功后均返回 0,操作失败返回负值。
1) copy_from_user
// to: 目标地址,内核空间地址
// from: 源地址,用户空间地址
// n: 要拷贝的数据字节数
static __always_inline unsigned long __must_check
copy_from_user(void *to, const void __user *from, unsigned long n)
{
if (likely(check_copy_size(to, n, false)))
n = _copy_from_user(to, from, n);
return n;
}
2) copy_to_user
// to: 目标地址,用户空间地址
// from: 源地址,内核空间地址
// n: 要拷贝的数据字节数
static __always_inline unsigned long __must_check
copy_to_user(void __user *to, const void *from, unsigned long n)
{
if (likely(check_copy_size(from, n, true)))
n = _copy_to_user(to, from, n);
return n;
}
8、编程验证
内核部分:
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/uaccess.h>
#define SIZE 256
#define DEV_NAME 'M'
#define DEV_CLEAN _IO( DEV_NAME, 0)
struct mycdev {
char buf[SIZE];
dev_t dev;
struct cdev* cdev;
struct file_operations* fops;
struct class* class;
struct device device;
};
struct mycdev* mycdev = NULL;
ssize_t mycdev_open(struct inode *inode, struct file *filp)
{
printk("mycdev open\n");
filp->private_data = mycdev;
return 0;
}
ssize_t mycdev_read( struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
int err = 0;
unsigned int pos = *ppos;
unsigned int count = size;
struct mycdev* mycdev = filp->private_data;
if(pos > SIZE){
return count ? -ENXIO : 0 ;
}
if(count > SIZE - pos){
count = SIZE - pos;
}
printk("read begin, pos:%d, count:%d \n",pos, count);
err = copy_to_user(buf, (void*)((char*)mycdev->buf + pos), count);
if(err){
printk("copy to user err\n");
return -EPERM;
}
*ppos += count;
printk("read %d bytes from mydev->buf[%d]\n", count, pos);
return count;
}
static ssize_t mycdev_write( struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
int err = 0;
unsigned int pos = *ppos;
unsigned int count = size;
struct mycdev* mycdev = filp->private_data;
if(pos > SIZE){
return count ? -ENXIO : 0 ;
}
if(count > SIZE - pos){
count = SIZE - pos;
}
printk("write begin, pos:%d, count:%d \n",pos, count);
err = copy_from_user(mycdev->buf + pos, buf, count);
if(err){
printk("copy from user err\n");
return -EFAULT;
}
*ppos += count;
printk("write %d bytes to mydev->buf[%d]\n", count, pos);
return count;
}
static loff_t mycdev_llseek(struct file *filp, loff_t offset, int orig)
{
if(offset < 0){
return -EINVAL;
}
if(0 == orig){
filp->f_pos = offset;
}else if(1 == orig){
filp->f_pos += offset;
}else if(2 == orig){
filp->f_pos = SIZE + offset;
}
return filp->f_pos;
}
static long mycdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct mycdev* mycdev = filp->private_data;
switch (cmd){
case DEV_CLEAN:
memset(mycdev->buf, 0, SIZE);
break;
default :
printk("don't have this cmd\n");
return -EINVAL;
}
return 0;
}
static int mycdev_release(struct inode *inode, struct file *filp)
{
printk("mycdev release\n");
return 0;
}
static struct file_operations mycdev_fops = {
.owner = THIS_MODULE,
.open = mycdev_open,
.read = mycdev_read,
.write = mycdev_write,
.llseek = mycdev_llseek,
.release = mycdev_release,
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36)
.ioctl = mycdev_ioctl,
#else
.unlocked_ioctl = mycdev_ioctl,
.compat_ioctl = mycdev_ioctl,
#endif
};
int mycdev_init(struct mycdev* mycdev)
{
int err = 0;
if(!mycdev || !mycdev->fops){
printk("mycdev or mycdev->fops is null\n");
goto out;
}
err = alloc_chrdev_region(&mycdev->dev, 0 , 1, "mycdev");
if(err < 0){
printk("alloc chrdev err\n");
goto alloc_chrdev_erro;
}
mycdev->cdev = cdev_alloc();
if(!mycdev->cdev){
printk("cdev alloc err\n");
goto mycdev_alloc_erro;
}
cdev_init(mycdev->cdev, mycdev->fops);
mycdev->cdev->owner = THIS_MODULE;
return 0;
mycdev_alloc_erro:
unregister_chrdev_region(mycdev->dev,1);
alloc_chrdev_erro:
out:
return -ENOMEM;
}
void mycdev_device_release(struct device *dev)
{
printk("mycdev device release\n");
}
int mycdev_creat_sysfs(struct mycdev* mycdev)
{
int err;
mycdev->class = class_create(THIS_MODULE, "mycdev_cls");
if(!mycdev->class){
return -ENOMEM;
}
mycdev->device.class = mycdev->class;
mycdev->device.init_name = "mycdev";
mycdev->device.devt = mycdev->dev;
mycdev->device.release = mycdev_device_release;
err = device_register(&mycdev->device);
if(err < 0){
printk("device register err\n");
return err;
}
return 0;
}
static int __init hello_init (void)
{
int ret = 0;
mycdev = (struct mycdev*)kzalloc(sizeof(*mycdev), GFP_KERNEL);
if(!mycdev){
printk("mycdev alloc err\n");
return -ENOMEM;
}
mycdev->fops = &mycdev_fops;
ret = mycdev_init(mycdev);
if(ret < 0){
printk("mycdev init err\n");
return ret;
}
ret = cdev_add(mycdev->cdev,mycdev->dev,1);
if(ret < 0)
{
printk("cdev add err %d\n",ret);
return ret;
}
ret = mycdev_creat_sysfs(mycdev);
if(ret < 0){
printk("mycdev creat sysfs err\n");
return ret;
}
return ret;
}
static void __exit hello_exit (void)
{
printk("mycdev exit\n");
cdev_del(mycdev->cdev);
device_unregister(&mycdev->device);
class_destroy(mycdev->class);
unregister_chrdev_region(mycdev->dev,1);
kfree(mycdev);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("baron-z");
MODULE_DESCRIPTION("my cdev test driver");
应用程序部分:
这部分主要用于验证内核实现,不具备严谨性,只是测试代码
#include <stdio.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <malloc.h>
#include <string.h>
#define DEV_NAME 'M'
#define DEV_CLEAN _IO( DEV_NAME, 0)
int main(int argc, char* argv[])
{
int size = 0;
char* buff = (char*)malloc(255);
int fd = -1;
buff[254] = '\0';
if(argc < 2)
return 0;
fd = open("/dev/mycdev", O_RDWR);
if(fd < 0){
printf("open /dev/mycdev err\n");
return -1;
}
if(!strcmp("write", argv[1])){
write(fd, argv[2], strlen(argv[2]));
printf("write %s to mycdev buf\n\n", argv[2]);
}else if(!strcmp("read", argv[1])){
read(fd, buff, 255);
printf("read data form mycdev : %s\n\n", buff);
}else if(!strcmp("clean", argv[1])){
ioctl(fd, DEV_CLEAN);
printf("clean mycdev buf\n\n");
}
close(fd);
return 0;
}
验证结果:
[root@100ask:~]# ./a.out write 1234556 // 写入 1234556
write 1234556 to mycdev buf
[root@100ask:~]#
[root@100ask:~]# ./a.out read // 读取数据
read data form mycdev : 1234556
[root@100ask:~]#
[root@100ask:~]# ./a.out clean // 清除数据
clean mycdev buf
[root@100ask:~]#
[root@100ask:~]# ./a.out read // 再次读取数据
read data form mycdev :