目录简述
目录
前言:
一、Linux的文件
二、Linux文件系统目录结构
三、文件访问的方式
(1)通用方式:open/read/write/lseek/close
示例:
(2)非通用函数:ioctl/mmap
示例:
(3)函数用法查询
四、系统内核文件:
前言:
初始环节:带着以下的问题,针对性的阅读实践,理解程度更深哈
在 Linux 系统中,一切都是“文件”:普通文件、驱动程序、网络通信等等。所有的操作,都是通过“文件 IO”来操作的。(1)Linux的文件从哪里来?
(2)Linux文件目录结构是怎么样的?
(3)Linux系统里如何访问文件呢?
(4)Linux系统调用如何进入内核模式?在内核中的文件是如何访问的呢?
接下来的文章内容,将详细的解答上面的问题。如果有一些些帮助,不妨三连关注( ^_^ )。
一、Linux的文件
Linux的文件从哪里来?
Linux的文件既可以是真实保存到存储介质的文件也可以是自身内核提供的虚拟文件,还可以设备节点。如下图所示:
例:可以在Linux系统上查询设备节点,crw(char)对应的字符设备,brw(block)对应块设备,后面主设备号对应哪个驱动,次设备号对应哪个硬件。如下图所示:
二、Linux文件系统目录结构
Linux文件目录结构是怎么样的?
Linux内核源码目录结构:
- arch/:
- 包含和硬件体系相关的代码,如:alpha/,arm/,mips/等。
- block/:
- 块设备驱动程序调度。
- crypto/:
- 常用加密和散列算法,还有一些压缩和CRC校验算法。
- Documentation/:
- 内核各部分的通用解释和注释。
- drivers/:
- 设备的驱动程序。
- fs/:
- 支持的各种的文件系统,如:NTFS,FAT,ETX2/3/4,sysfs,procfs,NFS等。
- include/:
- 头文件,与系统相关的头文件放在/include/linux。
- init/:
- 内核初始化和启动代码。
- ipc/:
- 进程间通信代码:包含进程通信(IPC)机制的实现,如消息队列,信号量和共享内存。
- kernel/:
- 该内核最核心的部分,包括进程进度、定时器等,而和平台相关的一部分放在arch/*/kernel目录下。
- lib/:
- 该目录包含库函数和一些辅助函数,分别是通用内核对象(kobject)处理程序和循环冗余校验(CRC)计算函数等。
- mm/:
- 内存管理相关代码。
- net/:
- 该目录包含网络(无论什么类型的网络)协议相关代码。
- scripts/:
- 该目录包含在内核开发过程中使用的脚本工具,还有其他有用的工具。
- security/:
- 该目录包含安全框架相关代码。
- sound/:
- 该ALSA、OSS音频设备的驱动核心代码和常用设备驱动。
- usr/:
- 实现用于打包和压缩的cpio等。
- include:
- 内核API级别头文件。
三、文件访问的方式
Linux系统里如何访问文件呢?
(1)通用方式:open/read/write/lseek/close
示例:
实现文件的复制
- 这里打开老文件,没有的话,建立一个新文件(open)。
- while循环将老文件里的内容写到新文件里(write),循环结束条件---当读不到内容时(read)
- 最后关闭老文件和新文件(close)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
/*
* ./copy 1.txt 2.txt
* argc = 3
* argv[0] = "./copy"
* argv[1] = "1.txt"
* argv[2] = "2.txt"
*/
int main(int argc, char **argv)
{
int fd_old, fd_new;
char buf[1024];
int len;
/* 1. 判断参数 */
if (argc != 3)
{
printf("Usage: %s <old-file> <new-file>\n", argv[0]);
return -1;
}
/* 2. 打开老文件 */
fd_old = open(argv[1], O_RDONLY);
if (fd_old == -1)
{
printf("can not open file %s\n", argv[1]);
return -1;
}
/* 3. 创建新文件 */
fd_new = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if (fd_new == -1)
{
printf("can not creat file %s\n", argv[2]);
return -1;
}
/* 4. 循环: 读老文件-写新文件 */
while ((len = read(fd_old, buf, 1024)) > 0)
{
if (write(fd_new, buf, len) != len)
{
printf("can not write %s\n", argv[2]);
return -1;
}
}
/* 5. 关闭文件 */
close(fd_old);
close(fd_new);
return 0;
}
(2)非通用函数:ioctl/mmap
在Linux中,还可以把一个文件的所有内容映射到内存,然后直接读写内存即可读写文件。(mmap)
示例:
实现文件的复制:
- 打开老文件(open),确定老文件的大小(fstat→结构体stat)
- 将老文件映射到stat.st_size大小的buff内存里(mmap)注:地址由内核分配,映射区域可被读取。
- 创建新文件,并将buff写到新文件里。(open、write)
- 最后关闭老文件和新文件(close)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
/*
* ./copy 1.txt 2.txt
* argc = 3
* argv[0] = "./copy"
* argv[1] = "1.txt"
* argv[2] = "2.txt"
*/
int main(int argc, char **argv)
{
int fd_old, fd_new;
struct stat stat;
char *buf;
/* 1. 判断参数 */
if (argc != 3)
{
printf("Usage: %s <old-file> <new-file>\n", argv[0]);
return -1;
}
/* 2. 打开老文件 */
fd_old = open(argv[1], O_RDONLY);
if (fd_old == -1)
{
printf("can not open file %s\n", argv[1]);
return -1;
}
/* 3. 确定老文件的大小 */
if (fstat(fd_old, &stat) == -1)
{
printf("can not get stat of file %s\n", argv[1]);
return -1;
}
/* 4. 映射老文件 */
//MAP_SHARD:写入映射区的数据会复制回文件,且运行其他映射文件的进程共享
buf = mmap(NULL, stat.st_size, PROT_READ, MAP_SHARED, fd_old, 0);
if (buf == MAP_FAILED)
{
printf("can not mmap file %s\n", argv[1]);
return -1;
}
/* 5. 创建新文件 */
fd_new = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if (fd_new == -1)
{
printf("can not creat file %s\n", argv[2]);
return -1;
}
/* 6. 写新文件 */
if (write(fd_new, buf, stat.st_size) != stat.st_size)
{
printf("can not write %s\n", argv[2]);
return -1;
}
/* 5. 关闭文件 */
close(fd_old);
close(fd_new);
return 0;
}
(3)函数用法查询
怎么查询以上函数的用法呢?这里有三种方式,help、man、info。
最常用的方式是man,man手册既可以查看命令的用法,还可以查看函数的详细介绍。
man手册具体分为9大类:
以open函数为例,可以查看到头文件、了解到函数参数以及用法。
man 2 open
四、系统内核文件:
Linux系统调用如何进入内核模式?
用户态转到内核态,一般用swi和svc指令产生异常引入内核,并且传入参数让内核知道调用的是sys_open还是sys_read。这里有三种方式:(具体如图)
- old ABI :是在swi命令的时候也传入参数
- EABI: 先将参数放入R7寄存器里,后调用swi指令
- ARM64:先将参数放入R8寄存器里,后调用svc指令
在内核中的文件是如何访问的呢?
进入内核后,先分辨文件的类型,然后根据不同的文件类型去找不同的设备驱动,继而进行读写或者输入输出控制。