Linux文件系统与设备文件

发布时间:2024年01月22日

Linux文件系统与设备文件

请添加图片描述


字符设备和块设备体现了Linux中的一切皆文件的设计思想,驱动通过文件操作相关的系统调用和C库函数(本质也属于系统调用)被访问,其次驱动工程师在设备驱动中不可避免会与设备文件系统打交道,这也引出了我们需要掌握设备文件系统的相关知识。

Linux文件操作

文件操作系统调用

涉及到创建打开关闭等操作

  1. 创建

int creat(const char *filename, mode_t mode)

其中参数filename指的是文件名,mode指的是文件的权限,它同umask决定了文件的最终权限,umask代表了文件在创建时要去除的一些存取权限

int umask(int newmask)

将umask设置为新的mask,然后返回旧的umask,只影响读写执行的权限

  1. 打开

int open(const char *pathname, int flags, mode_t mode);

int open(const char *pathname, int flags);

pathname默认的是当前文件夹的下面

文件的打开标志:

标志含义
O_RDONLY
O_WRONLY
O_RDWRrdonly,wronly和rdwr这三个标志只能选取其中一个,不能同时存在
O_APPEND
O_CREAT
O_EXEC如果使用了O_CREAT而且文件已经存在,就会发生一个错误
O_NOBLOCK以非阻塞的方式打开一个文件
O_TRUNC如果文件已经存在,则删除文件的内容

如果使用了mode_t,表示打开时有文件的访问权限:

标志含义
S_IRUSR用户可以读
S_IWUSR
S_IXUSR
S_IRGRP组可以读
S_IWGRP
S_IXGRP
S_IROTH其他人可以读
S_IWOTH
S_IXOTH
S_IRWXO其他人可以读写执行
S_ISUID设置用户执行ID(set UID)
S_ISGID设置组执行ID

除了上述宏来表示的产生标志之外,我们自己也可以用数字来表示

文件权限共5位:从左往右数第一位用户ID、第二位组ID、第三位自己的权限、第四位组权限、第五位其他人的权限;

数字表示1(执行权限)、2(写权限)、4(读权限)、0(无权限)

  • 例如要创建一个用户可读、可写、可执行、但是组没有权限,其他人可以读、可以执行的文件,并设置用户ID。

    open(filename, O_CREAT, 10 705);

    open(filename, O_CREAT, S_IRWXU|S_IROTH|S_XOTH|S_ISUID);

如果文件打开成功会返回一个文件描述符,以后对于文件的所有操作都可以通过对这个文件描述符进行操作来实现。

  1. 读写

int read(int fd, const void *buf, size_t length);

buf为缓冲区名称,length为缓冲区的大小,单位为字节,表示在fd中读取length个字节写入buf中,返回的是实际读取的字节数。

int write(int fd, const void *buf, size_t length);

表示从buf中读取length字节数写入fd中,返回实际写入的字节数

  1. 定位lseek

int lseek(int fd, offset_t offset, int whence);

lseek将文件的读写指针相对whence移动了offset个字节,返回文件指针相对于文件开发的位置

SEEK_SET相对文件开头
SEEK_CUR相对当前位置
SEEK_END相对文件末尾

lseek(fd, 0, SEEK_END) 表示文件的大小

也就是文件指针是从wherece的位置处开始移动,结果移动了0个位置,指针就停在了whence的位置,而函数返回值是文件指针相对于文件开头的位置,因此这个返回值就是文件的长度。

  1. 关闭

int close(int fd);

例如,编写一个程序,在当前目录下面创建用户可读写文件hello.txt,在其中写入“hello,software weekly”,关闭该文件,再次打开该文件,读取其中的内容输出在屏幕上

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

#define BUFF_SIZE 100

