Linux系统:基础IO

1 C接口回顾

#include<stdio.h>
#include<string.h>
int main()
{
  FILE* fp=fopen("bit.txt","w");//以只写的方式创建一个文件,若文件已经存在,会先把文件清空
  if(fp==NULL)
  {
    perror("fopen\n");
    return 1;
  }
  const char* s="linix so easy\n";
  fwrite(s,strlen(s),1,fp);//将字符串s写到文件bit.txt中
  fclose(fp);
  return 0;

在这里插入图片描述

#include<stdio.h>
#include<string.h>
int main()
{
  FILE*fp=fopen("bit.txt","r");//以只读的方式打开已经存在的文件,若文件不存在,则打开失败
  if(fp==NULL)
  {
    perror("fopen");
  }
  char  buf[64];
  while(fgets(buf,sizeof(buf),fp))//将文件中的内容读到buf中去
  {
  
  printf("%s",buf);
   }
  fclose(fp);
  return 0;
}

在这里插入图片描述

#include<stdio.h>
#include<string.h>
int main()
{
  FILE*fp=fopen("log.txt","a");//向文件末尾添加数据,文件不存在,则创建数据
  if(fp==NULL)
  {
    printf("file error\n");
  return 1;

  }
  int cnt=5;
  while(cnt--)
  {
    fputs("hello log\n",fp);//向文件中写数据
  }
  fclose(fp);
  return 0;
}

在这里插入图片描述

2 系统文件调用接口

2.1 open

在这里插入图片描述

  • pathname:要打开或创建的目标文件
  • flags: 打开文件时,可以传入多个参数选项,用下面一个或多个常量进行“或”运算
参数含义
O_RDONLY只读打开
O_WRONLY只写打开
O_RDWR读,写打开
O_CREAT文件若不存在,则创建它,需要使用mode选项,来指明新文件的访问权限
O_APPEND追加写
O_TRUNC每次打开的时候先清空文件
  • mode:文件的访问权限,用八进制数字来表示
  • 返回值:
    成功:新打开的文件描述符(fd)
    失败:-1

flags参数的理解
eg

#include<stdio.h>
#define ONE 0x1//0000 0001
#define TWO 0x2//0000 0010
#define THREE 0x4//0000 0100
void show(int flags)
{
  if(flags & ONE)
  {
    printf("hello one\n");
  }
  if(flags & TWO)
  {
    printf("hello two\n");
  }
 if(flags & THREE)
 {
   printf("hello three\n");
 }
}
int main()
{
show(ONE);
printf("------------------------\n");

show(TWO);
printf("------------------------\n");
show(ONE|TWO);
printf("------------------------\n");
show(ONE|TWO|THREE);
printf("------------------------\n");
}

可以看到,示例代码中的宏都是只有一个bit位为1的整数,各个宏之间并不相同,因此可以标识不同的状态,或运算可以传递不同的宏,与运算可以得到传递的宏的含义,flags的参数选项的实现也是如此。

2.2 close

在这里插入图片描述

使用:传入要关闭文件的描述符,即可关闭该文件
返回值:
关闭成功,返回文件描述符
关闭失败,返回-1

2.3 write

在这里插入图片描述

  • fd:要进行写操作的文件的文件描述符
  • buf:将字符串buf写入文件中
  • count:写入字符的个数
  • 返回值:返回真正写入字符的个数

2.4 read

在这里插入图片描述

  • fd:要进行读操作的文件的文件描述符
  • buf:将文件中读到的内容存放到数组buf中
  • count:读取的字符个数
  • 返回值:返回真正读取的字符个数

2.5 系统文件调用接口演示

往bite.txt里面写“hello,write\n”

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{

 int fd=open("bite.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);//文件权限为rw-rw-rw-

  if(fd<0)
  {
    perror("open");
    return 1;
  }
  printf("open success,fd:%d\n",fd);
  const char*s="hello write\n";
 write(fd,s,strlen(s));
close(fd);
 return 0;
}

在这里插入图片描述

往bite.txt里面追加写,即每次打开文件的时候不清空,接着在文件末尾写

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{

     int fd=open("bite.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
  if(fd<0)
  {
    perror("open");
    return 1;
  }
  printf("open success,fd:%d\n",fd);
  const char*s="hello write\n";
  const char*ss="hello append\n";
  write(fd,s,strlen(s));
  write(fd,ss,strlen(ss));

 close(fd);
  return 0;
}

在这里插入图片描述
读取bite.txt中的内容

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
  int fd=open("bite.txt",O_RDONLY);//只读
 if(fd<0)
  {
    perror("open");
    return 1;
  }
 printf("open success,fd:%d\n",fd);
 char buff[128];
 memset(buff,'\0',sizeof(buff));
read(fd,buff,sizeof(buff));
printf("%s",buff);
  close(fd);
  return 0;
}

在这里插入图片描述

3 文件描述符

#include<stdio.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
  int fd1=open("eg1.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
  int fd2=open("eg2.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
  int fd3=open("eg3.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
  int fd4=open("eg4.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
  int fd5=open("eg5.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
  
  printf("open success,fd1:%d\n",fd1);
  printf("open success,fd2:%d\n",fd2);
  printf("open success,fd3:%d\n",fd3);
  printf("open success,fd4:%d\n",fd4);
  printf("open success,fd5:%d\n",fd5);
  close(fd1);
  close(fd2);
  close(fd3);
  close(fd4);
  close(fd5);

}

在这里插入图片描述

可以看到,我们打开一个文件,文件描述符的值是从3开始依次递增,那么0,1,2去哪了呢?

3.1 0&1&2

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2
0,1,2对应的硬件设备一般是:键盘,显示器,显示器

所以输入,输出还可以采用以下方式

#include<stdio.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{

char buff[128];
ssize_t s=read(0,buff,sizeof(buff));//从标准输入里面读取内容
  if(s>0)
  {
    write(1,buff,strlen(buff));//向标准输出写内容,即打印到显示屏上
  }
return 0;
}


在这里插入图片描述

3.2 fd的本质

进程与文件的联系
进程要访问文件,必须先打开文件,一个进程可以打开多个文件,那么系统中可能就会存在大量被打开的文件,所以操作系统是如何管理这么多的文件呢?先描述,再组织
在内核中,OS为了管理每一个被打开的文件,都会构建一个file结构体,里面包含了一个被打开文件的几乎所有内容,再用双链表组织起来

在这里插入图片描述

从图中可以知道,文件描述符就是从0开始的整数,当打开文件的时候,操作系统便会为文件创建file结构体来描述目标文件。当进程进行open调用的时候,为了能找到文件,必须让进程和文件关联起来,所以每一个进程都有一个指针*files,指向一个数组,这个数组是一个指针数组,每个指针指向一个打开的文件。

所以 fd的本质就是该数组的下标,知道了文件描述符,就能找到对应的文件

3.3 fd的分配规则

我们打开一个文件,文件描述符是从3开始分配
那么先关闭0呢?

  close(0);
    int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
    if(fd<0)
    {
      perror("open");
      return 1;
    }
    printf("fd:%d\n",fd);

在这里插入图片描述
可以看到文件描述符变为了0
如果关闭了1呢?

  close(1);
    int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
    if(fd<0)
    {
      perror("open");
      return 1;
    }
    printf("fd:%d\n",fd);

在这里插入图片描述

可以看到,fd的值变为了1,但还有一个奇怪的现象,本来应该要打印到显示器上的语句,打印到了log.txt中,这其实就是所谓的输出重定向

由以上可以得出结论:fd的分配原则是:从最小的,没有被占用的文件描述符开始分配。

3.4重定向

3.4.1 输出重定向(上份代码)

3.4.2输入重定向

close(0);//从标准输入读重定向到从log.txt读
int fd=open("log.txt",O_RDWR);
if(fd<0)
{
 perror("open\n");
 return 1;
}
 char buf[64];
 printf("fd:%d\n",fd);
while(fgets(buf,sizeof(buf),stdin))
{

 printf("%s",buf);
}



在这里插入图片描述

3.4.3追加重定向

 close(1);//从标准输出追加重定向到从log.txt追加
 int fd=open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
 if(fd<0)
 {
   perror("open");
   return 1;
 }
 fprintf(stdout,"you can see me\n");
 printf("fd:%d\n",fd);
 char buf[64];
 fgets(buf,sizeof(buf),stdin);//从标准输入中读取字符串到buf中
 printf("%s\n",buf);
 return 0;

在这里插入图片描述

3.4.4 重定向的本质

在这里插入图片描述

重定向的本质,就是在OS内部,更改fd对应的内容指向

3.4.5 dup2系统调用

dup2是系统级可以实现重定向的函数
在这里插入图片描述
在这里插入图片描述
注意:传参的时候,是要改变newfd的指向,使newfd的指向和oldfd的指向一样

int main(int argc,char*argv[])
{
   //dup2的使用
    if(argc!=2)//在命令行要输入两个参数
    {
      return 2;
    }
   int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
   if(fd<0)
   {
     perror("open");
     return 1;
   }
   dup2(fd,1);//标准输出重定向到log.txt中
   fprintf(stdout,"%s\n",argv[1]);//在log.txt中打印第二个参数
    close(fd);
    }

在这里插入图片描述

4系统硬件管理方法

对于不同的硬件设备,它们的驱动程序提供给操作系统的read,write接口一定是不一样的,操作系统为了统一管理硬件设备,给每个硬件设备都配备了struct file结构体,把硬件设备描述成为文件,结构体里面的read,write函数指针分别对应指向驱动提供的接口。

在这里插入图片描述

这样在上层看来,就没有任何硬件的差别了,看待所有硬件,都统一成为了struct file,这也是Linux的设计哲学,一切皆文件。

5 缓冲区

5.1 什么是缓冲区

简单理解,就是一段内存空间

5.2 为什么要有缓冲区

在和外部设备进行IO操作的时候,数据量的大小并不是主要问题,和外设预备IO的过程是最耗费时间的。所以先将数据写到缓冲区,然后通过一定的刷新策略,再将缓冲区的数据刷新到外设上,可以减少与外设的访问次数,相对而言会提高效率。

5.3 缓冲区的刷新策略

①立即刷新
②行刷新,即遇到\n才会刷新
③ 满刷新 缓冲区满了才会刷新

int main()
 10 {
 11   //c语言提供的函数                                  
 12   printf("hello,printf\n");                         
 13   fprintf(stdout,"hello fprintf\n");
 14   const char*s="hello fputs\n";               
 15   fputs(s,stdout);                   
 16   //OS提供的                                                                                       
 17   const char*ss="hello write\n";        
 18   write(1,ss,strlen(ss));
 19   fork();
 20                                                                    
 21 }      

在这里插入图片描述

同一份代码,直接输出到显示器上和输出重定向到文件中,打印出的结果并不一样,并且观察发现输出重定向的时候,C语言提供的接口统一打印了两遍,而操作系统提供的只打印了一遍,这是为何呢?

a 一般而言,如果向显示器打印,刷新策略是行刷新,那么最后执行fork的时候,一定是函数执行完了并且数据已经被刷新了,此时缓冲区已经没有数据了,fork的话也不必再拷贝数据给子进程,所以向显示器打印,只打印了一次。
b 如果对程序进行了重定向,要向磁盘文件打印,那么此时的刷新策略就成为了满刷新,当fork的时候,一定是函数执行完了,但数据还没有刷新,所以这部分在缓冲区的数据也相当于是父进程的数据,在fork的时候会拷贝给子进程一份,因此便会打印两遍。
c 但操作系统提供的接口为何还是只打印了一遍呢?可见,我们上文中提到的缓冲区,并不是由操作系统维护的,如果是操作系统维护的,那么重定向的时候,操作系统提供的接口也会打印两次。实际上,上文所提到的缓冲区是C标准库维护的缓冲区,属于用户级的缓冲区。而系统调用write会直接到内核缓冲区,不会受到影响。

6 文件系统

6.1 磁盘文件

之前讨论的都是在内存中已经打开的文件,而还有没有被打开的文件,这些文件都属于磁盘级文件

6.2 磁盘物理结构

主要由磁盘盘片,磁头,伺服系统,音圈马达等组成
在这里插入图片描述

6.3 磁盘的存储结构

在这里插入图片描述

磁盘存储数据的基本单位是扇区,那么如何找到一个扇区呢?
CHS寻址
1 先确定在哪一个面(对应的就是哪一个磁头)
2 再确定在哪个磁道上
3 最后确定在哪个扇区上

6.4 磁盘的抽象结构

Linux管理磁盘文件,是将磁盘抽象为线性的结构进行分区管理的
在这里插入图片描述

此时,将数据存储到磁盘,就是将数据存储到该数组
找到磁盘特定扇区的位置,就是找到数组特定的位置
对磁盘的管理,就是对该数组的管理
将一整块磁盘,划分成为了多个磁盘分区

对于每个分区,还要做细化管理
在这里插入图片描述
Boot Block: 存储磁盘启动时加载操作系统的相关信息
Boot group:每一个分区都会划分为若干个Block group,而每个Block group内部,又被划分为了不同的区域

名称内容
Data blocks多个4KB大小的集合,存储文件的内容
inode Tableinode是一个大小为128字节的空间,保存的是对应文件的属性,而该组块内,是所有文件的inode的集合;为了标识唯一性,每一个inode都会有一个特定的inode编号
Block BitmapBlock Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有用,(假如有10000+个DataBlock,那么就会有10000+个比特位与之相对应,其中比特位为1,代表被占用,否则未被占用)
inode Bitmap每个bit表示一个inode是否被使用,被占用为1,未使用为0
Group Descriptor Table块组描述符,这个块多大,已经使用了多少,有多少个inode,使用了多少,还剩多少未被使用…
Super Block文件系统的属性信息
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值