概述
1、字符驱动组成
1.1字符驱动的模块加载与卸载
//设备结构体模板
struct xxx_dev_t
{
struct cdev cdev;......
}xxx_dev;
在字符驱动模块加载函数中应该事先设备号的申请与cdev注册,而写在函数中应该实现设备号的释放和cdev的注销。
一般习惯将设备定义为一个设备相关的结构体,其包含该设备所涉及的cdev,私有数据与信号量等信息。
//设备驱动模块加载函数模板
static int _ _init xxx_init(void)
{
......cdev_init(&xxx_dev.cdev,&xxx_fops);//初始化cdevxxx_dev.cdev.owner= THIS_MODULE;//获取字符设备号if(xxx_major){register_chrdev_region(xxx_dev_no,1,DEV_NAME);}else {alloc_chrdev_region(&xxx_dev_no,1,DEV_NAME);}ret = cdev_add(&xxx_dev.cdev,xxx_dev_no,1);//注册设备....
}
/*设备驱动模块卸载函数*/
static void _ _exit xxx_exit(void)
{
static void _ _exit xxx_exit(void)
{
unregister_chrdev_region(xxx_dev_no, 1); //释放占用的设备号cdev_del(&xxx_dev.cdev); //注销设备...
}
1.2 字符设备驱动的file_operations 结构体成员函数
file_operations结构体中成员函数是字符设备驱动与内核的接口,是用户空间对linux进行系统调用最终的落实者。大多数字符设备驱动会实现read()、write()和ioctl()函数,模板如下:
// 驱动读设备之模板
ssize_txxx_read(struct file *filp, char _ _user *buf, size_t count, loff_t*f_ops)
{
...copy_to_user(buf, ... , ...);......
}
// 驱动写设备之模板
ssize_t xxx_write(struct file *filp, const char _ _ user *buf, size_t, count, loff_t *f_pos)
{
...copy_from_user(..., buf. ...);...
}
// 驱动ioctl函数之模板
Int xxx_ioctl(struct inode *inode,struct file *filp, unsigned int cmd, unsigned long arg)
{
...switch(cmd){case xxx_CMD1:...break;case xxx_CMD2:...break;default:return -ENOTTY;//不能支持的命令}
}
注意:在设备驱动的函数中,flip是文件结构体指针,buf是用户空间内存的地址,该地址在内核空间不能直接读写,count是要读的字节数,f_ops是的德位置相对于文件开头的偏移。
由于内核空间与用户空间的内存不能直接互访,因此借助函数copy_from_user()完成用户空间到内核空间的复制,函数copy_to_user()完成内核空间到用户空间的复制。
以上两个函数原型为:
unsigned long copy_from_user(void *to, const void __user *from, unsigned long count);
unsigned long copy_to_user(void __user *to, const void *from , unsigned long count);
上述两个函数返回不能被复制的字节数,因此,如果完全复制成功,返回值为0。
在字符驱动中,需要定义一个file_operations,并将具体设备驱动的函数值赋给file_operations的成员。
struct file_operation xxx_fops
{
.owner= THIS_MODULE,.read= xxx_read..write= xxx_write,.ioctl= xxx_ioctl,......
}
2. 字符驱动的结构
2.1 cdev结构体
在 Linux 2.6 内核中使用 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)
cdev 结构体的另一个重要成员 file_operations 定义了字符设备驱动提供给虚拟文件系统的接口函数。
Linux 2.6 内核提供了一组函数用于操作 cdev 结构体,如下所示:
void cdev_init(struct cdev *, struct file_operations *);
cdev_init()函数用于初始化 cdev 的成员,并建立 cdev 和 file_operations 之间的连接。
struct cdev *cdev_alloc(void);
struct cdev *cdev_alloc(void);
cdev_alloc()函数用于动态申请一个 cdev 内存。
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
cdev_add()函数和cdev_del()函数分别向系统添加和删除一个 cdev,完成字符设备的注册和注销。对 cdev_add()的调用通常发生在字符设备驱动模块加载函数中,而对cdev_del()函数的调用则通常发生在字符设备驱动模块卸载函数中。
2.2 分配和释放设备号
在 调 用 cdev_add() 函 数 向 系 统 注 册 字 符 设 备 之 前 , 应 首 先 调 用register_chrdev_region()或 alloc_chrdev_region()函数向系统申请设备号,
alloc_chrdev_region --自动分配设备号
register_chrdev_region --分配以设定的设备号。
这两个函数的原型如下:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
register_chrdev_region() 函 数 用 于 已 知 起 始 设 备 的 设 备 号 的 情 况 ; 而alloc_chrdev_region()用于设备号未知,向系统动态申请未被占用的设备号的情况。函数调用成功之后,会把得到的设备号放入第一个参数 dev 中。alloc_chrdev_region()与register_chrdev_region()对比的优点在于它会自动避开设备号重复的冲突。相 反 地 , 在 调 用cdev_del() 函 数 从 系 统 注 销 字 符 设 备 之 后 ,unregister_chrdev_region()应该被调用以释放原先申请的设备号,这个函数的原型如下:
void unregister_chrdev_region(dev_t from, unsigned count);
int register_chrdev_region(dev_t from, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
register_chrdev_region() 函 数 用 于 已 知 起 始 设 备 的 设 备 号 的 情 况 ; 而alloc_chrdev_region()用于设备号未知,向系统动态申请未被占用的设备号的情况。函数调用成功之后,会把得到的设备号放入第一个参数 dev 中。alloc_chrdev_region()与register_chrdev_region()对比的优点在于它会自动避开设备号重复的冲突。相 反 地 , 在 调 用cdev_del() 函 数 从 系 统 注 销 字 符 设 备 之 后 ,unregister_chrdev_region()应该被调用以释放原先申请的设备号,这个函数的原型如下:
void unregister_chrdev_region(dev_t from, unsigned count);
2.3 file_operations 结构体
file_operations 结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行 Linux 的 open()、write()、read()、close()等系统调用时最终被调用。file_operations 结构体目前已经比较庞大,它的定义如下 所示。
file_operations 结构体
struct file_operations
{
struct file_operations
{
struct module *owner;// 拥有该结构的模块的指针,一般为 THIS_MODULESloff_t(*llseek)(struct file *, loff_t, int);// 用来修改文件当前的读写位置ssize_t(*read)(struct file *, char _ _user *, size_t, loff_t*);// 从设备中同步读取数据ssize_t(*aio_read)(struct kiocb *, char _ _user *, size_t, loff_t);// 初始化一个异步的读取操作ssize_t(*write)(struct file *, const char _ _user *, size_t, loff_t*); // 向设备发送数据ssize_t(*aio_write)(struct kiocb *, const char _ _user *, size_t, loff_t);// 初始化一个异步的写入操作int(*readdir)(struct file *, void *, filldir_t); // 仅用于读取目录,对于设备文件,该字段为 NULLunsigned int(*poll)(struct file *, struct poll_table_struct*); // 轮询函数,判断目前是否可以进行非阻塞的读取或写入int(*ioctl)(struct inode *, struct file *, unsigned int, unsigned long);// 执行设备 I/O 控制命令long(*unlocked_ioctl)(struct file *, unsigned int, unsigned long); // 不使用 BLK 文件系统,将使用此种函数指针代替 ioctllong(*compat_ioctl)(struct file *, unsigned int, unsigned long);// 在 64 位系统上,32 位的 ioctl 调用将使用此函数指针代替int(*mmap)(struct file *, struct vm_area_struct*); // 用于请求将设备内存映射到进程地址空间int(*open)(struct inode *, struct file*);// 打开int(*flush)(struct file*);int(*release)(struct inode *, struct file*); // 关闭int(*synch)(struct file *, struct dentry *, int datasync);// 刷新待处理的数据int(*aio_fsync)(struct kiocb *, int datasync);// 异步 fsyncint(*fasync)(int, struct file *, int); // 通知设备 FASYNC 标志发生变化int(*lock)(struct file *, int, struct file_lock*);ssize_t(*readv)(struct file *, const struct iovec *, unsigned long,loff_t*);ssize_t(*writev)(struct file *, const struct iovec *, unsigned long,loff_t*);// readv 和 writev:分散/聚集型的读写操作ssize_t(*sendfile)(struct file *, loff_t *, size_t, read_actor_t,void*);// 通常为 NULLssize_t(*sendpage)(struct file *, struct page *, int, size_t,loff_t *, int);// 通常为 NULLunsigned long(*get_unmapped_area)(struct file *,unsigned long,unsigned long, unsigned long, unsigned long); // 在进程地址空间找到一个将底层设备中的内存段映射的位置int(*check_flags)(int);// 允许模块检查传递给 fcntl(F_SETEL...)调用的标志int(*dir_notify)(struct file *filp, unsigned long arg);// 仅对文件系统有效,驱动程序不必实现int(*flock)(struct file *, int, struct file_lock*);
};
llseek()函数用来修改一个文件的当前读写位置,并将新位置返回,在出错时,这个函数返回一个负值。
read()函数用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一个负值。
read()函数用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一个负值。
write()函数向设备发送数据,成功时该函数返回写入的字节数。如果此函数未被实现,当用户进行 write()系统调用时,将得到-EINVAL 返回值。
readdir()函数仅用于目录,设备节点不需要实现它。
ioctl()提供设备相关控制命令的实现(既不是读操作也不是写操作),当调用成功时,返回给调用程序一个非负值。内核本身识别部分控制命令,而不必调用设备驱动中的
ioctl()。如果设备不提供 ioctl()函数,对于内核不能识别的命令,用户进行 ioctl()系统调用时将获得-EINVAL 返回值。
mmap()函数将设备内存映射到进程内存中,如果设备驱动未实现此函数,用户进行 mmap()系统调用时将获得-ENODEV 返回值。这个函数对于帧缓冲等设备特别有意义。
当用户空间调用 Linux API 函数 open()打开设备文件时,设备驱动的 open()函数最终被调用。驱动程序可以不实现这个函数,在这种情况下,设备的打开操作永远成功。与 open()函数对应的是 release()函数。
poll()函数一般用于询问设备是否可被非阻塞地立即读写。当询问的条件未触发时,用户空间进行 select()和 poll()系统调用将引起进程的阻塞。
aio_read()和 aio_write()函数分别对与文件描述符对应的设备进行异步读、写操作。设备实现这两个函数后,用户空间可以对该设备文件描述符调用 aio_read()、aio_write()等系统调用进行读写。
readdir()函数仅用于目录,设备节点不需要实现它。
ioctl()提供设备相关控制命令的实现(既不是读操作也不是写操作),当调用成功时,返回给调用程序一个非负值。内核本身识别部分控制命令,而不必调用设备驱动中的
ioctl()。如果设备不提供 ioctl()函数,对于内核不能识别的命令,用户进行 ioctl()系统调用时将获得-EINVAL 返回值。
mmap()函数将设备内存映射到进程内存中,如果设备驱动未实现此函数,用户进行 mmap()系统调用时将获得-ENODEV 返回值。这个函数对于帧缓冲等设备特别有意义。
当用户空间调用 Linux API 函数 open()打开设备文件时,设备驱动的 open()函数最终被调用。驱动程序可以不实现这个函数,在这种情况下,设备的打开操作永远成功。与 open()函数对应的是 release()函数。
poll()函数一般用于询问设备是否可被非阻塞地立即读写。当询问的条件未触发时,用户空间进行 select()和 poll()系统调用将引起进程的阻塞。
aio_read()和 aio_write()函数分别对与文件描述符对应的设备进行异步读、写操作。设备实现这两个函数后,用户空间可以对该设备文件描述符调用 aio_read()、aio_write()等系统调用进行读写。
2.4 ioctl幻数
详见博文《linux字符设备驱动的 ioctl 幻数》。
2.5 使用文件私有数据
一般都将文件的私有数据 private_data 指向设备结构,read(),write(),ioctl(),llseek(),等函数通过private_data访问设备结构体。
体,read()、write()、ioctl()、llseek()等函数通过 private_data 访问设备结构体。
体,read()、write()、ioctl()、llseek()等函数通过 private_data 访问设备结构体。
2.6 支持一个 globalmem 设备的 globalmem 驱动
/*======================================================================
A globalmem driver as an example of char device drivers
The initial developer of the original code is Baohua Song
<author@linuxdriver.cn>. All Rights Reserved.
======================================================================*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#define GLOBALMEM_SIZE 0x1000 /*全局内存最大4K字节*/
//#define MEM_CLEAR 0x1 /*清0全局内存*/
A globalmem driver as an example of char device drivers
The initial developer of the original code is Baohua Song
<author@linuxdriver.cn>. All Rights Reserved.
======================================================================*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#define GLOBALMEM_SIZE 0x1000 /*全局内存最大4K字节*/
//#define MEM_CLEAR 0x1 /*清0全局内存*/
#define GLOBALMEM_MAGIC 'k'
#define MEM_CLEAR _IO(GLOBALMEM_MAGIC,0x1a)
#define GLOBALMEM_MAJOR 254 /*预设的globalmem的主设备号*/
static globalmem_major = GLOBALMEM_MAJOR;
/*globalmem设备结构体*/
struct globalmem_dev
{
struct cdev cdev; /*cdev结构体*/
unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/
};
struct globalmem_dev *globalmem_devp; /*设备结构体指针*/
/*文件打开函数*/
int globalmem_open(struct inode *inode, struct file *filp)
{
/*将设备结构体指针赋值给文件私有数据指针*/
filp->private_data = globalmem_devp;
return 0;
}
/*文件释放函数*/
int globalmem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* ioctl设备控制函数 */
static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
struct globalmem_dev *dev = filp->private_data;/*获得设备结构体指针*/
switch (cmd)
{
case MEM_CLEAR:
memset(dev->mem, 0, GLOBALMEM_SIZE);
printk(KERN_INFO "globalmem is set to zeron");
break;
default:
return - EINVAL;
}
return 0;
}
/*读函数*/
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,
loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
/*内核空间->用户空间*/
if (copy_to_user(buf, (void*)(dev->mem + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %dn", count, p);
}
return ret;
}
/*写函数*/
static ssize_t globalmem_write(struct file *filp, const char __user *buf,
size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
/*用户空间->内核空间*/
if (copy_from_user(dev->mem + p, buf, count))
ret = - EFAULT;
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %d bytes(s) from %dn", count, p);
}
return ret;
}
/* seek文件定位函数 */
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig)
{
case 0: /*相对文件开始位置偏移*/
if (offset < 0)
{
ret = - EINVAL;
break;
}
if ((unsigned int)offset > GLOBALMEM_SIZE)
{
ret = - EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case 1: /*相对文件当前位置偏移*/
if ((filp->f_pos + offset) > GLOBALMEM_SIZE)
{
ret = - EINVAL;
break;
}
if ((filp->f_pos + offset) < 0)
{
ret = - EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = - EINVAL;
break;
}
return ret;
}
/*文件操作结构体*/
static const struct file_operations globalmem_fops =
{
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
/*初始化并注册cdev*/
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err, devno = MKDEV(globalmem_major, index);
cdev_init(&dev->cdev, &globalmem_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &globalmem_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
/*设备驱动模块加载函数*/
int globalmem_init(void)
{
int result;
dev_t devno = MKDEV(globalmem_major, 0);
/* 申请设备号*/
if (globalmem_major)
result = register_chrdev_region(devno, 1, "globalmem");
else /* 动态申请设备号 */
{
result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
globalmem_major = MAJOR(devno);
}
if (result < 0)
return result;
/* 动态申请设备结构体的内存*/
globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
if (!globalmem_devp) /*申请失败*/
{
result = - ENOMEM;
goto fail_malloc;
}
memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
globalmem_setup_cdev(globalmem_devp, 0);
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
return result;
}
/*模块卸载函数*/
void globalmem_exit(void)
{
cdev_del(&globalmem_devp->cdev); /*注销cdev*/
kfree(globalmem_devp); /*释放设备结构体内存*/
unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); /*释放设备号*/
}
MODULE_AUTHOR("Song Baohua");
MODULE_LICENSE("Dual BSD/GPL");
module_param(globalmem_major, int, S_IRUGO);
module_init(globalmem_init);
module_exit(globalmem_exit);
#define MEM_CLEAR _IO(GLOBALMEM_MAGIC,0x1a)
#define GLOBALMEM_MAJOR 254 /*预设的globalmem的主设备号*/
static globalmem_major = GLOBALMEM_MAJOR;
/*globalmem设备结构体*/
struct globalmem_dev
{
struct cdev cdev; /*cdev结构体*/
unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/
};
struct globalmem_dev *globalmem_devp; /*设备结构体指针*/
/*文件打开函数*/
int globalmem_open(struct inode *inode, struct file *filp)
{
/*将设备结构体指针赋值给文件私有数据指针*/
filp->private_data = globalmem_devp;
return 0;
}
/*文件释放函数*/
int globalmem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* ioctl设备控制函数 */
static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
struct globalmem_dev *dev = filp->private_data;/*获得设备结构体指针*/
switch (cmd)
{
case MEM_CLEAR:
memset(dev->mem, 0, GLOBALMEM_SIZE);
printk(KERN_INFO "globalmem is set to zeron");
break;
default:
return - EINVAL;
}
return 0;
}
/*读函数*/
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,
loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
/*内核空间->用户空间*/
if (copy_to_user(buf, (void*)(dev->mem + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %dn", count, p);
}
return ret;
}
/*写函数*/
static ssize_t globalmem_write(struct file *filp, const char __user *buf,
size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
/*用户空间->内核空间*/
if (copy_from_user(dev->mem + p, buf, count))
ret = - EFAULT;
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %d bytes(s) from %dn", count, p);
}
return ret;
}
/* seek文件定位函数 */
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig)
{
case 0: /*相对文件开始位置偏移*/
if (offset < 0)
{
ret = - EINVAL;
break;
}
if ((unsigned int)offset > GLOBALMEM_SIZE)
{
ret = - EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case 1: /*相对文件当前位置偏移*/
if ((filp->f_pos + offset) > GLOBALMEM_SIZE)
{
ret = - EINVAL;
break;
}
if ((filp->f_pos + offset) < 0)
{
ret = - EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = - EINVAL;
break;
}
return ret;
}
/*文件操作结构体*/
static const struct file_operations globalmem_fops =
{
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
/*初始化并注册cdev*/
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err, devno = MKDEV(globalmem_major, index);
cdev_init(&dev->cdev, &globalmem_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &globalmem_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
/*设备驱动模块加载函数*/
int globalmem_init(void)
{
int result;
dev_t devno = MKDEV(globalmem_major, 0);
/* 申请设备号*/
if (globalmem_major)
result = register_chrdev_region(devno, 1, "globalmem");
else /* 动态申请设备号 */
{
result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
globalmem_major = MAJOR(devno);
}
if (result < 0)
return result;
/* 动态申请设备结构体的内存*/
globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
if (!globalmem_devp) /*申请失败*/
{
result = - ENOMEM;
goto fail_malloc;
}
memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
globalmem_setup_cdev(globalmem_devp, 0);
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
return result;
}
/*模块卸载函数*/
void globalmem_exit(void)
{
cdev_del(&globalmem_devp->cdev); /*注销cdev*/
kfree(globalmem_devp); /*释放设备结构体内存*/
unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); /*释放设备号*/
}
MODULE_AUTHOR("Song Baohua");
MODULE_LICENSE("Dual BSD/GPL");
module_param(globalmem_major, int, S_IRUGO);
module_init(globalmem_init);
module_exit(globalmem_exit);
除了在globalmem_open()函数中通过filp->private_data = globalmem_devp语句将设备结构体指针赋值给文件私有数据指针并在globalmem_read()、globalmem_write()、globalmem_llseek() 和 globalmem_ioctl() 函 数 中 通 过 struct globalmem_dev *dev =filp->private_data语句获得设备结构体指针并使用该指针操作设备结构体外。
如果 globalmem 不只包括一个设备,而是同时包括两个或两个以上的设备,采用 private_data 的优势就会显现出来。
2.7 支持两个 globalmem 设备的 globalmem 驱动
/*======================================================================
A globalmem driver as an example of char device drivers
There are two same globalmems in this driver
This example is to introduce the function of file->private_data
The initial developer of the original code is Baohua Song
<author@linuxdriver.cn>. All Rights Reserved.
======================================================================*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#define GLOBALMEM_SIZE 0x1000 /*全局内存最大4K字节*/
#define MEM_CLEAR 0x1 /*清0全局内存*/
#define GLOBALMEM_MAJOR 254 /*预设的globalmem的主设备号*/
static globalmem_major = GLOBALMEM_MAJOR;
/*globalmem设备结构体*/
struct globalmem_dev
{
struct cdev cdev; /*cdev结构体*/
unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/
};
struct globalmem_dev *globalmem_devp; /*设备结构体指针*/
/*文件打开函数*/
int globalmem_open(struct inode *inode, struct file *filp)
{
/*将设备结构体指针赋值给文件私有数据指针*/
struct globalmem_dev *dev;
dev = container_of(inode->i_cdev,struct globalmem_dev,cdev);
filp->private_data = dev;
return 0;
}
/*文件释放函数*/
int globalmem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* ioctl设备控制函数 */
static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
struct globalmem_dev *dev = filp->private_data;/*获得设备结构体指针*/
switch (cmd)
{
case MEM_CLEAR:
memset(dev->mem, 0, GLOBALMEM_SIZE);
printk(KERN_INFO "globalmem is set to zeron");
break;
default:
return - EINVAL;
}
return 0;
}
/*读函数*/
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,
loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
/*内核空间->用户空间*/
if (copy_to_user(buf, (void*)(dev->mem + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %dn", count, p);
}
return ret;
}
/*写函数*/
static ssize_t globalmem_write(struct file *filp, const char __user *buf,
size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
/*用户空间->内核空间*/
if (copy_from_user(dev->mem + p, buf, count))
ret = - EFAULT;
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %d bytes(s) from %dn", count, p);
}
return ret;
}
/* seek文件定位函数 */
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig)
{
case 0: /*相对文件开始位置偏移*/
if (offset < 0)
{
ret = - EINVAL;
break;
}
if ((unsigned int)offset > GLOBALMEM_SIZE)
{
ret = - EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case 1: /*相对文件当前位置偏移*/
if ((filp->f_pos + offset) > GLOBALMEM_SIZE)
{
ret = - EINVAL;
break;
}
if ((filp->f_pos + offset) < 0)
{
ret = - EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = - EINVAL;
break;
}
return ret;
}
/*文件操作结构体*/
static const struct file_operations globalmem_fops =
{
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
/*初始化并注册cdev*/
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err, devno = MKDEV(globalmem_major, index);
cdev_init(&dev->cdev, &globalmem_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &globalmem_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
/*设备驱动模块加载函数*/
int globalmem_init(void)
{
int result;
dev_t devno = MKDEV(globalmem_major, 0);
/* 申请设备号*/
if (globalmem_major)
result = register_chrdev_region(devno, 2, "globalmem");
else /* 动态申请设备号 */
{
result = alloc_chrdev_region(&devno, 0, 2, "globalmem");
globalmem_major = MAJOR(devno);
}
if (result < 0)
return result;
/* 动态申请2个设备结构体的内存*/
globalmem_devp = kmalloc(2*sizeof(struct globalmem_dev), GFP_KERNEL);
if (!globalmem_devp) /*申请失败*/
{
result = - ENOMEM;
goto fail_malloc;
}
memset(globalmem_devp, 0, 2*sizeof(struct globalmem_dev));
globalmem_setup_cdev(&globalmem_devp[0], 0);
globalmem_setup_cdev(&globalmem_devp[1], 1);
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
return result;
}
/*模块卸载函数*/
void globalmem_exit(void)
{
cdev_del(&(globalmem_devp[0].cdev));
cdev_del(&(globalmem_devp[1].cdev)); /*注销cdev*/
kfree(globalmem_devp); /*释放设备结构体内存*/
unregister_chrdev_region(MKDEV(globalmem_major, 0), 2); /*释放设备号*/
}
MODULE_AUTHOR("Song Baohua");
MODULE_LICENSE("Dual BSD/GPL");
module_param(globalmem_major, int, S_IRUGO);
module_init(globalmem_init);
module_exit(globalmem_exit);
A globalmem driver as an example of char device drivers
There are two same globalmems in this driver
This example is to introduce the function of file->private_data
The initial developer of the original code is Baohua Song
<author@linuxdriver.cn>. All Rights Reserved.
======================================================================*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#define GLOBALMEM_SIZE 0x1000 /*全局内存最大4K字节*/
#define MEM_CLEAR 0x1 /*清0全局内存*/
#define GLOBALMEM_MAJOR 254 /*预设的globalmem的主设备号*/
static globalmem_major = GLOBALMEM_MAJOR;
/*globalmem设备结构体*/
struct globalmem_dev
{
struct cdev cdev; /*cdev结构体*/
unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/
};
struct globalmem_dev *globalmem_devp; /*设备结构体指针*/
/*文件打开函数*/
int globalmem_open(struct inode *inode, struct file *filp)
{
/*将设备结构体指针赋值给文件私有数据指针*/
struct globalmem_dev *dev;
dev = container_of(inode->i_cdev,struct globalmem_dev,cdev);
filp->private_data = dev;
return 0;
}
/*文件释放函数*/
int globalmem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* ioctl设备控制函数 */
static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
struct globalmem_dev *dev = filp->private_data;/*获得设备结构体指针*/
switch (cmd)
{
case MEM_CLEAR:
memset(dev->mem, 0, GLOBALMEM_SIZE);
printk(KERN_INFO "globalmem is set to zeron");
break;
default:
return - EINVAL;
}
return 0;
}
/*读函数*/
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,
loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
/*内核空间->用户空间*/
if (copy_to_user(buf, (void*)(dev->mem + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %dn", count, p);
}
return ret;
}
/*写函数*/
static ssize_t globalmem_write(struct file *filp, const char __user *buf,
size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
/*用户空间->内核空间*/
if (copy_from_user(dev->mem + p, buf, count))
ret = - EFAULT;
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %d bytes(s) from %dn", count, p);
}
return ret;
}
/* seek文件定位函数 */
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig)
{
case 0: /*相对文件开始位置偏移*/
if (offset < 0)
{
ret = - EINVAL;
break;
}
if ((unsigned int)offset > GLOBALMEM_SIZE)
{
ret = - EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case 1: /*相对文件当前位置偏移*/
if ((filp->f_pos + offset) > GLOBALMEM_SIZE)
{
ret = - EINVAL;
break;
}
if ((filp->f_pos + offset) < 0)
{
ret = - EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = - EINVAL;
break;
}
return ret;
}
/*文件操作结构体*/
static const struct file_operations globalmem_fops =
{
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
/*初始化并注册cdev*/
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err, devno = MKDEV(globalmem_major, index);
cdev_init(&dev->cdev, &globalmem_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &globalmem_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
/*设备驱动模块加载函数*/
int globalmem_init(void)
{
int result;
dev_t devno = MKDEV(globalmem_major, 0);
/* 申请设备号*/
if (globalmem_major)
result = register_chrdev_region(devno, 2, "globalmem");
else /* 动态申请设备号 */
{
result = alloc_chrdev_region(&devno, 0, 2, "globalmem");
globalmem_major = MAJOR(devno);
}
if (result < 0)
return result;
/* 动态申请2个设备结构体的内存*/
globalmem_devp = kmalloc(2*sizeof(struct globalmem_dev), GFP_KERNEL);
if (!globalmem_devp) /*申请失败*/
{
result = - ENOMEM;
goto fail_malloc;
}
memset(globalmem_devp, 0, 2*sizeof(struct globalmem_dev));
globalmem_setup_cdev(&globalmem_devp[0], 0);
globalmem_setup_cdev(&globalmem_devp[1], 1);
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
return result;
}
/*模块卸载函数*/
void globalmem_exit(void)
{
cdev_del(&(globalmem_devp[0].cdev));
cdev_del(&(globalmem_devp[1].cdev)); /*注销cdev*/
kfree(globalmem_devp); /*释放设备结构体内存*/
unregister_chrdev_region(MKDEV(globalmem_major, 0), 2); /*释放设备号*/
}
MODULE_AUTHOR("Song Baohua");
MODULE_LICENSE("Dual BSD/GPL");
module_param(globalmem_major, int, S_IRUGO);
module_init(globalmem_init);
module_exit(globalmem_exit);
container_of()的作用是通过结构体成员的指针找到对应结 构 体 的 指 针 , 这 个 技 巧 在 Linux 内 核 编 程 中 十 分 常 用 。 在 container_of
(inode->i_cdev,struct globalmem_dev,cdev)语句中,传给 container_of()的第 1 个参数是结构体成员的指针,第 2 个参数为整个结构体的类型,第 3 个参数为传入的第 1 个参数即结构体成员的类型,container_of()返回值为整个结构体的指针。
(inode->i_cdev,struct globalmem_dev,cdev)语句中,传给 container_of()的第 1 个参数是结构体成员的指针,第 2 个参数为整个结构体的类型,第 3 个参数为传入的第 1 个参数即结构体成员的类型,container_of()返回值为整个结构体的指针。
2.8 验证
编译 globalmem 的驱动,得到 globalmem.ko 文件。运行“insmod globalmem.ko”命令加载模块,通过“lsmod”命令,发现 globalmem 模块已被加载。再通过“cat /proc/devices”命令查看,发现多出了主设备号为 254 的“globalmem”字符设备驱动,
如下所示:
[root@localhost driver_study]# cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 /dev/vc/0
4 tty
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
21 sg
29 fb
128 ptm
136 pts
171 ieee1394
180 usb
189 usb_device
254 globalmem
接下来,通过“mknod /dev/globalmem c 254 0”命令创建“/dev/globalmem”设备节点,并通过“echo 'hello world' > /dev/globalmem”命令和“cat /dev/globalmem”命令分别验证设备的写和读,结果证明“hello world”字符串被正确地写入 globalmem字符设备:
[root@localhost driver_study]# echo 'hello world' > /dev/globalmem
[root@localhost driver_study]# cat /dev/globalmem
hello world
如果启用了 sysfs 文件系统,将发现多出了/sys/module/globalmem 目录,该目录下
的树型结构如下所示:
|-- refcnt
'-- sections
|-- .bss
|-- .data
|-- .gnu.linkonce.this_module
|-- .rodata
|-- .rodata.str1.1
|-- .strtab
|-- .symtab
如下所示:
[root@localhost driver_study]# cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 /dev/vc/0
4 tty
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
21 sg
29 fb
128 ptm
136 pts
171 ieee1394
180 usb
189 usb_device
254 globalmem
接下来,通过“mknod /dev/globalmem c 254 0”命令创建“/dev/globalmem”设备节点,并通过“echo 'hello world' > /dev/globalmem”命令和“cat /dev/globalmem”命令分别验证设备的写和读,结果证明“hello world”字符串被正确地写入 globalmem字符设备:
[root@localhost driver_study]# echo 'hello world' > /dev/globalmem
[root@localhost driver_study]# cat /dev/globalmem
hello world
如果启用了 sysfs 文件系统,将发现多出了/sys/module/globalmem 目录,该目录下
的树型结构如下所示:
|-- refcnt
'-- sections
|-- .bss
|-- .data
|-- .gnu.linkonce.this_module
|-- .rodata
|-- .rodata.str1.1
|-- .strtab
|-- .symtab
|-- .text
'-- _ _versions
refcnt 记录了 globalmem 模块的引用计数,sections 下包含的多个文件则给出了globalmem所包含的 BSS、数据段和代码段等的地址及其他信息。
对于代码清单 6.18 给出的支持两个 globalmem设备的驱动,在加载模块后需创建两个设备节点,/dev/globalmem0 对应主设备号 globalmem_major,次设备号 0,/dev/globalmem1 对 应 主 设 备 号 globalmem_major , 次 设 备 号 1 。 分 别 读 写/dev/globalmem0 和/dev/globalmem1,发现都读写到了正确的对应设备。
'-- _ _versions
refcnt 记录了 globalmem 模块的引用计数,sections 下包含的多个文件则给出了globalmem所包含的 BSS、数据段和代码段等的地址及其他信息。
对于代码清单 6.18 给出的支持两个 globalmem设备的驱动,在加载模块后需创建两个设备节点,/dev/globalmem0 对应主设备号 globalmem_major,次设备号 0,/dev/globalmem1 对 应 主 设 备 号 globalmem_major , 次 设 备 号 1 。 分 别 读 写/dev/globalmem0 和/dev/globalmem1,发现都读写到了正确的对应设备。
3.说明
本文是学习《Linux 设备驱动开发详解》字符驱动部分的笔记。
最后
以上就是淡定自行车为你收集整理的linux2.6驱动学习笔记之字符驱动的全部内容,希望文章能够帮你解决linux2.6驱动学习笔记之字符驱动所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复