Linux 系统中,外存中的数据都是以文件的形式保存的,对目录和各种设备的操作也等同于文件的操作。
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 文件类型示例
#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文件设置为文件所有者可读可写,其他用户为只读权限
#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 函数示例
#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结构来存放文件属性
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; // 最后一次改变时间(指属性)
};
下例展示了如何获取文件大小
#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
#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;当一个进程终止时,它所有已打开的文件都由内核自动关闭
#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 函数用于在指定的文件描述符中将文件指针定位到相应的位置
#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 - 非阻塞主要用在网络服务中,使得服务器得到最大的利用
非阻塞时设置 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
// 1. 获取文件的 flags
flags = fcntl(fd, F_GETFL, 0);
// 2. 增加文件的某个 flgas,例如将阻塞设置为非阻塞
flags |= O_NONBLOCK;
// 3. 设置文件的 flags
fcntl(fd, F_SETFL, flags);
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
}
#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
#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 函数用于关闭文件
#include <stdio.h>
FILE *fp;
if ((fp=fopen("xxx", "a+")) == NULL) {
exit(0);
}
fclose(fp);
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)
#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)
/*
移动文件流的读写位置
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)
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 字符
};
#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 的文件互为硬链接文件 |
| 删除源文件,链接文件依然存在,但是无法指向源文件 | 删除硬链接文件或者删除源文件任意一个,文件数据实际并未删除;只有删除源文件以及所对应的所有硬链接文件,文件数据才被删除,同时释放磁盘空间 |
#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