文件系统
文件系统是操作系统管理存储设备上文件的机制,负责组织、存储、检索和保护文件
功能
空间管理:对存储设备空间进行分配与回收,比如为新文件分配磁盘块,文件删除后回收空间 。
文件组织:以目录和文件形式构建层次化结构(树状结构 ),方便管理和查找,如在 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