树莓派Linux 系统内核结构及IO口驱动代码交叉编译和调试

 

文件系统

 

文件系统是操作系统管理存储设备上文件的机制,负责组织、存储、检索和保护文件

功能

空间管理:对存储设备空间进行分配与回收,比如为新文件分配磁盘块,文件删除后回收空间 。

文件组织:以目录和文件形式构建层次化结构(树状结构 ),方便管理和查找,如在 Linux 系统中,从根目录 “/” 开始,可建立多级子目录存放文件 。

文件操作:支持创建、删除、打开、关闭、读取、写入文件等操作 。

文件保护:通过权限设置(如读、写、执行权限 )、文件加密等机制,限制不同用户对文件的访问 。

数据映射:实现文件逻辑结构(用户看到的文件形式 )到物理存储(实际在磁盘上的存储位置 )的映射 。

 

常见类型

FAT32:广泛用于 Windows 系统和可移动存储设备,兼容性好,但不支持单个文件大于 4GB,对大文件存储支持有限 。

NTFS:Windows NT 及后续系统主流文件系统,支持大文件、长文件名,有完善的权限管理和日志功能,可靠性高 。

exFAT:为解决 FAT32 大文件支持问题推出,适用于大容量移动存储设备,支持更大文件和分区,跨平台兼容性不错 。

ext4:Linux 系统常用文件系统,性能好,支持大文件和大分区,具备日志功能保障数据一致性,可在线扩展文件系统大小 。

XFS:高性能文件系统,常用于企业级应用和大型数据存储,能高效处理高并发读写,支持动态扩展 。

 

 

虚拟文件系统

 

虚拟文件系统(Virtual File System,VFS )是操作系统内核组件,是物理文件系统与服务间的接口层,并非实际文件系统,只存在于内存,系统启动时建立,关闭时消亡

为用户程序提供抽象层,使其无需关心底层实际文件系统类型(如 FAT32、NTFS、ext4 等 ),就能用统一接口访问文件,让操作系统可同时支持多种文件系统,在用户空间程序看来如同一个文件系统 

Linux内核结构图

 

 

分区的概念

分区是将计算机存储设备(如硬盘 )划分为多个相对独立的区域,每个区域可视为一个逻辑存储单元,有各自的文件系统、存储容量等属性。通过分区,可实现数据分类存储、管理,还能隔离不同类型数据(如系统文件和用户数据 ),提高数据安全性和系统稳定性。

Windows 与 Linux 分区的不同


标识和管理

Windows:用 C 盘、D 盘这些盘符来区分分区,在 “磁盘管理” 这个图形工具里就能管理,很直观。不过老式分区表下,最多 4 个主分区,要更多就得弄扩展分区。也能通过动态磁盘灵活管理空间。
Linux:把磁盘当成文件,在 /dev 下面用类似 /dev/sda1 这样的名字标识分区,像 sda 是第一块盘,1 是第一个分区。常用命令行工具管理,也有图形工具。还能通过 LVM 把多个磁盘空间合起来,灵活调整分区大小。


挂载方式

Windows:分区创建好就自动有盘符,直接点盘符就能访问里面内容。
Linux:得把分区 “挂” 到某个目录下才能用,比如把一个分区挂到 /home 目录,访问 /home 就是访问这个分区。根目录必须得有分区挂载,外接设备常挂在 /media 这些目录。


文件系统

Windows:常用 FAT32(适合小存储设备,但大文件不行 )、NTFS(功能多,权限管理强 )、exFAT(用于大容量移动设备 ) 。
Linux:有 ext4(常用,性能好 )、XFS(处理大文件和高并发厉害 )等文件系统 

 

地址相关概念

总线地址

含义:与 CPU 可访问内存容量相关的地址概念 ,32 位操作系统下 CPU 最多访问
2的32次方bit(即 4GB)内存 ,64 位操作系统下理论上可访问2的64次方bit 内存。
作用:决定 CPU 能直接访问的内存范围,影响系统内存管理和硬件性能。


物理地址

