6.字符设备驱动体验,字符设备驱动学习
字符设备驱动学习
在Linux系统中,驱动程序通常采用内核模块的程序结构来进行编码。因此,编译/安装一个驱动程序,其实质就是编译/安装一个内核模块。
一、编译安装字符设备驱动程序
memdev文件中:在这个文件里和真实的硬件无关,只是虚构了一个数组
1 #include <linux/module.h> 2 #include <linux/fs.h> 3 #include <linux/init.h> 4 #include <linux/cdev.h> 5 #include <asm/uaccess.h> 6 7 8 int dev1_registers[5]; 9 int dev2_registers[5]; 10 11 struct cdev cdev; 12 dev_t devno; 13 14 /*文件打开函数*/ 15 int mem_open(struct inode *inode, struct file *filp) 16 { 17 /*获取次设备号*/ 18 int num = MINOR(inode->i_rdev); 19 20 if (num==0) 21 filp->private_data = dev1_registers; 22 else if(num == 1) 23 filp->private_data = dev2_registers; 24 else 25 return -ENODEV; //无效的次设备号 26 27 return 0; 28 } 29 30 /*文件释放函数*/ 31 int mem_release(struct inode *inode, struct file *filp) 32 { 33 return 0; 34 } 35 36 /*读函数*/ 37 static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos) 38 { 39 unsigned long p = *ppos; 40 unsigned int count = size; 41 int ret = 0; 42 int *register_addr = filp->private_data; /*获取设备的寄存器基地址*/ 43 44 /*判断读位置是否有效*/ 45 if (p >= 5*sizeof(int)) 46 return 0; 47 if (count > 5*sizeof(int) - p) 48 count = 5*sizeof(int) - p; 49 50 /*读数据到用户空间*/ 51 if (copy_to_user(buf, register_addr+p, count)) 52 { 53 ret = -EFAULT; 54 } 55 else 56 { 57 *ppos += count; 58 ret = count; 59 } 60 61 return ret; 62 } 63 64 /*写函数*/ 65 static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos) 66 { 67 unsigned long p = *ppos; 68 unsigned int count = size; 69 int ret = 0; 70 int *register_addr = filp->private_data; /*获取设备的寄存器地址*/ 71 72 /*分析和获取有效的写长度*/ 73 if (p >= 5*sizeof(int)) 74 return 0; 75 if (count > 5*sizeof(int) - p) 76 count = 5*sizeof(int) - p; 77 78 /*从用户空间写入数据*/ 79 if (copy_from_user(register_addr + p, buf, count)) 80 ret = -EFAULT; 81 else 82 { 83 *ppos += count; 84 ret = count; 85 } 86 87 return ret; 88 } 89 90 /* seek文件定位函数 */ 91 static loff_t mem_llseek(struct file *filp, loff_t offset, int whence) 92 { 93 loff_t newpos; 94 95 switch(whence) { 96 case SEEK_SET: 97 newpos = offset; 98 break; 99 100 case SEEK_CUR: 101 newpos = filp->f_pos + offset; 102 break; 103 104 case SEEK_END: 105 newpos = 5*sizeof(int)-1 + offset; 106 break; 107 108 default: 109 return -EINVAL; 110 } 111 if ((newpos<0) || (newpos>5*sizeof(int))) 112 return -EINVAL; 113 114 filp->f_pos = newpos; 115 return newpos; 116 117 } 118 119 /*文件操作结构体*/ 120 static const struct file_operations mem_fops = 121 { 122 .llseek = mem_llseek, 123 .read = mem_read, 124 .write = mem_write, 125 .open = mem_open, 126 .release = mem_release, 127 }; 128 129 /*设备驱动模块加载函数*/ 130 static int memdev_init(void) 131 { 132 /*初始化cdev结构*/ 133 cdev_init(&cdev, &mem_fops); 134 135 /* 注册字符设备 */ 136 alloc_chrdev_region(&devno, 0, 2, "memdev"); 137 cdev_add(&cdev, devno, 2); 138 } 139 140 /*模块卸载函数*/ 141 static void memdev_exit(void) 142 { 143 cdev_del(&cdev); /*注销设备*/ 144 unregister_chrdev_region(devno, 2); /*释放设备号*/ 145 } 146 147 MODULE_LICENSE("GPL"); 148 149 module_init(memdev_init); 150 module_exit(memdev_exit);
该文件的Makefile文件中:
1 obj-m := memdev.o 2 KDIR := /home/kernel/kernel/linux-ok6410 //我的内核代码路径 3 all: 4 make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm 5 clean: 6 rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.bak *.order
二、字符设备文件
以上图片可以完整体现出应用程序、字符设备文件、以及设备驱动之间的依赖关系。字符设备文件通过主设备号对应设备驱动程序、二应用程序通过文件名对应字符设备文件
通过字符设备文件,应用程序可以使用相应的字符设备驱动程序来控制字符设备。这里需要提及一下创建字符设备文件的方法有两种:
1.使用mknod命令
mknod /dev/文件名 c 主设备号 次设备号
当然在使用mknod创建的时候需要注意不能使得创建的文件名在这之前就存在,还有主设备号可以通过cat proc/devices命令进行查看,与内核模块相对应的设备号。
2.使用函数在驱动程序中创建
这里我们使用mknod /dev/memdev c 252 0 对我们的虚拟设备文件驱动创建设备文件
三、应用程序的编写
write.c文件:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/stat.h> 5 #include <fcntl.h> 6 int main() 7 { 8 int src = 2013; 9 int fd=0; 10 fd = open("/dev/memdev",O_RDWR); //以可读可写的方式打开 11 write(fd,&src,sizeof(int)); 12 close(fd); 13 return 0; 14 }
read.c文件:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/stat.h> 5 #include <fcntl.h> 6 7 int main() 8 { 9 int dst = 0; 10 int fd = 0; 11 fd = open("/dev/memdev",O_RDWR); 12 read(fd,&dst,sizeof(int)); 13 printf("dat is %d\n",dst); 14 close(fd); 15 return 0; 16 }
3.1编译运行文件
3.1.1编译
3.1.2运行
当然在运行之前得先查看开发板上面有没有应用程序需要的库文件运行:arm-linux-readelf -d write_mem,在到开发板的/lib/目录下面查看是否有这个库,如果没有则可以对文件进行动态编译或者移植库