一个简单字符型设备驱动及其测试
驱动对一些人来说很难,而对一些人来说很容易。窃以为,理解简单设备驱动模型不难,深入理解并与Linux内核设计联系到一起需要花费时间。对于移植者来说,如何将自己自定义的模块天衣无缝放到内核中,是比较重要的——不过,有许多现在的“模板”可供参考,总算不用白手起家。
本文所述的仅是一个独立的、简单的字符型设备驱动。最简单的字符设备当属经典的“Hello World”。而这个比它复杂一点:在用户空间写一字符串到内核空间,再将其写到用户空间。从应用层角度来看,先写一字符串到内核,再读这个字符串。
先说测试程序,代码如下:
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#define device "/dev/bar"
int main(void)
{
int fd;
char buf[] = "Late Lee";
char buf2[30]={0};
int len;
fd = open(device, O_RDWR);
if (fd < 0)
{
perror("Open device faile!");
return -1;
}
len = write(fd, buf, sizeof(buf));
printf("buf: %s %d/n", buf, len);
len = read(fd, buf2, 25); // 由此指定读取数据,可大可小,但是驱动只读取这个指定的(大者读实际值),并返回
printf("buf2: %s %d/n", buf2, len);
close(fd);
return 0;
}
代码很简单,使用open打开设备,调用write将buf的东西写到由fd指定的设备(文件)中。之后再使用read读到buf2中,打印字符串,最后关闭设备。就这么简单。
实际运行结果如下:
# ./a.out
buf: Late Lee 9
buf2: The voice from hell: Late 25
其中“The voice from hell:”仅仅表明这个字符来自驱动,无实际意义。
驱动程序也很简单,代码如下:
Late Lee from http://www.latelee.org
简单的字符型设备驱动
从应用层获取一数据,再复制到应用层(在前面添加字符串)。
注册设备号及设备号的几个宏,均系ldd3例子scull。
何处释放data更好?看ldd3,似乎exit中更好。
2011-04-29 & 2011-05-06
*************************************************************************/
#include <linux/module.h>
#include <linux/kernel.h> /**< printk() */
#include <linux/init.h>
#include <linux/cdev.h> /**< cdev_* */
#include <linux/fs.h>
#include <asm/uaccess.h> /**< copy_*_user */
#include <linux/types.h> /**< size_t */
#include <linux/errno.h> /**< error codes */
#include <linux/string.h>
#ifdef DEBUG /* define it in Makefile or somewhere */
/* KERN_INFO */
#define debug(fmt, ...) printk(KERN_DEBUG fmt, ##__VA_ARGS__)
#else
#define debug(fmt, ...)
#endif
#define DEV_NAME "foo"
//#define HAVE_MAJOR
#ifdef HAVE_MAJOR
#define DEVICE_MAJOR 248
#else /* auto alloc */
#define DEVICE_MAJOR 0
#endif /* HAVE_MAJOR */
#define FOO_NR_DEVS 1
struct cdev foo_cdev;
int foo_major = DEVICE_MAJOR;
int foo_minor = 0;
int foo_nr_devs = FOO_NR_DEVS;
dev_t devno;
char *data;
static int foo_open(struct inode *inode, struct file *filp)
{
debug("in %s()/n", __func__);
return 0;
}
static int foo_release(struct inode *inode, struct file *filp)
{
debug("in %s()/n", __func__);
//kfree(data);
//data = NULL;
return 0;
}
static ssize_t foo_read(struct file *filp, char *buf, size_t count, loff_t *f_ops)
{
int len;
char *tmp;
if (count < 0)
return -EINVAL;
tmp = (char *)kmalloc(sizeof(char) * (count+1), GFP_KERNEL); // ?? here
sprintf(tmp, "The voice from hell: %s", data);
len = strlen(tmp);
if (len < count)
count = len;
if ( copy_to_user(buf, tmp, count) )
return -EFAULT;
debug("in %s() tmp: %s/n", __func__, tmp);
debug("in %s() buf: %s/n", __func__, buf);
debug("in %s() data: %s/n", __func__, data);
kfree(tmp);
return count;
}
static ssize_t foo_write(struct file *filp, const char *buf, size_t count, loff_t *f_ops)
{
if (count < 0)
return -EINVAL;
data = (char *)kmalloc(sizeof(char) * (count+1), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
if (copy_from_user(data, buf, count+1))
return -EFAULT;
debug("in %s() buff: %s/n", __func__, buf);
debug("in %s() data: %s/n", __func__, data);
return count;
}
static int foo_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd)
{
default:
return -EINVAL;
}
return 0;
}
static struct file_operations foo_fops = {
.owner = THIS_MODULE,
.open = foo_open,
.release = foo_release,
.read = foo_read,
.write = foo_write,
.ioctl = foo_ioctl,
};
static int __init foo_init(void)
{
int ret = -1;
cdev_init(&foo_cdev, &foo_fops);
foo_cdev.owner = THIS_MODULE;
/* register to who? */
if (foo_major)
{
devno = MKDEV(foo_major, foo_minor);
ret = register_chrdev_region(devno, foo_nr_devs, DEV_NAME);
}
else
{
ret = alloc_chrdev_region(&devno, foo_minor, foo_nr_devs, DEV_NAME); /* get devno */
foo_major = MAJOR(devno); /* get major */
}
if (ret < 0)
{
debug(" %s can't get major %d/n", DEV_NAME, foo_major);
return -EINVAL;
}
ret = cdev_add(&foo_cdev, devno, 1);
if (ret < 0)
{
debug(" %s cdev_add failure!/n", DEV_NAME);
return -EINVAL;
}
debug("%s init ok!/n", DEV_NAME);
return 0;
}
static void __exit foo_exit(void)
{
if (data != NULL)
kfree(data);
unregister_chrdev_region(devno, 1);
cdev_del(&foo_cdev);
debug("%s exit ok!/n", DEV_NAME);
}
module_init(foo_init);
module_exit(foo_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Chiangchin Li");
其中最后两个函数foo_init和foo_exit分别在加载驱动和卸载驱动时执行。如果驱动程序直接编入内核,可以在启动信息中看到,如果是单独加载,在执行insmod时执行,当然,在何处显示与printk打印级别有关系。
我们的驱动中使用KERN_DEBUG,因此可以使用dmesg查看——注意,由于insmod、rmmod等等需要使用root权限,文中统一使用root,shell提示符为“#”。
# dmesg | tail
……
foo init ok!
当执行应用层的测试程序时,dmesg的显示如下:
buf: Late Lee 9
buf2: The voice from hell: Late 25
# dmesg | tail
……
foo init ok!
in foo_open()
in foo_write() buff: Late Lee
in foo_write() data: Late Lee
in foo_read() tmp: The voice from hell: Late Lee
in foo_read() buf: The voice from hell: Late
in foo_read() data: Late Lee
in foo_release()
可以看到,当执行close时,驱动中执行foo_release。当卸载模块时,dmesg显示如下:
# dmesg | tail
……
foo init ok!
in foo_open()
in foo_write() buff: Late Lee
in foo_write() data: Late Lee
in foo_read() tmp: The voice from hell: Late Lee
in foo_read() buf: The voice from hell: Late
in foo_read() data: Late Lee
in foo_release()
foo exit ok!
对照测试程序及驱动显示的调试信息,可以看到应用层调用的如open、write、read、close等等系统调用,在驱动中均在对应的函数,它正是通过如下结构体来实现的:
.owner = THIS_MODULE,
.open = foo_open,
.release = foo_release,
.read = foo_read,
.write = foo_write,
.ioctl = foo_ioctl,
};
由于程序比较简单,不作注释和解释,一切尽在代码中。本文中的“模块”、“驱动”在某种意义上是同义词。
本文完整的“工程”代码可以在下面地址下载,为确保下载的东西的确是本文所提及的代码,最好确认一下它的md5和:deb16fcfbdc5e7f136ae988a0a26aad3
工程:简单字符型设备驱动例子-来自latelee.org
一个简单字符型设备驱动及其测试相关推荐
- linux 字符设备驱动测试,一个简单字符型设备驱动及其测试
驱动对一些人来说很难,而对一些人来说很容易.窃以为,理解简单设备驱动模型不难,深入理解并与Linux内核设计联系到一起需要花费时间.对于移植者来说,如何将自己自定义的模块天衣无缝放到内核中,是比较重要 ...
- 通过内存模拟硬盘实现一个简单的块设备驱动
本文的主要工作是通过硬盘来模拟内存,按照块设备驱动编程的框架实现一个简单的块设备驱动程序. 一.前期的准备工作 1.基本开发环境 Linux内核版本:Linux-3.4.10 开发板 : JZ2440 ...
- linux内核定义注册设备,linux字符型设备驱动 一.注册设备并创建设备文件
1.字符设备 字符设备.字符设备驱动与用户空间访问该设备的程序三者之间的关系 Linux内核中: a -- 使用cdev结构体来描述字符设备; b -- 通过其成员dev_t来定义设备号(分为主.次设 ...
- [设备驱动] 最简单的内核设备驱动--字符驱动
[设备驱动] 最简单的内核设备驱动--字符驱动 概要: x86平台上(linux-2.6.34.14;Linux debian 3.2.0-3-686-pae)编写一个256字节的字符驱动程序.在/ ...
- 《maven实战》笔记(2)----一个简单maven项目的搭建,测试和打包
参照<maven实战>在本地创建对应的基本项目helloworld,在本地完成后项目结构如下: 可以看到maven项目的骨架: src/main/java(javaz主代码) src/te ...
- 非即插即用型设备驱动的加载过程
非即插即用型设备驱动的加载过程 1. 非PnP总线驱动在系统启动时通过扫描注册表发现非PnP设备的存在,并向OS报告ID信息.(例如根总线驱动通过扫描 HKLM\ SYSTEM\ CurrentCon ...
- linux misc device字符杂项设备驱动
杂项设备也是在嵌入式系统中用得比较多的一种设备驱动.miscdevice共享一个主设备号MISC_MAJOR(即10),但次设备号不同.misc设备其实就是特殊的字符设备,主设备编号采用10,并且可自 ...
- 最简单的scull设备驱动
目录 一. 实验题目:设备管理与驱动编程 二. 实验目的 三. 实验内容 四. 实验过程及结果 1)关键代码 2)实验过程 第一部分 内核外编译: 第二部分 源码树内编译 五. 未解决的问题 附实验源 ...
- 使用内核定时器的second字符设备驱动及测试代码
驱动: #include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #inc ...
最新文章
- 打工人的一把辛酸泪,网站提现为啥多于800要扣20%的税?我想这些东西你需要明白
- 用中断的方式实现delay延时的一点注释
- python __repr__
- 山寨笔记本电脑风暴要来了
- 基站的小区号256变换
- 【每晚20点红包雨】2018天猫聚划算99大促欢聚盛典活动介绍
- java chmod 777_chmod 777 修改权限
- BZOJ 3699 GAL的数组
- CSDN 博客被自己误删了怎么办---(联系QQ客服)
- ajax请求是宏任务还是微任务_好程序员web前端学习路线分享了解AJAX是什么
- 【javaweb简单教程】1.搭建Web环境、初识JSP
- Kubernetes kube-proxy 如何与 iptables 完美配合使用
- 27. OP-TEE驱动篇----libteec接口在驱动中的实现
- 单片机总结及实训QY-BC12
- 计算机应用知识试题,《计算机应用基础》试题及答案
- 企业如何有效利用PLM系统(上)
- CQUPT Java 随堂测试1
- 【高精度定位】RTK定位与RTD定位知识科普
- 计算机组成原理 赖晓铮,计算机组成原理实验 2.5 运算器 赖晓铮
- uni-app getLocation:fail [geolocation:7]KEY错误 解决办法
热门文章
- 页面自动刷新,页面自动跳转
- 半夜“逃离上海”?阿里副总裁贾扬清回应:正常差旅回去看病 请不要误解
- 消息称百度网盘青春版降速23倍:从52MB/s降至2.2MB/s
- 转转集团二手双11大促:长沙用户“秒杀”99新iPhone12成首单
- 谷歌Pixel 6系列正式发布:搭载自研Tensor SoC 规格超骁龙888
- 格罗方德今年有望提高汽车芯片产量,并将投入60亿美元扩大产能
- iQOO Neo5搭载66W超快闪充:30分钟回血!
- 锤子手机成绝唱了,网友微博喊话罗永浩:赶快买回来吧!
- 三星电子预计第四季度营业利润为9万亿韩元 低于分析师预期
- Redmi K30S更多细节曝光:骁龙865加持 提供多款配色