> 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接口