完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
一、实验目的
ubuntu 12.04 内核3.2.14 三、实验内容及实验原理 写一个简单的字符设备驱动程序,要求:
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 // #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] = ' |