Linux 系统编程|文件
Linux 系统编程
第三章 文件I/O
3.1 标准 C 的I/O
1 | char *fgets(char *s, size_t size, FILE *stream); |
FILE
类型是一个结构体
1 | typedef struct iobuf { |
stdin
stdout
stderr
也是三个文件指针(流指针),在scanf
printf
等内部会被调用。
标准 I/O 缓存的三种类型
全缓存
要求填满整个缓冲区后才进行 I/O 操作。对于磁盘文件通常使用全缓存访问。可以使用
fflush()
刷缓存,程序在结束时也会自动刷缓存。行缓存
涉及一个终端时(如标准输入输出)使用行缓存。行缓存满后自动输出或碰到换行符自动输出。程序在结束时也会自动刷缓存。
1
2
3
4
5
6
int main() {
printf("hello world");
while (1) sleep(1);
return 0;
}则此程序不会有输出。
无缓存
标准错误流
stderr
通常不带缓冲区,这使得错误信息能尽快显示出来。
3.2 文件描述符
open
read
lseek
等函数都是内核提供的系统调用,它们不是 ANSI C 的组成部分,但都是 POSIX 的组成部分。
系统调用与 C 库的关系
文件操作方式
标准库函数:遵循 ISO 标准,基于流的 I/O,对文件指针进行操作
系统调用:兼容 POSIX 标准,基于文件描述符的 I/O,对文件描述符进行操作
文件描述符
- 对于内核而言,所有打开的文件都由文件描述符(一个非负整数)引用。
- 在 POSIX 应用程序中,整数
0
1
2
被替换成符号常数STDIN_FILENO
STDOUT_FILENO
STDERR_FILENO
(宏),它们都定义在头文件<unistd.h>
中。 - 文件描述符的范围是
0 - OPEN_MAX
,早期 UNIX 版本的上限是 19,现在很多系统将其增至 63,Linux 是 1023。
文件描述符与文件指针的转化
- 将文件描述符转化为文件指针
FILE *fdopen(int fd, const char *mode);
- 将文件指针转化为文件描述符
int fileno(FILE *stream);
3.3 文件操作
1 |
|
举例应用:文件拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void copy(int fdin, int fdout)
{
char buffer[BUFFER_LEN];
ssize_t nread;
while ((nread = read(fdin, buffer, BUFFER_LEN)) > 0) {
if (write(fdout, buffer, nread) != nread) {
fprintf(stderr, "write error: %s\n", strerror(errno));
/*
- errno 是一个系统维护的错误类型变量
- strerror 可以解析错误信息并转化为字符串输出
- 需要 #include <errno.h>、#include <string.h>
此语句等价于 perror("write error");
// perror 会在其内部调用 strerror,结合提示字符串输出
*/
exit(1);
}
}
if (nread < 0) {
fprintf(stderr, "read error: %s\n", strerror(errno));
// 等价于 perror("read error");
exit(1)
}
}
3.4 文件在内核中的数据结构
文件描述符表
- 文件描述符标志
- 文件表项指针
文件描述符就是这个结构体的数组下标
文件表项
- 文件状态标志
- 文件偏移量
- i 节点表项指针
- 引用计数器
i 节点
- 文件类型
- 对该文件操作的函数指针
- 文件长度
- 文件所有者
- 文件所在的设备
- 文件访问权限
- 指向存储文件的磁盘块的指针
3.5 重定向
1 |
|
3.6 文件状态标志操作
1 |
|
举例应用
1
2
3
4
5
6
7
8 void add_fl(int fd, int flag)
{
int val = fcntl(fd, F_GETFL);
val |= flag;
if (fcntl(fd, F_SETFL, val) < 0) {
perror("fcntl error");
}
}
1
2
3
4
5
6
7
8 void clr_fl(int fd, int flag)
{
int val = fcntl(fd, F_GETFL);
val &= ~flag;
if (fcntl(fd, F_SETFL, val) < 0) {
perror("fcntl error");
}
}备注:多个进程同时写一个文件时,应指定为追加模式。
3.7 文件 I/O 的五种模型
阻塞 I/O 模型
若所调用的 I/O 函数没有完成功能,进程就会挂起,直到数据到达才会返回。如终端(
scanf
)、网络设备的访问。非阻塞 I/O 模型
当请求的 I/O 操作不能完成时,则不让进程休眠,而是返回一个错误。如
open
read
write
访问。I/O 多路转接模型
如果请求的 I/O 操作阻塞,并非真正阻塞 I/O,而是让其中一个函数等待,其他函数还能运行。如
select
函数。信号驱动 I/O 模型
通过一个信号处理程序,使得系统可以自动捕获特定信号,进而启动 I/O。
异步 I/O 模型
当一个描述符已经准备好,可以启动 I/O时,进程会通知内核,由内核进行后续处理。(较少见)
举例说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 int main()
{
char buffer[4096] = {'\0'};
sleep(5);
add_fl(STDIN_FILENO, O_NONBLOCK);
ssize_t size = read(STDIN_FILENO, buffer, sizeof(buffer));
if (size < 0) {
perror("read error");
exit(1);
} else if (size == 0) {
printf("read finish\n");
} else {
if (write(STDOUT_FILENO, buffer, size) != size) {
perror("write error");
}
}
return 0;
}
1
2
3
4
5
6
7
8 /*
Case [1]
运行后什么也不做,sleep 结束后输出 read error: ...
Case [2]
运行后在 sleep 期间输入 control+D,sleep 结束后输出 read finish
Case [3]
运行后在 sleep 期间任意输入内容,sleep 结束后输出该内容
*/
第四章 文件系统
4.1 文件属性和权限操作
一、文件属性
1 | struct stat { |
1 | /* |
Linux 中的七种文件和七种宏
文件类型 | 宏 |
---|---|
普通文件(regular file) | S_ISREG() |
目录文件(directory file) | S_ISDIR() |
块特殊文件(block special file) | S_ISBLK() |
字符特殊文件(character special file) | S_ISCHR() |
命名管道 FIFO(named pipe) | S_ISFIFO() |
套接字(socket) | S_ISSOCK() |
符号链接(symbolic link) | S_ISLNK() |
用于判断时,可写
1
2
3 struct stat buf;
lstat("...", &buf);
if (S_ISREG(buf.st_mode)) {...}
二、权限操作
9 种文件访问权限位
对象 | 权限位 |
---|---|
用户 | S_IRUSR S_IWUSR S_IXUSR S_IRWXU |
用户同组 | S_IRGRP S_IWGRP S_IXGRP S_IRWXG |
其他人 | S_IROTH S_IWOTH S_IXOTH S_IRWXO |
文件权限通过按位或方式构造。
1
2
3
4 int fd = open("a.txt", O_WRONLY | O_CREAT, 0652);
// 等价于
int fd = open("a.txt", O_WRONLY | O_CREAT,
S_IRUSR | S_IWUSR | S_IRGRP | S_IXGRP | S_IWOTH);
判断文件有无权限(针对当前进程)
1 |
|
4.2 Linux 文件系统
“给磁盘中的块编号,使得磁盘看起来像一个数组。”
文件系统的三个区域(磁盘内)
超级块
存放文件系统本身的结构信息
i 节点表
存放 i 节点信息列表(和内核中的 i 节点是同步对应的)
数据区
存放文件内容
从文件名到文件内容
- 在目录项中查看文件名,找到对应的 i 节点编号
- 通过 i 节点编号找到 i 节点表中对应 i 节点
- 根据 i 节点中数据块编号访问对应数据块,获得文件内容
4.3 硬链接和软链接
一、创建
1 | ln hello.txt h_hello # 创建硬链接 |
二、本质
硬链接
- 和源文件共用一个 i 节点
- 每创建一个硬链接,链接数 +1。初始时链接数为 1
- 文件被删除当且仅当硬链接数为 0 且无其他进程打开该文件
- 源文件被删除后,硬链接仍有效(数据块并未删除)
软链接
- 自己具有一个 i 节点
- i 节点所指的数据块存放的是源文件的路径
- 源文件被删除后,软链接失效
硬链接和软链接都是文件。每一个链接都和其他文件一样具有一个目录项。
三、系统调用
1 |
|
备注
直接用
open
打开链接文件,实际打开的都是其引用的那个文件必须针对文件创建硬链接,只有超级用户才能对目录硬链接
- 硬链接的创建必须在同一个分区
- 创建软链接并不要求
actualpath
存在,此外软链接可以跨文件系统创建