含义:硬件实际地址或绝对地址,是内存单元或设备在物理硬件上的真实地址。
作用:硬件层面定位内存和设备,实现数据的实际存储和传输。


虚拟地址

含义:逻辑地址,由物理地址映射而来。
作用:让程序可使用比实际物理内存大得多的内存空间,提升内存管理灵活性和安全性,方便多程序运行。

 

地址映射

Linux 系统使用 MMU(内存管理单元 )进行内存虚拟化管理 。MMU 的作用是将物理地址转换为虚拟地址 。在驱动程序开发中,不能直接使用物理地址进行操作,而是要通过 MMU 映射后的虚拟地址 

 

 

查阅芯片手册

输入以下指令来查看树莓派CPU型号

cat /proc/cpuinfo

 

相关寄存器介绍

 

我们找到图中圈起来这三个寄存器他们的作用分别是:

GPFSEL0(GPIO Function Select 0):

       用于选择 GPIO 引脚 0 - 9 的功能

GPSET0(GPIO Pin Output Set 0):

       用于将 GPIO 引脚 0 - 31 设置为高电平

GPCLR0(GPIO Pin Output Clear 0):

        用于将 GPIO 引脚 0 - 31 设置为低电平

 

为什么是这些功能我们看后边的手册介绍:

 

功能配置

 

输出配置

 

清零配置

 

我们在编写驱动程序的时候,IO空间的起始地址是0x3f000000,加上GPIO的偏移量0x2000000,所以GPIO的物理地址应该是从0x3f200000开始的,然后在这个基础上进行Linux系统的MMU内存虚拟化管理,映射到虚拟地址上

根据上图各寄存器的尾部偏移,然后根据GPIO的物理地址0x3f200000可以知道这三个寄存器的物理地址:
GPFSEL0:0x3f200000
GPSET0: 0x3f20001c
GPCLR0: 0x3f200028

得到的是物理地址是不可操作的,我们需要转化成虚拟地址,通过 ioremap() 函数:

 void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags);


ioremap宏定义在asm/io.h内:

#define ioremap(cookie,size)    __ioremap(cookie,size,0)

参数说明:
     phys_addr:要映射的 I/O 内存资源起始物理地址 ,指定硬件寄存器或 I/O 内存区域起始位置。
     size:要映射的空间大小 ,即从起始地址开始连续的地址范围大小 
     flags:与要映射的 I/O 空间权限等有关的标志 ,用于指定映射属性 ,如是否缓存 

 返回值:

         成功时返回映射后的虚拟地址指针(类型为 void __iomem * ) 
         失败则返回 NULL 

 

底层驱动代码及上层测试代码

 

底层驱动代码

#include <linux/fs.h>            //      file_operations声明
#include <linux/module.h>        //      module_init module_exit声明
#include <linux/init.h>          //      __init __exit 宏定义声明
#include <linux/device.h>        //      class device声明
#include <linux/uaccess.h>       //      copy_from_user的头文件
#include <linux/types.h>         //      设备号 dev_t 类型声明
#include <asm/io.h>              //      ioremap iounmap 的头文件

static struct class *pin4_class;
static struct device *pin4_class_dev;

static dev_t devno;     // 设备号
static int major = 231; // 主设备号
static int minor = 0;   //次设备号
static char *module_name = "pin4"; //模块名

volatile unsigned int *GPFSEL0 = NULL; //volatile:不会因编译器的优化而省略,每次直接读值
volatile unsigned int *GPSET0  = NULL; //unsigned:将数字类型无符号化
volatile unsigned int *GPCLR0  = NULL;

//pin4_open函数
static int pin4_open(struct inode *inode, struct file *file)
{
    printk("pin4_open\n"); // 内核的打印函数,和printf类似

    //14~12位 设置为001
    *GPFSEL0 &=~(0x6<<12); //14,13位设置为00
    *GPFSEL0 |=  0x1<<12; //12位设置为1

    return 0;
}

