文章目录
一、作业
完善例子中的字符设备程序,使之满足以下功能:
- 打开关闭:设备关闭前不能被多次打开;
- 读:安装设备后用户可以读出最近写入到设备中的字符
- 写:设备支持每次写入字符不超过1024个,超过部分被丢弃;
- 控制:设备支持系统调用
ioctl(int d, int req,…)
。
当req = 0x909090
, 清除设备中写入的字符串。
二、解题过程
1.编译模块(设备驱动程序)
(1)创建模块文件xxx.c
gedit rwbuf.c
区分版本:这里不同,低版本内核2.6以下的是ioctl
,高版本内核2.6及其以上的是unlocked_ioctl
。
高版本内核2.6及其以上的:
// 模块
#include <linux/module.h>
// 内核
#include <linux/kernel.h>
// struct file_operations
#include <linux/fs.h>
// copy_to_user() & copy_from_user
#include <linux/uaccess.h>
// rwbuf.c, driver for virtual char-device
#define RW_CLEAR 0x123
// 设备名
#define DEVICE_NAME "rwbuf"
// buffer数组的最大长度
#define RWBUF_MAX_SIZE 200
// buffer数组的实际长度
static int rwlen = 0;
// 设备中存储字符串的相关数组
static char rwbuf[RWBUF_MAX_SIZE];
// 同一时间只允许一个打开的锁机制:0表示未被使用,1表示已被使用
static int inuse = 0;
// 设备的打开:返回-1表示错误;返回0表示成功
int rwbuf_open(struct inode *inode, struct file *file)
{
// 如果已被使用,报错
if (inuse == 1)
{
return -1;
}
// 未被使用
else
{
// 上锁,标志为已被使用
inuse = 1;
// increase the use count in struct module
try_module_get(THIS_MODULE);
return 0;
}
}
// 设备的关闭:只返回0表示成功无法表示报错
int rwbuf_release(struct inode *inode, struct file *file)
{
// 去锁,标志为未被使用
inuse = 0;
// decrease the use count in struct module
module_put(THIS_MODULE);
return 0;
}
// 设备的读操作:返回-1表示错误;返回(0, RWBUF_MAX_SIZE]表示成功
ssize_t rwbuf_read(struct file *file, char *buf, size_t count, loff_t *f_pos)
{
// 判断读取的长度是否有效
if (rwlen > 0 && rwlen <= RWBUF_MAX_SIZE)
{
// 从内核空间rwbuf复制到用户空间buf
copy_to_user(buf, rwbuf, count);
printk("[rwbuf_read-success]the size of rwlen after read = %d\n", rwlen);
return count;
}
else
{
printk("[rwbuf_read-error] rwlen = %d\n", rwlen);
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(rwbuf, buf, count);
// rwlen字符串的长度为buf的长度
rwlen = count;
printk("[rwbuf_write-success] the size of rwlen after write = %d\n", rwlen);
return count;
}
else
{
printk("[rwbuf_write-error] length of string = %d\n", 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)
{
// 字符串的长度设置为0,表示清零
rwlen = 0;
printk("[rwbuf_ioctl-success] the size of rwlen after ioctl = %d\n", rwlen);
return 0;
}
// 无此命令时
else
{
printk("[rwbuf_ioctl-error] the size of rwlen after ioctl = %d\n", rwlen);
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()
{
printk("Hello world\n");
// 表示注册成功与否:-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");
意思:
- 结构体
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 <stdio.h>
// 为了open()中的O_RDWR
#include <fcntl.h>
// #include <unistd.h>
// #include <sys/types.h>
// #include <sys/stat.h>
// 定义设备进入点(设备名)
#define DEVICE_NAME "/dev/rwbuf"
int main()
{
// 声明文件描述符
int fd;
// 记录关闭设备的返回值
int ret;
// 读取到设备的结果
char buff[100];
// 调用打开设备函数。注意O_RDWR是字母O
fd = open(DEVICE_NAME, O_RDWR);
// 打开失败
if (fd == -1)
{
printf("[open device %s error]\n", DEVICE_NAME);
return 0;
}
// 调用驱动程序的写操作接口函数
if (write(fd, "mytest", 5) == -1)
{
printf("[write device %s error]\n", DEVICE_NAME);
return 0;
}
// 调用驱动程序的读操作接口函数
if (read(fd, buff, 5) == -1)
{
printf("[read device %s error]\n", DEVICE_NAME);
return 0;
}
// 读取成功
else
{
buff[5] = '\0';
printf("[buff] %s\n", buff);
}
// 若ret的值为0,表示设备成功关闭
ret = close(fd);
printf("[close device %s] ret = %d\n", DEVICE_NAME, ret);
return 0;
}
gcc -o writeAndRead.out -g writeAndRead.c
./writeAndRead.out
(2)清除程序
gedit ioctl.c
#include <stdio.h>
// 为了open()中的O_RDWR
#include <fcntl.h>
// #include <unistd.h>
// #include <sys/types.h>
// #include <sys/stat.h>
// 定义设备进入点(设备名)
#define DEVICE_NAME "/dev/rwbuf"
int main()
{
// 声明文件描述符
int fd;
// 记录关闭设备的返回值
int ret;
// 控制命令
unsigned int cmd = 0x123;
// 调用打开设备函数。注意O_RDWR是字母O
fd = open(DEVICE_NAME, O_RDWR);
// 打开失败
if (fd == -1)
{
printf("[open device %s error]\n", DEVICE_NAME);
return 0;
}
// 调用驱动程序的写操作接口函数
if (ioctl(fd, cmd) == -1)
{
printf("[ioctl device %s error]\n", DEVICE_NAME);
return 0;
}
// 若ret的值为0,表示设备成功关闭
ret = close(fd);
printf("[close device %s] ret = %d\n", DEVICE_NAME, ret);
return 0;
}
gcc -o ioctl.out -g ioctl.c
./ioctl.out
(3)未关闭不能多次打开程序
gedit mtreopen.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd1 = open("/dev/rwbuf", O_RDWR);
if(fd1 == -1)
{
printf("first open file failed\n");
return 0;
}
int fd2 = open("/dev/rwbuf", O_RDWR);
if(fd2 == -1)
{
printf("reopen file failed\n");
return 0;
}
close(fd1);
return 0;
}
gcc -o mtreopen.out -g mtreopen.c
./mtreopen.out
5.卸载
(1)卸载设备驱动程序
sudo rmmod rwbuf
(2)删除设备文件(设备进入点)
sudo rm /dev/rwbuf
PS:
- 只要在模块中的设备号
60
和注册设备文件(mknod /dev/rwbuf c 60 0
)的60
一致就能将设备驱动和设备联系在一起。哪怕前者的设备名和后者的设备名不一致。 - 注册设备文件(
mknod /dev/rwbuf c 60 0
)的rwbuf
和用户程序(open("/dev/rwbuf", O_RDWR);
)的rwbuf
之间,只要两者的设备名一致就行。 - 也就是说在模块中设备名可以和用户程序中的设备名不一致。