Linux下的文件操作

Linux下,一切皆文件!

文件描述符 fd

什么是文件描述符?

在Linux系统中,一切皆文件,不仅我们平时认为的文件是文件,而且显示器,键盘,磁盘…都是文件。当进程需要对某些文件进行操作时,就必须在进程中打开所要操作的对应文件。而一个进程的所有状态信息都会被保存在进程的task_struct(Linux下的进程PCB)中,在task_struct中,存在一个指针,它指向一个结构体file_struct,而在这个结构体中存在一个数组fd_array[],这个数组保存的都是当前进程打开的文件的文件指针。这个数组的下标,就是我们所述的文件描述符。

图示:irN9JI.png

所以在进程中,只需要知道相应的文件所对应的下标,就可以找到对应的文件。

文件描述符分配规则

  • 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文件系统

存储一个文件,既要存储文件的数据信息,也要存储文件的属性信息

磁盘

电脑的磁盘是由盘片组成的,一个盘片有两个柱面,两个柱面都可以存储信息,每个柱面由很多同心圆组成,在每一个同心圆上有很多扇区(使用缺口分割),数据即存储在这些同心圆上,这样的一个圆叫做磁道,我们可以将它理解为线性结构,将整个磁盘分成很多个小块来管理,这样的操作叫做磁盘分区。

irGcRK.png

  • 超级块:存放文件系统本身的结构信息。
  • i节点表:存放文件属性 如:文件大小,所有者,最近修改时间等
  • 数据区:存放文件内容

创建一个文件的过程

  1. 存储属性:操作系统先找到一个空闲的i节点(用数字标识123456),操作系统将文件信息记录到其中。
  2. 存储数据:操作系统在数据区寻找空闲的数据块(多选),用来存放文件内容。
  3. 记录分配情况:假如一个文件的数据文件按顺序使用了数据块的300,600,900,那么操作系统就会在inode上的磁盘分布区记录上述块列表。
  4. 添加文件名到目录:假如新文件的文件名为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("[ahao@AHAOAHA ~ ]& ");
    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 :动态库所在路径

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值