linux 驱动基础 - 一、字符设备

作者: baron

    字符设备是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等,而字符设备用到的核心数据结构有fileinodecdevfile_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 函数提供给驱动程序以初始化的能力,从而为以后的操作完成初始化做准备。大部分驱动程序中应当完成下面工作。

  1. 检测设备特定的错误(注入设备未就绪或类似的硬件问题)
  2. 如果设备是首次打开,则对其进行初始化。
  3. 如果有必要,更新 fop 指针
  4. 分配并填写置于 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驱动程序的名称
fopsfile_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)

应用程序到内核空间的调用流程如下

  1. 应用层使用 open、write、read 函数操作 dev 目录下的设备文件
  2. 设备文件通过内核中对应设备的设备号,通过设备号找到对应的 file_operations 结构
  3. 调用 file_operations 结构中对应的 open、write、read 函 数驱动相关硬件进行操作

    有了设备号怎么创建设备节点呢,有两种方式一种是手动创建

// 创建设备节点
mknod /dev/xyz c(表示是字符设备) 主设备号 次设备号

//查看设备信息:
ls -l /dev/xyz

    除此之外还可以在驱动中自动创建设备文件,该方式依赖于设备模型,字符设备和设备模型的通过设备号关联在一起,只要有设备号就会在 /dev/ 下创建设备节点,参考设备驱动模型,在这篇文章中搜索 devtmpfs_create_node。,一般情况下我们通常按照下面步骤创建

  1. 调用class_create()函数,可以用它来创建一个类,这个类存放于sys/class/下面,
  2. 再调用 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 :
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值