int man()
{
	int ret;
	char buff[BUFF_SIZE];
	int fd = open("hello.txt", O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
	if(fd){
	ret = write(fd, "hello,software weekly", strlen(hello,software weekly));
	if(ret < 0)
	printf("write failed\n");
	close(fd);
}
	else{
	printf("creat failed\n");
}
	
	fd = open("hello.txt", O_RDONLY);
	ret = read(fd, buff, sizeof(buff));
	if(ret){
	printf("read failed\n");
	close(fd);
}
	buff[ret] = "\0";
	printf("%s\n", buff);
	close(fd);
}

C库文件操作

  1. 创建和打开

FILE *fopen(const char *path, const char *mode);

mode为C库函数打开的标志:

标志含义
r、rb
r+、r+b读写
w写,不存在则创建
w+、w+b读写,不存在则创建
a追加,不存在则创建
a+、a+b读写且追加,不存在则创建

b为二进制文件,在window下面是有区分的,但是Linux下面则没有

  1. 读写
int fgetc(FILE *stream);
int fputc(int c, FILE *stream);
char *fgets(char *s, int size, FILE *stream);
int fputs(const char *s, FILE *stream);
size_t fread(void *ptr, size_t size, size_t n, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);

fread表示从stream流中读取n个字段,每个字段大小为size个字节,将读取到的数据存到ptr中,返回已经读取的字段数。当读取的字段数小于n时,可能是在函数调用时出现了错误,也可能是读到了文件的末尾。因此要通过调用feof()和ferror()来判断。

另外C库还提供了定位函数:

int fseek(FILE *stream, long offset, int whence);
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
  1. 关闭

int fclose(FILE *stream);

例如,编写一个程序,在当前目录下面创建用户可读写文件hello.txt,在其中写入“hello,software weekly”,关闭该文件,再次打开该文件,读取其中的内容输出在屏幕上

#include <stdio.h>

#define BUFF_SIZE 100
int main()
{
	char buff[BUFF_SIZE];
	FILE *fd = fopen("hello.txt", w+);
	if (fd){
	fputs("hello,software weekly", fd);
	fclose(fd);
}

	fd = fopen("hello.txt", r+);
	if(fd){
	fgets(buff, sizeof(buff), fd);
	printf("%s\n", buff); //fgets函数会默认在字符串后面补"\0"
	fclose(fd);
}
}

Linux文件系统

Linux文件系统目录结构

linux文件系统目录结构

Linux文件系统与设备驱动

应用程序与文件系统直接的接口是系统调用,文件系统与设备文件之间的接口是file_operations结构体成员函数。

由于字符设备的上层没有类似于文件系统,所以字符设备的file_operations成员函数就直接由设备驱动提供了

块设备有两种访问方法:

  • 一种是不通过文件系统直接访问裸设备,在Linux内核实现了统一的def_blk_fops这一file_operations

    类似于”dd if=/dev/sdb1 of=sdb1.img”这一指令是不通过文件系统,直接访问的驱动设备文件

  • 第二种是通过文件系统访问块设备,file_operations的实现则位于文件系统内,文件系统会把针对文件的读写转换为针对块设备原始扇区的读写
    请添加图片描述
    在设备驱动程序的设计种,一般而言,会关心file和inode这两个结构体

file结构体

系统中每个打开的文件在内核空间都有一个关联的struct file,在内核和驱动源代码中,struct file的指针通常被命名为file或者filep

文件结构体struct file中读写模式mode、标志f_flags都是设备驱动关心得内容,而私有指针private_data在设备驱动中被广泛使用大多数指向设备驱动自定义以**用于描述设备的结构体,**是更高级的文件描述,依赖于低层的struct inode数据结构

请添加图片描述

inode结构体

VFS inode是Linux管理文件系统的最基本的单位,也是文件系统连接任何子目录,文件的桥梁,只与操作系统相关,用于保存文件或者目录信息。
请添加图片描述

表示设备文件的inode结构,i_rdev字段包含设备编号。Linux的设备编号分为主设备编号和次设备编号,前者为dev_t的高12位,后者位dev_t的低20位

unsigned int imajor(struct inode *inode); 获取主设备号,主设备号是驱动对应的概念,同一类设备一般使用相同的主设备号,序号一般从0开始

unsigned int imanor(struct inode *inode); 获取次设备号

查看 /proc/devices 可以获知系统中所注册的设备

file结构体和inode结构体的区别

struct inode和struct file的区别在于inode只关心操作系统找出底层文件结构的内容(例如什么设备文件),而不去具体的跟踪文件的当前位置和当前模式。struct file是个基本结构,实际上持有一个struct inode的指针,他代表打开的文件,并且提供一组函数,他们与底层文件结构执行方法有关

也就是说struct inode只代表内核中的文件,而struct file表示实际打开的文件,同一个文件被打开时可能有多个文件描述符,但他们都指向同一个inode。

devfs

devfs(设备文件系统)是由Linux 2.4内核出现的,使得设备驱动程序能够自主地管理自己的设备文件

  1. 可以在程序初始化的时候在/dev目录下创建设备文件,卸载设备时将它删除
  2. 设备驱动程序可以指定设备名、所有者和权限位
  3. 不再需要为设备驱动程序分配主设备号以及处理次设备号,在程序中可以直接给 register_chrdev()传递0主设备号以获得可用的主设备号,并在 devfs_register()中指定次设备号

udev用户空间设备管理

  • udev是在Linux 2.6的时候被引入,与devfs不同的是,就像谈恋爱,udev系统可以使得用户在上层自由选择和谁谈恋爱,而不能在内核空间限制和谁谈恋爱。对于devfs而言,第一个相亲女孩被命名为/dev/girl0,第二个相亲女孩被命名为/dev/girl1。而在用户空间实现的udev则可以,不管你中意的女孩是第几个,只要符合要求,都是/dev/mygirl。
  • udev完全工作在用户态,利用设备的热插拔事件来工作,详细的设备信息会由内核通过netlink套接字发送出来,发出的事情叫uevent。
  • 那冷插拔事件怎么办呢,设备当主机开机时就已经存在了,Linux内核提供了sysfs下面的一个uevent节点,往该节点写一个”add”,(具体怎么写我也不是很懂)导致内核重新发送netlink,之后udev就可以收到冷插拔的netlink消息了
  • 对于devfs,当一个并不存在的/dev节点被访问时,系统会自动加载相对应的设备驱动,而udev却不是这样,是在产生热插拔事件时才会加载相应的驱动,并且创建的对应节点

sysfs文件系统与Linux设备模型

udev和sysfs都是Linux系统的一部分,但它们有不同的功能和用途。sysfs是一个虚拟文件系统,用于从用户空间访问内核对象的属性。它将内核对象的属性表示为文件和目录,使得用户空间的程序能够通过标准的文件系统接口查询和更改内核对象的属性。包括展示设备驱动模型中各组件的层次关系。

在/sys/目录下的顶级目录:

block包含所有的块设备
devices包含系统所有的设备
bus包含所有的总线类型
class包含系统中的设备类型

他们实际上都会被认为是kobject的派生类,一个kobject对应sysfs中的一个目录。

在/sys/bus/pci的目录下又会出现devices和drivers,而这个devices目录下的文件是对/sys/devices下的文件的符号链接
请添加图片描述

与此不同,udev是一个设备管理器,它管理/dev目录下的设备节点。当内核检测到新设备时,它将发送一个uevent,udev将接收到这个事件并对其进行处理。这可能包括加载设备驱动,创建或删除设备节点,甚至更改设备的权限和所有权。udev的工作依赖于sysfs,因为它使用sysfs提供的信息来确定如何处理设备。

总的来说,sysfs是一个提供有关内核对象的信息的接口,而udev则使用这些信息来管理设备。就好比udev是做饭的方法,而sys是菜和米饭

  • 在Linux内核中,分别使用bus_type、device_driver和device来描述总线、驱动和设备,这三个结构体定义在include/linux/device.h中

    1. device_driver和device分别代表驱动和设备,他们都会挂载在总线上面,因此结构体中也都包含struct bus_type指针
    2. 驱动和设备注册的时候不需要都存在,注册设备时不需要对应的驱动被注册,注册驱动时不需要对应的设备被注册,而都注册之后,又如何连接起来呢
    3. 通过bus_type结构体的match()成员将两者捆绑在一起,一旦捆绑成功bus_driver的probe()就被执行
    4. 驱动、设备、总线的attribute都会落实为sys中的一个文件,会伴随着show()和store()这两个函数进行读写操作
    5. sysfs中的目录来源于device、device_driver、bus_type,而目录中的文件就来源于attribute
    6. 定义了一些快捷方式以方便attribute的创建工作
    #define DRIVER_ATTR(_name, _mode, _show, _store) \
     struct driver_attribute driver_attr_##_name = __ATTR(_name, _
    mode, _show, _store)
    #define DRIVER_ATTR_RW(_name) \
     struct driver_attribute driver_attr_##_name = __ATTR_RW(_name)
    #define DRIVER_ATTR_RO(_name) \
     struct driver_attribute driver_attr_##_name = __ATTR_RO(_name)
    #define DRIVER_ATTR_WO(_name) \
     struct driver_attribute driver_attr_##_name = __ATTR_WO(_name)
    
    #define BUS_ATTR(_name, _mode, _show, _store) \
     struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _
    store)
    #define BUS_ATTR_RW(_name) \
     struct bus_attribute bus_attr_##_name = __ATTR_RW(_name)
    #define BUS_ATTR_RO(_name) \
     struct bus_attribute bus_attr_##_name = __ATTR_RO(_name)
    
    #define DEVICE_ATTR(_name, _mode, _show, _store) \
    	struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
    #define DEVICE_ATTR_RW(_name) \
    	struct device_attribute dev_attr_##_name = __ATTR_RW(_name)
    #define DEVICE_ATTR_RO(_name) \
    	struct device_attribute dev_attr_##_name = __ATTR_RO(_name)
    #define DEVICE_ATTR_WO(_name) \
    	struct device_attribute dev_attr_##_name = __ATTR_WO(_name)
    #define DEVICE_ULONG_ATTR(_name, _mode, _var) \
    	struct dev_ext_attribute dev_attr_##_name = \
    		{ __ATTR(_name, _mode, device_show_ulong, device_store_ulong), &(_var) }
    #define DEVICE_INT_ATTR(_name, _mode, _var) \
    	struct dev_ext_attribute dev_attr_##_name = \
    		{ __ATTR(_name, _mode, device_show_int, device_store_int), &(_var) }
    #define DEVICE_BOOL_ATTR(_name, _mode, _var) \
    	struct dev_ext_attribute dev_attr_##_name = \
    		{ __ATTR(_name, _mode, device_show_bool, device_store_bool), &(_var) }
    #define DEVICE_ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store) \
    	struct device_attribute dev_attr_##_name =		\
    		__ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store)
    

    撰写不易,留下您的关注和点赞,我们一起进步!

文章来源:https://blog.csdn.net/weixin_45241469/article/details/135752653
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。