Linux读取文件的简单字符驱动程序

发布时间 2023-11-12 15:12:35作者: PolarisZg

Linux读取文件的简单字符驱动程序

设备驱动程序作为沟通外部硬件与Linux内核的纽带,属于嵌入式开发中不可避免的一环。Linux内核程序的开发和用户空间中开发的不同之处在于两点,一是内核程序由内核进行调用,基本没有一个类似于用户空间程序中的main函数,二是内核代码无法调用很多我们熟知的C语言库函数,仅仅能使用Linux内核提供的一部分较为原始的函数。

实验目的

  • 学习简单字符设备驱动程序的编写方法
  • 学习Linux内核程序的编写

出现的问题

  1. 【尚未解决】编写代码时,卸载驱动不干净。复现步骤为:

    1. 在驱动程序中为静态的为设备分配设备号并注册设备:
    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);
    
    1. 编译驱动程序,在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
    
    1. 使用lsmod命令检查设备安装情况:
    [root@qemu_imx6ul:/mnt/gpsdriver]# lsmod 
    Module                  Size  Used by    Tainted: G  
    gps_module              2729  0 
    
    1. 运行测试程序,测试驱动程序是否安装完成:
    [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
    
    1. 卸载设备及删除对应的设备文件:
    [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
    
    1. 此时再次安装设备驱动程序就会出现问题,提示设备号被占用:
     [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
    
    1. 查看已装载的模块,返回值为空,查看主设备号占用情况,发现依然被之前的,已经卸载的设备所占用:
    [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
    ···
    
    1. 个人的解决方案如下,但并未实际解决问题:

      • 重启qemu

      • 修改代码后更换一个设备号

    2. 相关的代码如下:

      • 设备注册
      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);
          }
      }
      
  2. 【已解决】内核程序对文件的读写问题:

    我的内核程序代码中,对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); // 恢复地址空间限制退出函数时添加,未必只在结尾处
    

    整个流程类似于中断函数的处理过程,保存现场-中断执行-恢复

  3. 【已解决】对文件的读写函数对读写位置的记录问题:

    原始的参考程序在读取字符后,使用内核中的参数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
    

步骤

img

运行结果

  1. qemu

    img

    img

代码

  1. 服务端的代码和实验4相同,不需要修改

  2. 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);
        }
    }
    
  3. 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;
    }
    
  4. 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);
    }