Linux下的文件操作
Linux下,一切皆文件!
文件描述符 fd
什么是文件描述符?
在Linux系统中,一切皆文件,不仅我们平时认为的文件是文件,而且显示器,键盘,磁盘…都是文件。当进程需要对某些文件进行操作时,就必须在进程中打开所要操作的对应文件。而一个进程的所有状态信息都会被保存在进程的task_struct(Linux下的进程PCB)中,在task_struct中,存在一个指针,它指向一个结构体file_struct,而在这个结构体中存在一个数组fd_array[],这个数组保存的都是当前进程打开的文件的文件指针。这个数组的下标,就是我们所述的文件描述符。
图示:
所以在进程中,只需要知道相应的文件所对应的下标,就可以找到对应的文件。
文件描述符分配规则
- Linux进程默认情况下会有三个默认打开的文件操作符,分别是标准输入0,标准输出1,标准错误2。
- 0,1,2对应的硬件设备分别是:键盘,显示器,显示器。
- 当进程打开一个文件时,操作系统会给该文件分配文件操作符,分配的方式很简单,即就是在file_struct中的文件描述符数组当中,找到当前没有被使用的最小的一个数组下标,作为新文件的文件描述符。
代码测试:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
int fd;
umask(0);//设置文件默认访问权限
fd = open("file", O_CWRONLY|O_CREAT,0644);
if(fd < 0)//打开文件失败
{
printf("打开文件失败!\n");
exit(-1);
}
printf("新文件的文件描述符 fd = %d\n", fd);
close(fd);//关闭文件
return 0;
}
程序结果:1
open接口介绍
#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);
pathname:要打开或创建的文件
flags:打开文件时,可以传入多个参数选项,之间可以用“或”隔开,构成flags
O_RDONLY:只读打开
O_WRONLY:只写打开
O_RDWR:读,写打开
O_CREAT:若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND:追加写
返回值:
成功打开文件,返回新文件的文件描述符
失败返回-1
输出/输入重定向
操作系统的标准输出是显示器,标准输入为键盘,输出/输入重定向的意思即就是将进程的标准输出/输入更改为用户自定义的文件。
常见的重定向有:>,<,这是操作系统提供给我们的重定向接口,当在shell命令行执行echo “hello world!” > file时,他会给当前路径下的file文件内写入"hello world!",不会在显示器上打印"hello world!"。
重定向的本质
Linux操作系统下,一切皆文件,当我们想给显示器上打印"hello world!“时,操作系统会执行的操作就是给显示器这个文件中,写入"hello world!”,当发生输出重定向时,操作系统会首先关闭标准输出,这时,file_struct结构体中的文件描述符数组中下标为1的位置就会闲置,当下标为1的位置闲置时,打开另外一个文件,那么这个新文件所分配到的文件标识符就会是1,当其他进程要向标准输出打印数据时,就会将此时的新文件默认为标准输出,这样就实现了输入重定向。
代码实现
- 在Linux下,可以通过系统调用接口实现重定向。
close接口介绍
#include<unistd.h>
int close(int fd);
fd:文件描述符
返回值:
成功返回0
失败返回-1
代码实现
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
int fd;
int close_right = -1;//默认关闭失败
close_right = close(1);//关闭标准输出stdout
if(close_right == -1)//关闭失败
{
exit(-1);
}
umask(0);
fd = open("file", O_WRONLY|O_CREAT,0644);
if(fd == -1)
{
exit(-1);
}
printf("hello world!\n");//向标准输入写入hello world!
close(fd);
return 0;
}
程序结果:向file文件中写入"hello world!"
FILE
FILE是C库提供的一个结构体,用来操作文件,因为IO相关函数与系统调用接口对应,而且库函数封装了系统调用,所以本质上,访问文件都是通过文件的文件描述符fd访问的,所以在C库提供的结构体FILE中,一定封装了文件描述符fd。
Linux文件系统
存储一个文件,既要存储文件的数据信息,也要存储文件的属性信息。
磁盘
电脑的磁盘是由盘片组成的,一个盘片有两个柱面,两个柱面都可以存储信息,每个柱面由很多同心圆组成,在每一个同心圆上有很多扇区(使用缺口分割),数据即存储在这些同心圆上,这样的一个圆叫做磁道,我们可以将它理解为线性结构,将整个磁盘分成很多个小块来管理,这样的操作叫做磁盘分区。
- 超级块:存放文件系统本身的结构信息。
- i节点表:存放文件属性 如:文件大小,所有者,最近修改时间等
- 数据区:存放文件内容
创建一个文件的过程
- 存储属性:操作系统先找到一个空闲的i节点(用数字标识123456),操作系统将文件信息记录到其中。
- 存储数据:操作系统在数据区寻找空闲的数据块(多选),用来存放文件内容。
- 记录分配情况:假如一个文件的数据文件按顺序使用了数据块的300,600,900,那么操作系统就会在inode上的磁盘分布区记录上述块列表。
- 添加文件名到目录:假如新文件的文件名为file,操作系统就会将入口(123456,file)添加到目录文件。
软链接
- 创建软连接:ln -s [要链接的文件名] [软链接名]
- 软连接创建的链接文件是一个独立的文件,可以指向被链接的文件,具有自己的inode。
硬链接
- 创建硬链接:ln [要链接的文件名] [硬链接名]
- 硬链接所创建的链接文件与被链接的文件具有同一个inode,可以理解为增加了一个inode与目标文件的映射关系。
为我们实现的shell添加重定向功能
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define MAX_ARGV 20
#define MAX_CMD 1024
int cmd_cut(char cmd[], char* argv[])//发生重定向则返回重定向符号所在位置
{
char* buf = " ";
int i = 0;
int stdout_off = 0;
char stdout_file_name[50];
argv[i++] = strtok(cmd, buf);
while(argv[i++]=strtok(NULL, buf))
{
if(*argv[i-1]=='>'||
*argv[i-1]=='<'||
*argv[i-1]=='&')
{
//记录重定向符号位置
stdout_off = i - 1;
}
}
//处理末尾输入的'\n'
argv[i - 1] = NULL;
if(*argv[i - 2] == '\n')
{
argv[i - 2] = NULL;
}
else
{
char* p = (argv[i-2] + strlen(argv[i-2]) - 1);
*p = '\0';
}
//判断是否发生重定向
if(stdout_off != 0)
{
return stdout_off;
}
return 0;
}
int which_redirect(char* argv[], const int redirect)
{
char stdout_file_name[50];
if(redirect != 0)
{
strcpy(stdout_file_name, argv[redirect+1]);//保存输出/输入文件名
if(*argv[redirect] == '>')//输出重定向
{
argv[redirect] = NULL;
close(1);
return open(stdout_file_name, O_WRONLY|O_CREAT);
}
else if(*argv[redirect] == '<')//输入重定向
{
argv[redirect] = NULL;
close(0);
return open(stdout_file_name, O_WRONLY|O_CREAT);
}
else if(*argv[redirect] == '&')
{
argv[redirect] = NULL;
close(1);
}
else
{
printf("重定向标识符错误!\n");
exit(0);
}
}
return -1;
}
//新进程
void new_pro(char* argv[], const int redirect)
{
pid_t id=fork();
char stdout_file_name[20];
int fd = -1;
if(-1==id)
{
perror("fork");
exit(-1);
}
else if(0==id)//子进程
{
fd = which_redirect(argv,redirect);//判断输出/输入/不发生重定向 并处理
execvp(argv[0],argv);
}
else//父进程
{
int st;
while(wait(&st)!=id);
}
}
int main()
{
char cmd[MAX_CMD]={'\0'};
char* argv[MAX_ARGV];
int redirect = 0;//重定向标识符 默认不发生重定向
while(1)
{
printf("[[email protected] ~ ]& ");
fgets(cmd,sizeof(cmd),stdin);
redirect = cmd_cut(cmd,argv);
new_pro(argv, redirect);
}
close(1);
return 0;
}
动态库&静态库
- 静态库(.a):程序在编译的过程中直接将库函数的代码复制到可执行文件中,程序在运行时不再需要静态库。
- 动态库(.so):程序在运行时才会去链接动态库的代码,多个程序共享使用库的代码。
- 静态库特点:可执行程序可移植性较强,但可执行程序体积较大。
- 动态库特点:可执行程序效率降低,可移植性差,但程序体积较小。
打包生成动态库
-
gcc -c [要生成静态库的代码文件名] -o [指定生成文件名.o]
-
ar -rc [lib静态库名称.a] [*.o]
ar是gnu归档工具
rc表示(replace and create)
-
ar -tv [静态库文件名] :查看静态库中的目录列表
t(列出静态库中的文件)
v(详细信息)
使用静态库
- gcc [代码文件名] -L[静态库路径] -I[静态库名称]
打包生成动态库
-
gcc -fPIC -c [要生成静态库的代码文件名]
fPIC:产生位置无关码(position independent code)
-
gcc -shared -o [lib动态库名称.so] [*.o]
shared:表示生成共享库格式
使用动态库
-
gcc [要编译的文件名] -o [指定可执行文件名] -L[动态库所在路径] -l[动态库名]
l :链接动态库,只要库名即可
L :动态库所在路径