Linux读取文件的简单字符驱动程序
设备驱动程序作为沟通外部硬件与Linux内核的纽带,属于嵌入式开发中不可避免的一环。Linux内核程序的开发和用户空间中开发的不同之处在于两点,一是内核程序由内核进行调用,基本没有一个类似于用户空间程序中的main
函数,二是内核代码无法调用很多我们熟知的C语言库函数,仅仅能使用Linux内核提供的一部分较为原始的函数。
实验目的
- 学习简单字符设备驱动程序的编写方法
- 学习Linux内核程序的编写
出现的问题
-
【尚未解决】编写代码时,卸载驱动不干净。复现步骤为:
- 在驱动程序中为静态的为设备分配设备号并注册设备:
static int device_file_major_number = 200; static const char device_name[] = "gps-driver"; ··· result = register_chrdev(device_file_major_number, device_name, &gpsdriver_fops);
- 编译驱动程序,在qemu中安装该程序,并建立对应的设备文件
[root@qemu_imx6ul:/mnt/gpsdriver]# insmod gps-module.ko [ 908.892069] gps_module: loading out-of-tree module taints kernel. [ 908.982070] gps-driver: Initialization started [ 908.987177] gps-driver: register_device() is called. [ 908.998338] gps-driver: registered char device with major number = 0 [ 909.052069] gps-driver: open file with file name = ./gps-example.txt [root@qemu_imx6ul:/mnt/gpsdriver]# mknod /dev/mydev c 200 0
- 使用lsmod命令检查设备安装情况:
[root@qemu_imx6ul:/mnt/gpsdriver]# lsmod Module Size Used by Tainted: G gps_module 2729 0
- 运行测试程序,测试驱动程序是否安装完成:
[root@qemu_imx6ul:/mnt/gpsdriver]# ./test.out [ 3590.190814] gps-driver: device_file_read() is callde [ 3590.300994] gps-driver: read line in file [ 3590.305288] gps-driver: device_file_read() end $GPGGA,213035.000,3447.5084,N,11339.3580,E,1,07,1.6,97.9,M,-18.0,M,,0000*43 [ 3590.389111] gps-driver: device_file_read() is callde [ 3590.400221] gps-driver: read line in file [ 3590.402298] gps-driver: device_file_read() end $GPGLL,3447.5084,N,11339.3580,E,213035.000,A*38 [ 3590.432294] gps-driver: device_file_read() is callde [ 3590.436235] gps-driver: no more date in fil
- 卸载设备及删除对应的设备文件:
[root@qemu_imx6ul:/mnt/gpsdriver]# rmmod gps_module [ 5513.512332] gps-driver: Exiting [ 5513.541602] gps-driver: close file with file name = ./gps-example.txt [ 5513.542072] gps-driver: unregister_device() is called [root@qemu_imx6ul:/mnt/gpsdriver]# rm /dev/mydev
- 此时再次安装设备驱动程序就会出现问题,提示设备号被占用:
[root@qemu_imx6ul:/mnt/gpsdriver]# insmod gps-module.ko [ 5634.068533] gps-driver: Initialization started [ 5634.072070] gps-driver: register_device() is called. [ 5634.072070] gps-driver: can't register char device with errorcode = -16 [ 5634.222716] gps-driver: Initialization started [ 5634.241620] gps-driver: register_device() is called. [ 5634.248363] gps-driver: can't register char device with errorcode = -16 insmod: can't insert 'gps-module.ko': Device or resource busy
- 查看已装载的模块,返回值为空,查看主设备号占用情况,发现依然被之前的,已经卸载的设备所占用:
[root@qemu_imx6ul:/mnt/gpsdriver]# lsmod Module Size Used by Tainted: G [root@qemu_imx6ul:/mnt/gpsdriver]# cat /proc/devices Character devices: ··· 180 usb 189 usb_device 200 gps-driver 207 ttymxc ···
-
个人的解决方案如下,但并未实际解决问题:
-
重启qemu
-
修改代码后更换一个设备号
-
-
相关的代码如下:
- 设备注册
int register_device(void) { int result = 0; printk(KERN_NOTICE "gps-driver: register_device() is called.\n"); // 将设备注册为 device_file_major_number result = register_chrdev(device_file_major_number, device_name, &gpsdriver_fops); if (result < 0) { printk(KERN_WARNING "gps-driver: can\'t register char device with errorcode = %i\n", result); return result; } device_file_major_number = MAJOR(MKDEV(result,0)); printk(KERN_NOTICE "gps-driver: registered char device with major number = %i\n", device_file_major_number); // 关联gps数据文件 gpsFile = filp_open(FILE_PATH, O_RDONLY, 0); if (IS_ERR(gpsFile)) { printk(KERN_ALERT "gps-driver: Failed to open the file: %s\n", FILE_PATH); unregister_chrdev(device_file_major_number, device_name); return PTR_ERR(gpsFile); } printk(KERN_NOTICE "gps-driver: open file with file name = %s\n", FILE_PATH); return 0; }
- 设备释放
void unregister_device(void) { filp_close(gpsFile, NULL); printk(KERN_NOTICE "gps-driver: close file with file name = %s\n", FILE_PATH); printk(KERN_NOTICE "gps-driver: unregister_device() is called\n"); if (device_file_major_number != 0) { unregister_chrdev(device_file_major_number, device_name); } }
-
【已解决】内核程序对文件的读写问题:
我的内核程序代码中,对gps数据文件的读写调用了下述函数:
ssize_t vfs_read(struct file *, char __user *, size_t, loff_t *);
注意这两个函数的第二个参数buffer,前面都有__user修饰符,这就要求这两个buffer指针都应该指向用户空间的内存,如果对该参数传 递kernel空间的指针,这两个函数都会返回失败-EFAULT
因此,需要改变kernel对内存地址检查的处理方式,即在函数的开头与结尾处添加下述代码:
mm_segment_t oldfs; oldfs = get_fs(); // 保存当前的地址空间限制 set_fs(KERNEL_DS); // 解除地址空间限制,允许内核空间直接访问用户空间的地址 ··· set_fs(oldfs); // 恢复地址空间限制退出函数时添加,未必只在结尾处
整个流程类似于中断函数的处理过程,保存现场-中断执行-恢复
-
【已解决】对文件的读写函数对读写位置的记录问题:
原始的参考程序在读取字符后,使用内核中的参数
loff_t *possition
记录当前读取的位置:*possition += count; return count;
但当使用文件读取函数vfs_read()时,其需要一个参数来确定读取开始的位置,这个函数会自动的更新读取位置参数。也就是说,不再需要自己去更新记录文件读取位置的变量,否则,会出现隔一个字符读取一个的情况:
[root@qemu_imx6ul:/mnt/gpsdriver]# ./test.out [15997.282077] gps-driver: device_file_read() is callde [15997.322077] gps-driver: read line in file [15997.327606] gps-driver: device_file_read() end 1: $PG,1050034.04N13938,,,7169.,,1.,,00*3$PL,4758,,13.50E233.0,*8$PS,,,63,33,30,3,,,.,.,.*E$PM,10500A34.04N13938,,.2314,411,0 [15997.457270] gps-driver: device_file_read() is callde [15997.477827] gps-driver: read line in file [15997.480681] gps-driver: device_file_read() end 2: GVG314,,M02,,.,*3$PG,1060034.04N13938,,,7169.,,1.,,00*0$PL,4758,,13.50E233.0,*B$PS,,,63,33,30,3,,,.,.,.*E$PM,10600A34.04N13938,,.7190,411,0
步骤
运行结果
-
qemu
代码
-
服务端的代码和实验4相同,不需要修改
-
device_file.c
#include "device_file.h" #include <linux/fs.h> /* file stuff */ #include <linux/kernel.h> /* printk() */ #include <linux/errno.h> /* error codes */ #include <linux/module.h> /* THIS_MODULE */ #include <linux/cdev.h> /* char device stuff */ #include <asm/uaccess.h> /* copy_to_user() */ static const char g_s_Hello_World_string[] = "Hello world from kernel mode! 1234567890abcdefghijklmnopqrstuvwxyz"; static const ssize_t g_s_Hello_World_size = sizeof(g_s_Hello_World_string); #define FILE_PATH "./gps-example.txt" static struct file *gpsFile; /*===============================================================================================*/ static ssize_t device_file_read(struct file *file_ptr, char __user *user_buffer, size_t count, loff_t *possition) { // printk(KERN_NOTICE "Simple-driver: Device file is read at offset = %i, read bytes count = %u", (int)*possition, (unsigned int)count); // if (*possition >= g_s_Hello_World_size) // return 0; // if (*possition + count > g_s_Hello_World_size) // count = g_s_Hello_World_size - *possition; // if (copy_to_user(user_buffer, g_s_Hello_World_string + *possition, count) != 0) // return -EFAULT; // *possition += count; // return count; mm_segment_t oldfs; oldfs = get_fs(); set_fs(KERNEL_DS); char buffer[256] = {'\0'}; int bytes_read = 0; printk(KERN_NOTICE "gps-driver: device_file_read() is callde\n"); while (bytes_read < count) { char c = '\0'; if (vfs_read(gpsFile, &c, 1, possition) <= 0) { printk(KERN_WARNING "gps-driver: no more date in file\n"); set_fs(oldfs); return 0; } buffer[bytes_read] = c; bytes_read++; // *possition += 1; if (c == '\n') break; } printk(KERN_NOTICE "gps-driver: read line in file\n"); if (copy_to_user(user_buffer, buffer, bytes_read + 1) != 0) return -EFAULT; printk(KERN_NOTICE "gps-driver: device_file_read() end\n"); set_fs(oldfs); return bytes_read; } /*===============================================================================================*/ static struct file_operations gpsdriver_fops = { .owner = THIS_MODULE, .read = device_file_read, }; static int device_file_major_number = 200; static const char device_name[] = "gps-driver"; /*===============================================================================================*/ int register_device(void) { int result = 0; printk(KERN_NOTICE "gps-driver: register_device() is called.\n"); // 将设备注册为 device_file_major_number result = register_chrdev(device_file_major_number, device_name, &gpsdriver_fops); if (result < 0) { printk(KERN_WARNING "gps-driver: can\'t register char device with errorcode = %i\n", result); return result; } device_file_major_number = MAJOR(MKDEV(result,0)); printk(KERN_NOTICE "gps-driver: registered char device with major number = %i\n", device_file_major_number); // 关联gps数据文件 gpsFile = filp_open(FILE_PATH, O_RDONLY, 0); if (IS_ERR(gpsFile)) { printk(KERN_ALERT "gps-driver: Failed to open the file: %s\n", FILE_PATH); unregister_chrdev(device_file_major_number, device_name); return PTR_ERR(gpsFile); } printk(KERN_NOTICE "gps-driver: open file with file name = %s\n", FILE_PATH); return 0; } /*-----------------------------------------------------------------------------------------------*/ void unregister_device(void) { filp_close(gpsFile, NULL); printk(KERN_NOTICE "gps-driver: close file with file name = %s\n", FILE_PATH); printk(KERN_NOTICE "gps-driver: unregister_device() is called\n"); if (device_file_major_number != 0) { unregister_chrdev(device_file_major_number, device_name); } }
-
test.c
测试是否能按行读取#include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> int main(void){ char buffer[256]; char* ptr; int count; int fd; fd = open("/dev/mydev", 0); if(fd < 0){ printf("error open device file.\n"); exit(1); } count = read(fd, buffer, sizeof(buffer)); printf("%s",buffer); count = read(fd, buffer, sizeof(buffer)); if(count == 0 || count == NULL) return 0; printf("%s",buffer); // ptr = buffer; // while(*ptr != '\0'){ // printf("%c\n",*ptr); // ptr++; // } count = read(fd, buffer, sizeof(buffer)); if(count == 0 || count == NULL) printf("out\n"); close(fd); return 0; }
-
client.c
和实验四的大致相同,因为我是先读出一行再去进行数据格式化,因此仅需修改从文件中读出一行的操作即可,具体代码为27-32行#include<stdio.h> #include<string.h> #include<stdlib.h> char line[128]; char ans[6][128]; char time[32]; char date[32]; char latitude[32]; char ns; char longitude[32]; char ew; float location; float speed; float height; void tcpsend(const char* message); int main(int argc, char* argv[], char* env[]) { char buffer[1024]; memset(buffer, 0, sizeof(buffer)); int lineNum = 0; memset(ans,'\0',sizeof(ans)); int fd; fd = open("/dev/mydev", 0); if(fd < 0) { printf("error open device file.\n"); exit(1); } while(1) { memset(line,'\0',sizeof(line)); memset(time,'\0',sizeof(time)); memset(date,'\0',sizeof(date)); memset(latitude,'\0',sizeof(latitude)); ns = '\0'; memset(longitude,'\0',sizeof(longitude)); ew = '\0'; location = 0; speed = 0; lineNum++; float h1 = 0; float h2 = 0; if(read(fd,line,sizeof(line)) == 0) { printf("txt error in line %d \n",lineNum); fclose(gpsread); return 1; } int result = 0; result = sscanf(line,"$GPRMC,%20[^,],%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%20[^,]",&time,&date); if(result == 2) { int h,min,y,m,d; float s; int degree,minute; h = (time[0] - '0') * 10 + (time[1] - '0'); min = (time[2] - '0') * 10 + (time[3] - '0'); s = (time[4] - '0') * 10 + (time[5] - '0') + (time[7] - '0') * 0.1 + (time[8] - '0') * 0.01 + (time[9] - '0') * 0.001; d = (date[0] - '0') * 10 + (date[1] - '0'); m = (date[2] - '0') * 10 + (date[3] - '0'); y = 2000 + (date[4] - '0') * 10 + (date[5] - '0'); sprintf(ans[0], "时间: %04d 年 %02d 月 %2d 日, %02d 时 %02d 分 %02.0f 秒\n", y ,m ,d ,h ,min, s); continue; } result = 0; result = sscanf(line,"$GPGLL,%10[^,],%c,%10[^,],%c",&latitude,&ns,&longitude,&ew); if(result == 4) { int degree; int minute; double second; if(strlen(longitude) == 9) { degree = (longitude[0] - '0') * 10 + (longitude[1] - '0'); minute = (longitude[2] - '0') * 10 + (longitude[3] - '0'); second = ((longitude[5] - '0') * 1000 + (longitude[6] - '0') * 100 + (longitude[7] - '0') * 10 + (longitude[8] - '0')) * 6.0 / 1000; } else if(strlen(longitude) == 10) { degree = (longitude[0] - '0') * 100 + (longitude[1] - '0') * 10 + (longitude[2] - '0'); minute = (longitude[3] - '0') * 10 + (longitude[4] - '0'); second = ((longitude[6] - '0') * 1000 + (longitude[7] - '0') * 100 + (longitude[8] - '0') * 10 + (longitude[9] - '0')) * 6.0 / 1000; } if(ns == 'E') { sprintf(ans[1], "位置: 东经 %d 度 %02d 分 %.2f 秒 ", degree, minute, second); } else { sprintf(ans[1], "位置: 西经 %d 度 %02d 分 %.2f 秒 ", degree, minute, second); } degree = (latitude[0] - '0') * 10 + (latitude[1] - '0'); minute = (latitude[2] - '0') * 10 + (latitude[3] - '0'); second = ((latitude[5] - '0') * 1000 + (latitude[6] - '0') * 100 + (latitude[7] - '0') * 10 + (latitude[8] - '0')) * 6.0 / 1000; if(ns == 'N') { sprintf(ans[2], ", 北纬 %d 度 %02d 分 %.2f 秒 \n", degree, minute, second); } else { sprintf(ans[2], ", 南纬 %d 度 %02d 分 %.2f 秒 \n", degree, minute, second); } continue; } result = 0; result = sscanf(line,"$GPGGA,%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%f,%*[^,],%f,%*[^,]",&h1,&h2); if(result == 2) { sprintf(ans[5], "高度: %.1f 米\n", h1-h2); continue; } result = 0; int ld,lm; float s = 114.514; result = sscanf(line, "$GPVTG,%d.%d,%*[^,],,%*[^,],%*[^,],%*[^,],%f,%*[^,]", &ld, &lm, &s); if(result >= 2) { int degree = ld; int min = lm * 60 / 100; sprintf(ans[3], "方向: 北 %03d 度 %02.0f 分\n", degree, min); sprintf(ans[4], "航速: %.1f 公里\/小时\n",s); // printf("%s", ans[0]); // printf("%s", ans[1]); // printf("%s", ans[2]); // printf("%s", ans[3]); // printf("%s", ans[4]); // printf("%s", ans[5]); // printf("\n"); sprintf(buffer,"%s%s%s%s%s%s\n",ans[0],ans[1],ans[2],ans[3],ans[4],ans[5]); tcpsend(buffer); memset(ans,'\0',sizeof(ans)); memset(buffer,'\0',sizeof(buffer)); continue; } } return 0; } #include <unistd.h> #include <netinet/in.h> #include <sys/socket.h> #include <arpa/inet.h> #define PORT 6789 void tcpsend(const char* message) { int client_socket; struct sockaddr_in server_address; char buffer[1024]; // 创建客户端套接字 client_socket = socket(AF_INET, SOCK_STREAM, 0); if (client_socket == -1) { perror("Error creating socket"); exit(1); } // 设置服务器地址 server_address.sin_family = AF_INET; server_address.sin_port = htons(PORT); // 服务端监听的端口 server_address.sin_addr.s_addr = inet_addr("10.0.2.2"); // 服务端的IP地址 // 连接到服务器 if (connect(client_socket, (struct sockaddr*)&server_address, sizeof(server_address)) == -1) { perror("Error connecting to server"); exit(1); } // 发送数据给服务器 send(client_socket, message, strlen(message), 0); // 接收服务器的响应 memset(buffer, 0, sizeof(buffer)); int bytes_received = recv(client_socket, buffer, sizeof(buffer), 0); if (bytes_received == -1) { perror("Error receiving data"); exit(1); } printf("Received from server: \n %s", buffer); // 关闭连接 close(client_socket); }