一、文件IO系统调用
(1)一般的,进程是不能访问内核的。它不能访问内核所占内存空间也不能调用内核函数。CPU硬件决定了这些(这就是为什么它被称作“保护模式”)。
(2)为了和用户空间上运行的进程进行交互,内核提供了一组接口。透过该接口,应用程序可以访问硬件设备和其他操作系统资源。这组接口在应用程序和内核之间扮演了使者的角色,应用程序发送各种请求,而内核负责满足这些请求(或者让应用程序暂时搁置)。实际上提供这组接口主要是为了保证系统稳定可靠,避免应用程序肆意妄行,惹出大麻烦。
(3)应用程序与Linux内核之间存在着系统调用接口,由glibc实现open、read和write函数。
(4)具体流程
根据__NR_open变量判断open等函数
①用户层调用open等函数;
②glibc内的open等函数会设置触发异常的原因,并调用汇编指令swi或svc来触发异常;
③cpu发现该异常,就会调用Linux内核中的异常处理函数,分辨异常原因并处理异常(根据不同原因调用不同的函数)。
(4)应用层将触发异常的原因传递给内核
①old ABI:将open等函数导致的原因信息嵌入swi指令内,内核解析该指令。(ABI指应用程序二进制接口)
②EABI:将触达原因先存放于R7或R8寄存器中,内核从R7或R8中读取信息从而解析。
③最后调用sys_call_table数组对应的函数
注:配置内核和应用程序要使用相同的ABI接口。
(5)每个进程有自己的文件句柄空间,进程之间互不影响。
(6)文件句柄与具体文件挂钩
先获得未使用的文件句柄,然后打开文件得到一个file结构体,最后在当前进程记录下这个文件:记录“文件句柄、file结构体”的对应关系。后面APP使用文件句柄操作文件时,在内核里面就是根据对应的file结构体去操作文件
对于每一个进程,都有一个task_struct结构体,fd数组的每一项都指向一个file结构体,每个file结构体对应一个文件,如下图
二、每个进程有自己的文件句柄空间,进程之间互不影响。
每次调用open函数来打开同一个文件时,总会获得一个新的文件句柄fd。
每一套fd之间是互不影响的,内部的f_pos是相互独立的(f_pos就是操作的文件位置,每次操作过后f_pos就会自动+1)。因此,通过read函数对不同fd进行读取一个字节数据,最终获得的数据是相同的。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char **argv)
{
char buf[10];
char buf2[10];
if (argc != 2)
return -1;
int fd = open(argv[1], O_RDONLY);
int fd2 = open(argv[1], O_RDONLY);
printf("fd = %d\n", fd);
printf("fd2 = %d\n", fd2);
read(fd, buf, 1);
read(fd2, buf2, 1);
printf("data get from fd : %c\n", buf[0]);
printf("data get from fd2: %c\n", buf2[0]);
return 0;
}
如上,fd和fd2打开相同的文件, 使用read函数读取一个字符,读取的结果是一致的,运行结果如下:
$ cat 1.txt
adf
$ ./dup 1.txt
fd = 3
fd2 = 4
data get from fd : a
data get from fd2: a
三、常见文件句柄
句柄0(标准输入,stdin):
- 这个句柄用于接收输入数据。默认情况下,它从键盘读取输入数据,但可以被重定向到从文件或其他输入流中读取数据。
- 在C语言中,它通常与
stdin
关联,在Python中与sys.stdin
关联。句柄1(标准输出,stdout):
- 这个句柄用于输出数据。默认情况下,它将数据输出到屏幕上,但可以被重定向到文件或其他输出流。
- 在C语言中,它通常与
stdout
关联,在Python中与sys.stdout
关联。句柄2(标准错误,stderr):
- 这个句柄用于输出错误信息。默认情况下,它也将数据输出到屏幕上,通常用于显示错误消息。它也可以被重定向到文件或其他流,独立于标准输出。
- 在C语言中,它通常与
stderr
关联,在Python中与sys.stderr
关联。
三、dup函数
3.1 函数原型
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
3.2 作用
复制文件描述符,就是两个文件句柄指向同一个文件描述符,这两个文件句柄共享文件偏移地址、状态
oldfd:被复制的文件句柄
3.3 使用dup函数复制文件句柄
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
if(argc != 2)
return -1;
int fd = open(argv[1], O_RDONLY);
int fd2 = open(argv[1], O_RDONLY);
int fd3 = dup(fd);
if(fd < 0 | fd2 < 0)
return -1;
char c1, c2, c3;
read(fd, &c1, 1);
read(fd2, &c2, 1);
read(fd3, &c3, 1);
printf("fd: %c\n", c1);
printf("fd2: %c\n", c2);
printf("fd3: %c\n", c3);
close(fd);
close(fd2);
return 0;
}
dup函数首先会获得最小的未被使用的文件句柄,但不会去新建一个file结构体,而是指向传入的fd对应的结构体,fd和fd3指向同一个文件,因此fd用read函数读取一个字符后,pos+1,fd3会读取下一个字符
运行结果如下:
$ cat 1.txt
abc
$ gcc -o dup dup.c
$ ./dup 1.txt
fd: a
fd2: a
fd3: b
四、dup2函数
4.1 函数原型
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
oldfd:被复制的文件句柄
newfd:复制得到的文件句柄,可以指定复制得到的文件句柄为newfd
4.2 作用
以dup2(fd,1)为例
①关闭文件句柄1的文件;
②文件句柄1指向句柄为fd的file结构体
由于句柄1是标准输出,后续输出都会定向到句柄为fd的file结构体,如下图所示
4.3 代码实现
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int fd;
int c = 2333;
fd = open(argv[1], O_RDWR);
printf("%d\n", fd);
dup2(fd,1);
printf("%d\n", c);
return 0;
}
运行结果
$ gcc -o dup2 dup2.c
$ cat 2.txt //2.txt为空
$ ./dup2 2.txt
3
$ cat 2.txt
2333 //输出重定向