//open_write函数
static ssize_t pin4_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
	int userCmd;
    printk("pin4_write\n");

    copy_from_user(&userCmd,buf,count);

    if(userCmd==1)
    {
    	*GPSET0 |= 0x1<<4;
    }
    if(userCmd==0)
    {
    	*GPCLR0 |= 0x1<<4;
    }
    else
    {
    	printk("undo!\n");
    }
        
    return 0;
}

static int pin4_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    printk("pin4_read\n");
    return 0;
}

static struct file_operations pin4_fops = {
    .owner = THIS_MODULE,
    .open  = pin4_open,
    .write = pin4_write,
    .read  = pin4_read,
};

int __init pin4_drv_init(void) //真实驱动入口
{
    int ret;

    devno = MKDEV(major,minor); //2. 创建设备号
    ret = register_chrdev(major , module_name , &pin4_fops); //3.注册驱动,告诉内核,把这个驱动加入到内核的链表中

    pin4_class = class_create( THIS_MODULE, "myfirstdemo" ); // 让代码在dev自动生成设备
    pin4_class_dev = device_create( pin4_class , NULL , devno , NULL , module_name ); //创建设备文件

    GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4); //第一个参数真正的物理地址,第二个参数映射的大小  一个寄存器4个字节 4*8=32bit
    //32位计算机中,一个字长等于32位,一个字节是8位,所以从长度来说一个字长等于4个字节
    GPSET0  = (volatile unsigned int *)ioremap(0x3f20001C,4);
    //由于返回值是void*型需要强制转换 void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags)
    GPCLR0  = (volatile unsigned int *)ioremap(0x3f200028,4);
    //ioremap作用是:物理地址转换成虚拟地址

    return 0;
}

void __exit pin4_drv_exit(void)
{
	iounmap(GPFSEL0);
	iounmap(GPSET0);
	iounmap(GPCLR0);

    device_destroy(pin4_class,devno);
    class_destroy(pin4_class);
    unregister_chrdev( major, module_name);
}

module_init(pin4_drv_init);
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

上层测试代码

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main()
{
        int fd;
	int cmd;

        fd=open("/dev/pin4",O_RDWR);

        if(fd<0)
        {
                printf("open failed!\n");
                perror("why");
        }
        else
        {
                printf("open successfully!\n");
        }

        printf("input commnd : 1/0 \n1: pin4 high\n0: pin4 low\n");
        scanf("%d",&cmd);

        if(cmd==1)
        {
                write(fd,&cmd,sizeof(int)); //第二个参数是内容 是指针类型的参数;第三个参数是文件大小如果文件是char类型,则为1。如果是int类型,则为sizeof(int)
        }
        else if(cmd==0)
        {
                write(fd,&cmd,sizeof(int));
        }
        else
        {
                printf("cmd is not good!\n");
        }
}

 

1、编译驱动代码(pin4driver.c)

       (1)把写好的驱动代码(driver2.c)放到源码树目录的 /drivers/char(存放字符设备驱动的源代码的目录 目录下

       (2)修改Makefile文件

(3)回到源码树目录下来进行模块编译,生成pin4driver.ko文件

ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules

(4)把驱动代码传给树莓派

scp pin4test.ko pi@192.168.1.17:/home/pi/work
指令 文件名       树莓派账号  IP    文件存放的路径

 

2、编译上层测试代码(pin4test.c)

arm-linux-gnueabihf-gcc pin4test.c -o pin4test

(2)把测试代码传给树莓派

scp pin4test pi@192.168.1.17:/home/pi/work

 

3、测试驱动代码

(1)回到树莓派加载刚刚编译好的驱动

sudo insmod pin4driver.ko

(2)查看dev目录底下是否生成pin4驱动文件

ls  /dev/pin4   //或者lsmod

(3)给加载好的驱动(driver)分配可执行权限(因为加载过来的驱动只有root用户具备权限所以要加权限不然打开驱动会失败)

sudo chmod 666 /dev/pin4

(4)运行测试代码

./pin4test1

(5)查看引脚状态是否改变

gpio readall

(6)最后卸载驱动

sudo rmmod pin4driver

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值