字符
回帖(1)
2021-12-23 14:49:11
一、实验目的
- 掌握简单字符设备驱动程序编写方法。
- 编写应用程序对驱动程序进行测试,学习应用程序与驱动程序之间的调用过程。
二、实验环境
ubuntu 12.04 内核3.2.14
三、实验内容及实验原理
写一个简单的字符设备驱动程序,要求:
- 定义一个全局结构指针,初始值为NULL,该数据结构中包含一个大小为1024的buffer和一个count整形变量
- 在open中对该全局结构进行NULL判断,为NULL则为其分配内存,并将buffer初始化为0,将count自加
- 在release中如果count为0,则释放,否则进行count自减
- 在read里面对该buffer进行读取
- 在write里面对该buffer进行赋值(任意赋值即可)
写测试程序进行测试
四、实验结果及其分析
1.编译模块(设备驱动程序)
(1)创建模块文件xxx.c
gedit rwbuf.c
区分版本:这里不同,低版本内核2.6以下的是ioctl,高版本内核2.6及其以上的是unlocked_ioctl。
// 模块
#include
// 内核
#include
// struct file_operations
#include
// copy_to_user() & copy_from_user
#include
// 不行,用于memset和strcpy
#include
// kmalloc和kfree
#include
// rwbuf.c, driver for virtual char-device
#define RW_CLEAR 0x123
// 设备名
#define DEVICE_NAME "rwbuf"
// buffer数组的最大长度
#define RWBUF_MAX_SIZE 1024
// 设备中存储信息的全局结构体指针
typedef struct Data
{
// 用于打开和释放的验证,0表示未使用,1表示已使用
int count;
// 表示字符串
char buffer[RWBUF_MAX_SIZE];
} Data, *DataPtr;
DataPtr myDataPtr = NULL;
// 设备的打开:只返回0表示成功无法表示报错
int rwbuf_open(struct inode *inode, struct file *file)
{
// 如果未被使用
if (myDataPtr == NULL)
{
// 分配堆
myDataPtr = (DataPtr)kmalloc(sizeof(Data), GFP_KERNEL);
// 字符数组清零
memset(myDataPtr->buffer, 0, sizeof(myDataPtr->buffer));
// 初始化为1,表示有人在使用了
myDataPtr->count = 1;
printk("[rwbuf_open-kmalloc-success]myDataPtr->count = %d.n", myDataPtr->count);
return 0;
}
// 已被使用
else
{
// 增加一个用户
myDataPtr->count += 1;
printk("[rwbuf_open-countup]myDataPtr->count = %d.n", myDataPtr->count);
return 0;
}
}
// 设备的关闭:返回-1表示错误;返回0表示成功
int rwbuf_release(struct inode *inode, struct file *file)
{
// 当未打开时,报错
if (myDataPtr == NULL)
{
printk("[rwbuf_release-error]n");
return -1;
}
// 当有用户使用时,则自减
else
{
myDataPtr->count -= 1;
printk("[rwbuf_release-countdown]myDataPtr->count = %d.n", myDataPtr->count);
// 当没用用户使用时,释放
if (myDataPtr->count == 0)
{
// 释放堆区
kfree(myDataPtr);
// 还要让指针回归NULL,不要野指针
myDataPtr = NULL;
printk("[rwbuf_release-free-success]n");
return 0;
}
return 0;
}
}
// 设备的读操作:返回-1表示错误;返回(0, RWBUF_MAX_SIZE]表示成功
ssize_t rwbuf_read(struct file *file, char *buf, size_t count, loff_t *f_pos)
{
// 判断读取的长度是否有效
if (strlen(myDataPtr->buffer) > 0 && strlen(myDataPtr->buffer) <= RWBUF_MAX_SIZE)
{
// 从内核空间rwbuf复制到用户空间buf
copy_to_user(buf, myDataPtr->buffer, count);
printk("[rwbuf_read-success]the size of myDataPtr->buffer after read = %dn", strlen(myDataPtr->buffer));
return count;
}
else
{
printk("[rwbuf_read-error] strlen(myDataPtr->buffer) = %dn", strlen(myDataPtr->buffer));
return -1;
}
}
// 设备的写操作接:返回-1表示错误;返回(0, RWBUF_MAX_SIZE]表示成功
ssize_t rwbuf_write(struct file *file, const char *buf, size_t count, loff_t *f_pos)
{
// 判断写入的长度是否有效
if (count > 0 && count <= RWBUF_MAX_SIZE)
{
// 从用户空间buf复制到内核空间rwbuf
copy_from_user(myDataPtr->buffer, buf, count);
printk("[rwbuf_write-success] the size of myDataPtr->buffer after write = %dn", strlen(myDataPtr->buffer));
return count;
}
else
{
printk("[rwbuf_write-error] length of string = %dn", count);
return -1;
}
}
// 设备的ioctl:返回-1表示错误;返回0表示成功
long rwbuf_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
printk("[RW_CLEAR:%x],[cmd:%x]n", RW_CLEAR, cmd);
// 命令
if (cmd == RW_CLEAR)
{
// 表示清零
memset(myDataPtr->buffer, 0, sizeof(myDataPtr->buffer));
printk("[rwbuf_ioctl-success] the size of myDataPtr->buffer after ioctl = %dn", strlen(myDataPtr->buffer));
return 0;
}
// 无此命令时
else
{
printk("[rwbuf_ioctl-error] the size of myDataPtr->buffer after ioctl = %dn", strlen(myDataPtr->buffer));
return -1;
}
}
// rwbuf_fops要在rwf_buf_init()前面声明,因为register_chrdev()函数要使用到
static struct file_operations rwbuf_fops =
{
open : rwbuf_open,
release : rwbuf_release,
read : rwbuf_read,
write : rwbuf_write,
unlocked_ioctl : rwbuf_ioctl
};
// module_init()内的初始化函数:返回-1表示错误;返回0表示成功
static int __init rwbuf_init()
{
// 表示注册成功与否:-1表示失败,0表示成功(同register_chrdev返回值)。初始化为-1
int ret = -1;
/*
参数1:设备的种类,即主设备号
参数2:设备的名称
参数3:和VFS对接的接口,即上面的结构体变量
*/
ret = register_chrdev(60, DEVICE_NAME, &rwbuf_fops);
// 注册失败
if (ret == -1)
{
printk("[rwbuf_init-register-failed]n");
}
// 注册成功
else
{
printk("[rwbuf_init-register-success]n");
}
// 返回ret(同register_chrdev返回值)
return ret;
}
// module_exit()内的退出函数。
static void __exit rwbuf_exit()
{
unregister_chrdev(60, DEVICE_NAME);
printk("[rwbuf_exit-unregister-success]n");
}
// 内核模块入口,相当于main()函数,完成模块初始化
module_init(rwbuf_init);
// 卸载时调用的函数入口,完成模块卸载
module_exit(rwbuf_exit);
// GPL协议证书
MODULE_LICENSE("GPL");
内核模块入口,相当于main()函数,完成模块初始化module_init(rwbuf_init);// 卸载时调用的函数入口,完成模块卸载module_exit(rwbuf_exit);// GPL协议证书MODULE_LICENSE("GPL"); 意思:
结构体static struct file_operations rwbuf_fops是定义函数指针
比如将open指向rwbuf_open,我们调用的时候就打open就是调用rwbuf_open。其中ioctl特殊,调用的时候用ioctl才对,用unlocked_ioctl反而不对,而且定义成旧版本的ioctl,对高版本内核更不对(编译不报错,但读进入的数cmd就成一个莫名其妙的8位十六进制数)。
结构体rwbuf_fops在rwf_buf_init_module()中被用到,生效对结构体变量的操作。
if(register_chrdev(60, DEVICE_NAME, &rwbuf_fops))
(2)Makefile
gedit Makefile
内容:
obj-m := rwbuf.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers
(3)编译
sudo make
2.创建设备文件(设备进入点)
(1)创建
sudo mknod /dev/rwbuf c 60 0
意思:
在/dev目录下创建一个名为rwbuf的设备文件
设备文件的类型是c(字符型设备)
主设备号是60
次设备号是0
(2)赋权
sudo chmod 777 /dev/rwbuf
更改设备的权限。没有读写权限的话,就会读出一堆无意义的乱码。
3.插入内核模块(加载设备驱动程序)
先清理一下缓存,不然一会就可能输出一大堆多余东西,影响到我们想要看到的输出东西
sudo dmesg -c
插入内核模块(加载设备驱动程序)
sudo insmod rwbuf.ko
查看是否成功
dmesg
4.测试用户应用程序(调用驱动程序)
(1)读写
gedit writeAndRead.c
#include
// 为了open()中的O_RDWR
#include
// 定义设备进入点(设备名)
#define DEVICE_NAME "/dev/rwbuf"
int main()
{
// 声明文件描述符
int fd;
// 记录关闭设备的返回值
int ret;
// 写入到设备的内容
char buff_write[10] = "volume";
// 读取到设备的结果
char buff_read[10] = "";
// 调用打开设备函数。注意O_RDWR是字母O
fd = open(DEVICE_NAME, O_RDWR);
// 打开失败
if (fd == -1)
{
printf("[open-error]n");
return 0;
}
else
{
printf("[open-success]n");
}
// 调用驱动程序的写操作接口函数
if (write(fd, buff_write, 5) == -1)
{
printf("[write-error]n");
return 0;
}
// 写入成功
else
{
printf("[write-success] %sn", buff_write);
}
// 调用驱动程序的读操作接口函数
if (read(fd, buff_read, 5) == -1)
{
printf("[read-error]n");
return 0;
}
// 读取成功
else
{
buff_read[5] = '