🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
> 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)