> Linux 系统中,外存中的数据都是以文件的形式保存的,对目录和各种设备的操作也等同于文件的操作。
[TOC]
## 1 Linux 系统文件和文件系统
### 1.1 Linux 文件类型(ls -l)
> 表1.1.1 - 各种文件类型含义
文件类型 | 标识
---- | ----
普通文件 | 第 1 个字符为 `-`
目录文件 | 第 1 个字符为 `d`
硬链接文件 | 除了显示的文件数量,其他都和某个普通文件一模一样的文件
软链接文件 | 第 1 个字符为 `l`
块设备文件 | 第 1 个字符为 `b`
socket | 第 1 个字符为 `s`
字符设备文件 | 第 1 个字符为 `c`
管道文件 | 第 1 个字符为 `p`
setUid 可执行文件 | 第 4 个字符为 `s`
setGid 可执行文件 | 第 7 个字符为 `s`
setUid 加 setGid 文件 | 第 4 个和第 7 个字符都是 `l`
> 清单1.1.2 - C 语言获取 Linux 文件类型示例
```c
#inclue <stdlib.h>
// 执行成功则返回 shell 命令的值;
// 调用/bin/sh 失败返回127;
// 其他失败原因返回-1;
// 参数string为NULLL,则返回非零值
int res = system("ls -l");
```
### 1.2 Linux 文件权限(ls -l)
> 清单1.2.1 将 `/etc/passwd` 文件设置为文件所有者可读可写,其他用户为只读权限
```c
#include <sys/types.h>
#include <sys/stat.h>
// 权限更改成功返回0;
// 失败返回-1;
// 错误原因存于errno
chmod("/etc/paddwd", S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
```
权限参数(mode)的组成方式:`S_I` `R/W/X` `USR/GRP/OTH`
其中 USR=所有者,GRP=组,OTH=其他用户;R=读,W=写,X=执行
----
通过设置权限掩码(umask),可以设置系统文件在创建时的**初始权限**
> 清单1.2.2 umask 函数示例
```c
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
int main() {
system("touch old_umask_file"); // 创建文件
mode_t new_umask = 0666; // 八进制的666
mode_t old_umask = umask(new_umask); // 设置系统权限掩码为0666,返回系统原来的权限掩码
system("touch 0666_umask_file"); // 设置过系统权限掩码后,创建的文件
system("mkdir 0666_umask_dir"); // 设置过系统权限掩码后,创建的文件夹
}
/*
ls-l
d--x--x--x 2 zhaoxuyang03 zhaoxuyang03 4096 1月 8 22:50 0666_umask_dir
---------- 1 zhaoxuyang03 zhaoxuyang03 0 1月 8 22:52 0666_umask_file
*/
```
----
> 在 Linux 系统中,定义了 `stat` 结构来存放文件属性
```c
struct stat {
dev_t st_dev; // 文件所在设备的 ID
ino_t st_ino; // 索引节点号
mode_t st_mode; // 文件保护模式
nlink_t st_nlink; // 文件的链接数(硬链接)
uid_t st_uid; // 用户 ID
gid_t st_gid; // 组 ID
dev_t st_rdev; // 设备号,针对设备文件
off_t st_size; // 文件字节数
unsigned long st_blksize; // 系统块的大小
unsigned long st_blocks; // 文件所占块数
time_t st_atime; // 最后一次访问时间(access)
time_t st_mtime; // 最后一次修改时间(modify)
time_t st_ctime; // 最后一次改变时间(指属性)
};
```
> 下例展示了如何获取文件大小
```c
#include <unistd.h>
#include <sys/stat.h>
int main() {
struct stat buf;
stat("/etc/passwd", &buf);
printf("%d\n", buf.st_size);
return 0;
}
```
## 2 不带缓存的文件 I/O 操作 (File)
不带缓存的文件 I/O 操作,包括系统调用或 API 的 I/O 操作,是由操作系统提供的,符合 POSIX 标准,但是不能移植到非 POSIX 标准的操作系统(如 Windows)。主要用到以下操作:
函数 | 作用
---- | ----
creat | 创建文件
open | 打开或创建文件
close | 关闭文件
read | 读文件
write | 写文件
lseek | 移动文件的读写位置
flock | 锁定文件或解除锁定(用于文件加建议锁)
fcntl | 文件描述符操作(用于文件加强制锁)
### 2.1 文件的创建 (creat)
creat 函数用于创建文件,成功则由内核返回一个最小可用的文件描述符,若有错误发生则会返回-1
```c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat(const char* pathname, mode_t mode);
```
### 2.2 文件的打开和关闭 (open & close)
- open 函数用于打开一个存在或不存在的文件,成功则由内核返回一个最小可用的文件描述符,若有错误发生则会返回-1
- close 函数用于关闭一个打开的文件,成功关闭则返回0,发生错误时返回-1;当一个进程终止时,它所有已打开的文件都由内核自动关闭
```c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char* pathname, int flags);
int open(const char* pathname, int flags, mode_t mode);
int close(int fd);
```
flags 值 | 参数说明
---- | ----
O_RDONLY | 以只读模式打开
O_WRONLY | 以写入模式打开
O_RDWR | 以读写模式打开
O_APPEND | 在文件尾写入数据
O_TRUNC | **设置文件的长度为0**,并舍弃现存的数据
O_CREAT | 创建文件,可使用 mode 参数设置访问权限
O_EXCL | 与 O_CREAT 一起使用,如果要创建的文件已存在,则打开失败
### 2.3 文件的读写操作 (write & read)
- read 函数用于从指定的文件描述符中读出数据
- 常规文件的读写不会阻塞,除非从网络或控制台等场景读入
- write 函数用于向打开的文件写入数据,写操作从文件当前位置开始
- lseek 函数用于在指定的文件描述符中将文件指针定位到相应的位置
```c
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void* buf, size_t count);
```
### 2.4 文件的非阻塞操作 (O_NONBLOCK)
- 文件打开时用 flags 中的 O_NONBLOCK 来表示 write/read 是非阻塞的
- 非阻塞与阻塞的区别在于**没有数据到达的时候是否立刻返回**
- 如果设备暂时没有数据可读就返回-1,同时置 errno 为 `EWOULDBLOCK`
- 非阻塞主要用在网络服务中,使得服务器得到最大的利用
```c
非阻塞时设置 flags |= O_NONBLOCK
阻塞时设置 flags &= ~O_NONBLOCK
非阻塞程序结构如下:
while(1) {
非阻塞read(设备1);
if(设备1有数据到达) {
处理数据;
}
非阻塞read(设备2);
if(设备2有数据到达) {
处理数据;
}
......
}
```
### 2.5 文件上锁 (flock & fcntl)
- flock 用于给文件加建议锁,一般情况下,系统很少使用建议锁
- fcntl 用于给文件加强制锁,内核将阻止其他任何文件对其进行读写操作
- 通过 F_GETFL, F_SETFL 分别用于读取、设置文件的属性,能够更改的文件标志有 O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, O_NONBLOCK
```c
// 1. 获取文件的 flags
flags = fcntl(fd, F_GETFL, 0);
// 2. 增加文件的某个 flgas,例如将阻塞设置为非阻塞
flags |= O_NONBLOCK;
// 3. 设置文件的 flags
fcntl(fd, F_SETFL, flags);
```
----
```c
struct flock {
short l_type; // 加锁的类型:F_RDLCK, F_WRLCK, F_UNLCK
short l_whence; // 对 l_start 的解释,分别为 SEEK_SET, SEEK_CUR, SEEK_END
off_t l_start; // 指明加锁部分的开始位置
off_t l_len; // 加锁的长度
pid_t l_pid; // 加锁进程的进程id
}
```
```c
#include <sys/file.h>
/*
operation有四种情况:
- LOCK_SH:建立共享锁定,多个进程可同时对同一个文件作共享锁定
- LOCK_EX:建立互斥锁定,一个文件同时只有一个互斥锁定
- LOCK_UN:解除文件锁定状态
- LOCK_NB:无法建立锁定时,此操作可不被阻断,马上返回进程,通常与 LOCK_SH 或 LOCK_EX 做 OR 组合
单一文件无法同时建立共享锁定和互斥锁定,而当使用dup()或fork()时文件描述词不会继承此种锁定
*/
int flock(int fd, int operation);
返回0表示成功,若有错误则返回-1,错误代码存于errno
```
```c
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock* lock);
返回0表示成功,若有错误则返回-1,错误代码存于errno
```
## 3 带缓存的流文件 I/O 操作 (InputStraem & OutputStream)
带缓存的流文件 I/O 操作,又称标准 I/O 操作,符合 ANSI C 标准,在内存中开辟缓冲区,为程序的每一个文件使用,比不带缓存的文件 I/O 程序方便移植;主要用到以下操作:
函数 | 作用
---- | ----
fopen | 打开或创建文件
fclose | 关闭文件
fgetc | 由文件中读取一个字符
fputc | 将一指定字符写入文件流中
fputs | 将一指定的字符串写入文件流中
fread | 从文件流成块读取数据
fwrite | 将数据成块写入文件流
fseek | 移动文件流的读写位置
rewind | 重设文件流的读写位置到文件开头
ftell | 取得文件流的读取位置
### 3.1 流文件的打开和关闭 (fopen & fclose)
- fopen 函数用于打开文件
- fclose 函数用于关闭文件
```c
#include <stdio.h>
FILE *fp;
if ((fp=fopen("xxx", "a+")) == NULL) {
exit(0);
}
fclose(fp);
```
```c
FILE* fopen(const char*path, const char* mode);
- r 表示打开只读文件。该文件必须存在
- r+ 表示打开可读写的文件。该文件必须存在
- w 表示打开只写文件。若文件存在则文件长度清为零,即**该文件内容会清空**;若文件不存在则创建该文件
- w+ 表示打开可读写文件。若文件存在则文件长度清为零,即**该文件内容会清空**;若文件不存在则创建该文件
- a 表示以附加方式打开只写文件。若文件不存在,则会建立该文件;所文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留
- a+ 表示以附加的方式打开可读写的文件。若文件不存在,则会建立该文件;所文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留
- 上述形态都可以再添加一个字符'b',表示打开二进制文件(POSIX 系统的 Linux 会忽略 b 字符);新建文件会有 S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH(0666)权限
int fclose(FILE* stream);
成功返回0,失败返回EOF
```
### 3.2 流文件的读写操作 (fget & fputc & fputs & fwrite & fread)
```c
#include <stdio.h>
int fget(FILE* stream); // 成功返回读取的字符,失败返回EOF
int fputc(int c, FILE* stream); // 成功返回写入的字符,失败返回EOF
int fputs(const char* s, FILE* stream); // 成功返回写出的字符个数,失败返回EOF
/*
- ptr:将写入的数据地址
- size:字符串长度
- nmemb:字符串数目
- stream:文件流
*/
size_f fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream); // 成功返回实际写入的 nmemb 数目,失败返回 EOF
size_t fread(const void* ptr, size_t size, size_t nmemb, FILE* stream); // 成功返回实际读取到的 nmemb 数目,失败返回 EOF
```
### 3.3 文件的定位 (fseek & ftell & rewind)
```c
/*
移动文件流的读写位置
stream 为文件流
whence 有以下几种:
- SEEK_SET 从距文件开头 offset 位移量为新的读写位置
- SEEK_CUR 以目前的读写位置往后增加 offset(允许负值) 个位移量
- SEEK_END 将读写位置指向文件尾后再增加 offset(允许负值) 个位移量
*/
int fseek(FILE* stream, long offset, int whence); // 调用成功返回0,有错误返回-1
// 取得文件流的读取位置
long ftell(FILE* stream); // 成功返回当前读写位置,有错误返回-1
// 重设文件流的读写位置为文件开头
void rewind(FILE* stream);
```
## 4 特殊文件的操作
函数 | 作用
---- | ----
opendir | 打开目录文件
readdir | 读取目录文件
closedir | 关闭目录文件
symlink | 建立软链接
link | 建立硬链接
### 4.1 目录文件的操作 (opendir & readdir & closedir)
```c
struct __dirstream {
void *__fd; // hurd 指针
char *__data; // 文件夹块
int __entry_data;
char *_ptr; // 当前在块中的指针
int __entry_ptr;
size_t __allocation;
size_t __size;
__libc_lock_define (, __lock);
};
typedef struct __dirstream DIR;
struct dirent {
ino_t d_ino; // 此目录进入点的 inode 的节点号
ff_t d_off; // 目录文件开头至此目录进入点的位移
signed short int d_reclen;
unsigned char d_type; // 所指的文件类型
har d_name[256]; // 文件名,不包含 NULL 字符
};
```
```c
#include <sys/types.h>
#include <dirent.h>
DIR* opendir(const char* name); // 成功返回目录流,失败返回NULL
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
struct dirent * readdir(DIR* dir); // 成功则返回下个目录进入点,有错误发生或读取到目录文件尾则返回NULL
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR * dir); // 关闭成功返回0,失败返回-1,错误原因存于errno中
```
### 4.2 链接文件的操作 (symlink & link)
软链接(符号链接) | 硬链接
--- | ---
跨越不同文件系统 | 不支持跨越不同文件系统(例如/bin目录和用户目录属于不同的文件系统)
可以在目录间建立 | 不可以给目录建立硬链接
如果链接指向的文件从一个目录移动到另一个目录,就无法通过软链接访问(含有源文件在文件结构中的路径信息) |
需要一个索引节点,需要占用空间 |
软连接文件和源文件是不同类型的文件,也是不同的文件,inode号也不同 | 具有相同 inode 的文件互为硬链接文件
删除源文件,链接文件依然存在,但是无法指向源文件 | 删除硬链接文件或者删除源文件任意一个,文件数据实际并未删除;只有删除源文件以及所对应的所有硬链接文件,文件数据才被删除,同时释放磁盘空间
```c
#include <unistd.h>
/*
建立软链接
- oldpath 已存在文件路径和文件名(一定要存在,否则不生效)
- newpath 链接的名称
*/
int symlink(const char* oldpath, const char *newpath); // 成功返回0,失败返回-1,错误原因存于errno
// 建立硬链接
int link(consta char* oldpath, const char *newpath); // 成功返回0,失败返回-1,错误原因存于errno
```
## 附录
- [Linux文件系统及文件删除原理](https://blog.csdn.net/weixin_45440548/article/details/104667469)
- 空白目录
- 精简版Spring的实现
- 0 前言
- 1 注册和获取bean
- 2 抽象工厂实例化bean
- 3 注入bean属性
- 4 通过XML配置beanFactory
- 5 将bean注入到bean
- 6 加入应用程序上下文
- 7 JDK动态代理实现的方法拦截器
- 8 加入切入点和aspectj
- 9 自动创建AOP代理
- Redis原理
- 1 Redis简介与构建
- 1.1 什么是Redis
- 1.2 构建Redis
- 1.3 源码结构
- 2 Redis数据结构与对象
- 2.1 简单动态字符串
- 2.1.1 sds的结构
- 2.1.2 sds与C字符串的区别
- 2.1.3 sds主要操作的API
- 2.2 双向链表
- 2.2.1 adlist的结构
- 2.2.2 adlist和listNode的API
- 2.3 字典
- 2.3.1 字典的结构
- 2.3.2 哈希算法
- 2.3.3 解决键冲突
- 2.3.4 rehash
- 2.3.5 字典的API
- 2.4 跳跃表
- 2.4.1 跳跃表的结构
- 2.4.2 跳跃表的API
- 2.5 整数集合
- 2.5.1 整数集合的结构
- 2.5.2 整数集合的API
- 2.6 压缩列表
- 2.6.1 压缩列表的结构
- 2.6.2 压缩列表结点的结构
- 2.6.3 连锁更新
- 2.6.4 压缩列表API
- 2.7 对象
- 2.7.1 类型
- 2.7.2 编码和底层实现
- 2.7.3 字符串对象
- 2.7.4 列表对象
- 2.7.5 哈希对象
- 2.7.6 集合对象
- 2.7.7 有序集合对象
- 2.7.8 类型检查与命令多态
- 2.7.9 内存回收
- 2.7.10 对象共享
- 2.7.11 对象空转时长
- 3 单机数据库的实现
- 3.1 数据库
- 3.1.1 服务端中的数据库
- 3.1.2 切换数据库
- 3.1.3 数据库键空间
- 3.1.4 过期键的处理
- 3.1.5 数据库通知
- 3.2 RDB持久化
- 操作系统
- 2021-01-08 Linux I/O 操作
- 2021-03-01 Linux 进程控制
- 2021-03-01 Linux 进程通信
- 2021-06-11 Linux 性能优化
- 2021-06-18 性能指标
- 2022-05-05 Android 系统源码阅读笔记
- Java基础
- 2020-07-18 Java 前端编译与优化
- 2020-07-28 Java 虚拟机类加载机制
- 2020-09-11 Java 语法规则
- 2020-09-28 Java 虚拟机字节码执行引擎
- 2020-11-09 class 文件结构
- 2020-12-08 Java 内存模型
- 2021-09-06 Java 并发包
- 代码性能
- 2020-12-03 Java 字符串代码性能
- 2021-01-02 ASM 运行时增强技术
- 理解Unsafe
- Java 8
- 1 行为参数化
- 1.1 行为参数化的实现原理
- 1.2 Java 8中的行为参数化
- 1.3 行为参数化 - 排序
- 1.4 行为参数化 - 线程
- 1.5 泛型实现的行为参数化
- 1.6 小结
- 2 Lambda表达式
- 2.1 Lambda表达式的组成
- 2.2 函数式接口
- 2.2.1 Predicate
- 2.2.2 Consumer
- 2.2.3 Function
- 2.2.4 函数式接口列表
- 2.3 方法引用
- 2.3.1 方法引用的类别
- 2.3.2 构造函数引用
- 2.4 复合方法
- 2.4.1 Comparator复合
- 2.4.2 Predicate复合
- 2.4.3 Function复合
- 3 流处理
- 3.1 流简介
- 3.1.1 流的定义
- 3.1.2 流的特点
- 3.2 流操作
- 3.2.1 中间操作
- 3.2.2 终端操作
- 3.3.3 构建流
- 3.3 流API
- 3.3.1 flatMap的用法
- 3.3.2 reduce的用法
- 3.4 collect操作
- 3.4.1 collect示例
- 3.4.2 Collector接口