基本字符设备的编写及代码分析
2014-10-10 19:08
134 查看
* 说明:用于演示一个最基本的字符设备驱动程序框架。
* 实现一个字符设备驱动的步骤:
* 1、实现模块代码框架
* 2、申请设备号,这个设备号由主、次设备号组成,是应用程序
* 通过文件访问设备的关键所在。在类unix系统中,一切设备
* 皆文件(网卡设备除外),而一个文件和一个inode对应,应用
* 层创建设备节点使用的mknod命令其实就是为了能够创建一个能
* 够代表设备的一个inode。应用程序通过路径名访问设备,但是
* 这个路径名会最终装换到对应的inode上,在创建设备节点时,
* 明确给出了主、次设备号,也即是通过路径名得到inode,通过
* inode得到了主、次设备号,最后通过主设备号查找到对应的设
* 备驱动程序,或者说是查找到了对应的cdev。
* 3、初始化一个代表字符设备的cdev对象,主要实现了cdev内部成员
* 的初始化和cdev同file_operations的关联,这样找到cdev就能
* 找到对应的操作方法集,从而实现设备的打开、关闭、读、写等
* 操作。
* 4、添加(或注册)cdev到内核,主要实现了将代表设备的cdev对象
* 的指针保存到内核中的相应数据结构中。另外,还实现了cdev和
* 主、次设备号的关联,这是通过设备号能找到对应cdev的关键所
* 在。
* 5、实现操作方法集中需要实现的操作,并非所有调用接口都要实现,
* 根据设备的具体情况而定。
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include "ioctl.h"
#define FSDEV_MAJOR
250
#define FSDEV_MINOR
0
#define FSDEV_NR 1
#define FSDEV_NAME "fsdev"
/* 可以将该结构体看作是继承于cdev父类的一个子类 */
struct fsdev {
struct cdev cdev;
/* 代表字符设备的一个抽象父类 */
unsigned char buf[256]; /* 设备相关的一些资源,封装的概念 */
};
static struct fsdev fsdev; /* 实例化一个对象 */
/* 该函数通常用来实现对设备的前期初始化,如初始化一些关键寄存器,
* 使设备处于待工作状态。和具体的设备密切相关
*/
static int fschr_open(struct inode *inode, struct file *filp)
{
return 0;
}
/* 该函数通常用来实现和open操作相反的操作 */
static int fschr_close(struct inode *inode, struct file *filp)
{
return 0;
}
/* 从设备中获取数据,和具体的设备密切相关 */
static ssize_t fschr_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
int ret;
int len;
/* 注意要对参数进行严格的检查,驱动程序的崩溃都可能会导致整个系统的崩溃 */
len = count > 256 ? 256 : count;
/* 不同层之间的拷贝,用特殊的拷贝函数,注意该函数返回的是没有拷贝完成的字节数 */
ret = copy_to_user(buf, fsdev.buf, len);
return len - ret;
}
/* 写数据到设备,和具体的设备密切相关 */
static ssize_t fschr_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
int ret;
int len;
len = count > 256 ? 256 : count;
ret = copy_from_user(fsdev.buf, buf, len);
return len - ret;
}
static int fschr_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret = 0;
/* 严格的参数检测,有利于系统的稳定 */
if (_IOC_TYPE(cmd) != FS_IOC_MAGIC)
return -ENOTTY;
if (_IOC_NR(cmd) > FS_IOC_MAXNR)
return -ENOTTY;
if (_IOC_DIR(cmd) & _IOC_READ)
/* 注意,这里的VERIFY_WRITE是针对应用层arg传递过来的指针而言的,应用层
* 的读操作恰恰是内核层对该指针所指向内存的写操作
*/
ret = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
ret = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (ret)
return -EFAULT;
switch (cmd) {
/* 命令字的定义推荐严格按照Linux的要求进行 */
case FS_IOC_SET_BUF:
memset(fsdev.buf, *(char *)arg, sizeof(fsdev.buf));
break;
}
return 0;
}
static struct file_operations fsops = {
/* 结构体中特定成员赋初值,前面加. */
.owner = THIS_MODULE,
.open = fschr_open,
.release = fschr_close,
.read = fschr_read,
.write = fschr_write,
.ioctl = fschr_ioctl,
};
static int __init fschr_init(void)
{
int ret;
dev_t devno;
/* 推荐使用该宏来生成设备号,因为内核对设备号的定义可能会变 */
devno = MKDEV(FSDEV_MAJOR, FSDEV_MINOR);
/* 静态的设备号申请,申请成功后可以通过cat /proc/devices命令查看 */
ret = register_chrdev_region(devno, FSDEV_NR, FSDEV_NAME);
if (ret) {
printk(KERN_ERR "fschr: register chrdev region failed\n");
goto reg_err;
}
memset(&fsdev, 0, sizeof(struct fsdev));
/* 初始化cdev对象,并和操作方法集关联 */
cdev_init(&fsdev.cdev, &fsops);
/* THIS_MODULE相当于this指针,指向驱动所在的模块,用于防止驱动在使用时,
* 模块被卸载
*/
fsdev.cdev.owner = THIS_MODULE;
/* 注册cdev到内核,并和设备号关联 */
ret = cdev_add(&fsdev.cdev, devno, FSDEV_NR);
if (ret) {
printk(KERN_ERR "fschr: add cdev failed\n");
goto add_err;
}
return 0;
add_err:
unregister_chrdev_region(devno, FSDEV_NR);
reg_err:
return ret;
}
static void __exit fschr_exit(void)
{
dev_t devno;
devno = MKDEV(FSDEV_MAJOR, FSDEV_MINOR);
cdev_del(&fsdev.cdev);
unregister_chrdev_region(devno, FSDEV_NR);
}
module_init(fschr_init);
module_exit(fschr_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Kevin Jiang <coreteker@gmail.com>");
MODULE_DESCRIPTION("This is an example for linux char driver");
* 实现一个字符设备驱动的步骤:
* 1、实现模块代码框架
* 2、申请设备号,这个设备号由主、次设备号组成,是应用程序
* 通过文件访问设备的关键所在。在类unix系统中,一切设备
* 皆文件(网卡设备除外),而一个文件和一个inode对应,应用
* 层创建设备节点使用的mknod命令其实就是为了能够创建一个能
* 够代表设备的一个inode。应用程序通过路径名访问设备,但是
* 这个路径名会最终装换到对应的inode上,在创建设备节点时,
* 明确给出了主、次设备号,也即是通过路径名得到inode,通过
* inode得到了主、次设备号,最后通过主设备号查找到对应的设
* 备驱动程序,或者说是查找到了对应的cdev。
* 3、初始化一个代表字符设备的cdev对象,主要实现了cdev内部成员
* 的初始化和cdev同file_operations的关联,这样找到cdev就能
* 找到对应的操作方法集,从而实现设备的打开、关闭、读、写等
* 操作。
* 4、添加(或注册)cdev到内核,主要实现了将代表设备的cdev对象
* 的指针保存到内核中的相应数据结构中。另外,还实现了cdev和
* 主、次设备号的关联,这是通过设备号能找到对应cdev的关键所
* 在。
* 5、实现操作方法集中需要实现的操作,并非所有调用接口都要实现,
* 根据设备的具体情况而定。
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include "ioctl.h"
#define FSDEV_MAJOR
250
#define FSDEV_MINOR
0
#define FSDEV_NR 1
#define FSDEV_NAME "fsdev"
/* 可以将该结构体看作是继承于cdev父类的一个子类 */
struct fsdev {
struct cdev cdev;
/* 代表字符设备的一个抽象父类 */
unsigned char buf[256]; /* 设备相关的一些资源,封装的概念 */
};
static struct fsdev fsdev; /* 实例化一个对象 */
/* 该函数通常用来实现对设备的前期初始化,如初始化一些关键寄存器,
* 使设备处于待工作状态。和具体的设备密切相关
*/
static int fschr_open(struct inode *inode, struct file *filp)
{
return 0;
}
/* 该函数通常用来实现和open操作相反的操作 */
static int fschr_close(struct inode *inode, struct file *filp)
{
return 0;
}
/* 从设备中获取数据,和具体的设备密切相关 */
static ssize_t fschr_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
int ret;
int len;
/* 注意要对参数进行严格的检查,驱动程序的崩溃都可能会导致整个系统的崩溃 */
len = count > 256 ? 256 : count;
/* 不同层之间的拷贝,用特殊的拷贝函数,注意该函数返回的是没有拷贝完成的字节数 */
ret = copy_to_user(buf, fsdev.buf, len);
return len - ret;
}
/* 写数据到设备,和具体的设备密切相关 */
static ssize_t fschr_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
int ret;
int len;
len = count > 256 ? 256 : count;
ret = copy_from_user(fsdev.buf, buf, len);
return len - ret;
}
static int fschr_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret = 0;
/* 严格的参数检测,有利于系统的稳定 */
if (_IOC_TYPE(cmd) != FS_IOC_MAGIC)
return -ENOTTY;
if (_IOC_NR(cmd) > FS_IOC_MAXNR)
return -ENOTTY;
if (_IOC_DIR(cmd) & _IOC_READ)
/* 注意,这里的VERIFY_WRITE是针对应用层arg传递过来的指针而言的,应用层
* 的读操作恰恰是内核层对该指针所指向内存的写操作
*/
ret = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
ret = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (ret)
return -EFAULT;
switch (cmd) {
/* 命令字的定义推荐严格按照Linux的要求进行 */
case FS_IOC_SET_BUF:
memset(fsdev.buf, *(char *)arg, sizeof(fsdev.buf));
break;
}
return 0;
}
static struct file_operations fsops = {
/* 结构体中特定成员赋初值,前面加. */
.owner = THIS_MODULE,
.open = fschr_open,
.release = fschr_close,
.read = fschr_read,
.write = fschr_write,
.ioctl = fschr_ioctl,
};
static int __init fschr_init(void)
{
int ret;
dev_t devno;
/* 推荐使用该宏来生成设备号,因为内核对设备号的定义可能会变 */
devno = MKDEV(FSDEV_MAJOR, FSDEV_MINOR);
/* 静态的设备号申请,申请成功后可以通过cat /proc/devices命令查看 */
ret = register_chrdev_region(devno, FSDEV_NR, FSDEV_NAME);
if (ret) {
printk(KERN_ERR "fschr: register chrdev region failed\n");
goto reg_err;
}
memset(&fsdev, 0, sizeof(struct fsdev));
/* 初始化cdev对象,并和操作方法集关联 */
cdev_init(&fsdev.cdev, &fsops);
/* THIS_MODULE相当于this指针,指向驱动所在的模块,用于防止驱动在使用时,
* 模块被卸载
*/
fsdev.cdev.owner = THIS_MODULE;
/* 注册cdev到内核,并和设备号关联 */
ret = cdev_add(&fsdev.cdev, devno, FSDEV_NR);
if (ret) {
printk(KERN_ERR "fschr: add cdev failed\n");
goto add_err;
}
return 0;
add_err:
unregister_chrdev_region(devno, FSDEV_NR);
reg_err:
return ret;
}
static void __exit fschr_exit(void)
{
dev_t devno;
devno = MKDEV(FSDEV_MAJOR, FSDEV_MINOR);
cdev_del(&fsdev.cdev);
unregister_chrdev_region(devno, FSDEV_NR);
}
module_init(fschr_init);
module_exit(fschr_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Kevin Jiang <coreteker@gmail.com>");
MODULE_DESCRIPTION("This is an example for linux char driver");
相关文章推荐
- LDD3源码分析之字符设备驱动程序(加上测试代码)
- Linux字符设备驱动编写基本流程
- Linux字符设备驱动程序编写基本流程
- Linux字符设备驱动程序编写基本流程
- 设备驱动学习之字符设备驱动内核代码分析(二)——字符设备结构体cdev
- Linux字符设备驱动程序编写基本流程
- 字符设备驱动模块与测试代码编写。
- Linux字符设备驱动程序编写基本流程
- 宋宝华 《Linux设备驱动开发详解》示例代码之基本字符设备驱动
- 代码:编写一个简单的字符设备驱动
- 字符设备驱动代码完整分析
- 第12课第4.3节 字符设备驱动程序之中断方式的按键驱动_编写代码
- Linux字符设备驱动程序编写基本流程
- 设备驱动学习之字符设备驱动内核代码分析(一)——设备号申请接口
- 字符设备驱动浅析(内核代码分析)
- 字符设备驱动高级篇2——字符设备驱动注册代码分析
- 批处理输入密码但不显示字符的代码---分析
- 编写代码实现从输入流中分析出数字串(C++)
- 编写代码实现从输入流中分析出数字串(C++)
- 计算机笔试题:写一个函数,检查字符是否是整数,如果是,返回其整数值。(或者:怎样只用4行代码编写出一个从字符串到长整型的函数)