Linux----基础IO

简介: Linux----基础IO

文章目录

复习C语言io知识

#include<stdio.h>
int main()
{
  //FILE* fp=fopen("./log.txt","r");
  FILE* fp=fopen("./log.txt","a");//追加,不会覆盖掉
  if(NULL==fp)
  {
    perror("fopen");
    return 1;
  }
  #if 
//  char buffer[32];
//  while(fgets(buffer,sizeof(buffer),fp)!=NULL)
//  {
//    printf("%s", buffer);
//  }
//  if(!feof(fp))
//  {
//    printf("fgets quit not normal\n");
//
//  }
//  else 
//  {
//    printf("fgets quit normal\n");
//  }
  int cnt=10;
  const char* msg="hello ";
  while(cnt--){
  fputs(msg,fp);
  }
  fclose(fp);
  return 0;
}

C 程序默认会打开3个输出流,stdin,stdout,stderr

stdin对应键盘,stdout对应显示器,stderr对应显示器


fputs(msg,stdout);//直接向显示器去写入


stdout是向显示器去输出

将原本应该显示到显示屏的内容,显示到了文件里面

本质是指把stdout的内容重定向到文件中

把原本应该打印在文件里面的打印在了显示器里面fputs向一般文件或者硬件设备都能写入

磁盘也是硬件

同理

c++:cin,cout cerr


c语言的一切操作实际上都是在向硬件写入(所有语言上对“文件”的操作都要贯穿操作系统)

即最终都是访问硬件,

用户行为–>语言,程序,lib–>OS–>驱动–>硬件


但是操作系统不相信任何人 ,所以访问操作系统是需要系统调用接口的

所以几乎所有的语言fopen,fclose,fread等等的底层一定是使用OS提供的系统调用


学习文件的系统调用接口

离OS更近,更能了解


文件打开

int open(const char *pathname, int flags);
 int open(const char *pathname, int flags, mode_t mode);

pathname就是要打开的路径名,flags就是我们打开的方式,mode就是打开的权限信息,

会返回一个文件描述符,int类型

flag是整数,:传递标志位,


int:32个bit位,一个bit,就代表一个标志,就代表一个标志位

0000 0000,如以最后一个标志位为0还是1,代表是读还是写

,以第一个标志位,代表是否创建文件

可以传多个,还快

if(O_WRONLY&flag)

判断结果,所以O_WRONLY O_rdnly O_CREAT

这些都是只有一个比特位为1的数据,而且都不重复


#define O_WRONLY 0x1

#define O_RDONLY 0x2

等等


如果想要得到两个以上的功能,我们直接|就可以了


这就是通过比特位的方式传多组标记的做法


文件关闭

  int fd=open("./lg.txt",O_WRONLY|O_CREAT);//以只写方式打开,如果文件不存在,就会帮助我们创建一个
  //相当于C语言中的w选项,
  if(fd<0)
  {
    //打开文件失败
    printf("open err\n");
  }
  close(fd);//文件就关掉了

我们没有输入第三个参数(权限参数),那么加入不存在这个文件,那么产生的文件的权限是乱的

文件描述符

 

  int fd=open("./g.txt",O_WRONLY|O_CREAT,0644);//一次传递两个标志位
  //以只写方式打开,如果文件不存在,就会帮助我们创建一个
  //相当于C语言中的w选项,0644以二进制的方式显示权限
  if(fd<0)
  {
    //打开文件失败
    printf("open err\n");
  }
  printf("fd:%d\n",fd);
  close(fd);//文件就关掉了

 int fd=open("./g.txt",O_WRONLY|O_CREAT,0644);//一次传递两个标志位
  int fd1=open("./g1.txt",O_WRONLY|O_CREAT,0644);//一次传递两个标志位
  int fd2=open("./g2.txt",O_WRONLY|O_CREAT,0644);//一次传递两个标志位
  int fd3=open("./g3.txt",O_WRONLY|O_CREAT,0644);//一次传递两个标志位
  //以只写方式打开,如果文件不存在,就会帮助我们创建一个

我们发现是从3开始连续

文件描述符那么0,1,2去那里了呢

0:标准输入,键盘

1:标准输出,显示器

2:标准错误,显示器

0 1 2 3 4 5 6 7

我们可以联想到一个数组的下标

open的返回值是OS系统给我们的

文件与进程

进程对文件的操作


要操作文件必须先打开文件

打开文件的本质:将文件相关的属性信息加载到内存

系统中会存在大量的进程,进程可以打开多个文件,系统中存在更多的打开的文件

那么OS 要把打开的文件管理起来,

(先描述再组织)

如果一个文件没有被打开,没有被创建,那么这个文件就在磁盘上,同理一个进程没有被打开,这个进程也在磁盘上面

如果创建了空文件(内容),要不要占磁盘空间呢,但是还有文件的属性,也是数据,,所以也是要占据磁盘空间,

磁盘文件=文件内容+文件的属性

对文件的操作:


对文件内容进行操作

对文件属性进行操作

管理的思路

先描述再组织


struct file
{
  //包含了打开文件的相关属性信息
  //链接属性
}

在进程里面有

struct task_struct

{

struct files_struct* fs;//地址,指向的就是其对应的内容

}

struct files_strtuct
{
struct file* fd_array[];//指针数组
}

因为array是一个指针数组,所以可以用对应的下标找到对应的地址0,1,2

相当于fd_array[0]就指向了一个文件


所以0,1,2分别被标准输入,标准输出,标准错误文件给占用

每次生成一个文件,再内存里面就要形成一个struct file结构,在把地址填入到array下标处


而我们再write和read的时候,都要传入fd


执行write和read调用的是进程,而进程就能通过自己的PCB 找到对应的fs指针,找到files_struct里面根据文件描述符找到对应的文件,进行相关操作

fd


本质就是内核中进程和文件关联的数组的下标



一切皆文件

一切皆为文件

每一个硬件都有其对应的write和read的方法

虚拟文件,可以类比于多态,使用函数指针,就用指针调用对应的函数,这些函数调用这些硬件对应的方法,

多态就是实现一切皆()的高级方法

如我们在上层调用read/write的时候,就指向了对应的fd,再指向其对应的方法

void Fd_Dewrite()
{
    const char* msg="hello";
    write(1,msg,strlen(msg));//向标准输出去写入
    write(1,msg,strlen(msg));//向标准输出去写入
    write(1,msg,strlen(msg));//向标准输出去写入
}

我们直接向标准输出去书出

直接从键盘输入

 read(0,buf,sizeof(buf)-1);//直接从键盘上写入
    printf("echo :%s",buf);


文件描述符的分配规则

void  Fd_Alloc_Base()
{
  close(0);
    close(2);//把0对应的标准输入给关闭
    int fd=open("./g.txt",O_CREAT|O_WRONLY,0644);
    printf("fd=%d \n",fd);
    close(fd);
}

分配规则

每次给新文件分配的fd,是从fd_array中找到最小的,没有被使用的,作为新的fd

void  Fd_Alloc_Base()
{
    close(1);
    close(2);//把0对应的标准输入给关闭
    int fd=open("./g.txt",O_CREAT|O_WRONLY,0644);
    printf("fd=%d \n",fd);
    close(fd);
}

我们把1的标准输出给关了,



没有显示到显示屏中,而是显示到了文件中

这就是输出重定向


原因:我们把1的文件描述符给关了,所以现在给g.txt里面的fd就是1,

而printf里面对应的文件的fd一定是1,所以现在g.txt的fd为=1,就写到了g.txt


而printf和fprintf里面的都是有相关的fd,因为操作系统最大,


write

功能:向文件描述符写入,在buf的用户缓冲区里面,期望写入cout个字节

return:返回的是实际向文件里面写入多少个字节的内容

#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    int fd=open("demo.txt",O_CREAT|O_WRONLY,0644);
    if(fd<0)
    {
        perror("open");
        exit(-1);
    }
    const char*msg="hello \n";
    int cnt=5;
    while(cnt)
    {
        write(fd,msg,strlen(msg));//我们写入文件的过程中,我们要不要加入\0呢,不需要,
        //因为\0作为字符串的结束标志位,只是c的规定,而文件关心字符串的内容,
        cnt--;
    }
    close(fd);
    return 0;
}

我们用write写入了5个hello,

read

功能:从文件描述符中读取指定内容,一次读取到的内容,都放到用户层缓冲区中,每次读取count个字节

return:如果count是我们期望读多少个字节,返回就是我们实际读取多少个字节

void FdRead()
{
    int fd=open("./demo.txt",O_RDONLY);//以读的方式打开不涉及到创建,权限也不要写了
    if(fd<0)
    {
        perror("open");
        exit(-1);
    }
    char buff[1024];
    ssize_t s=read(fd,buff,sizeof(buff)-1);//-1是因为我们不需要\0
    if(s>0)
    {
        //说明我们读取到了有效的内容
        //因为我们要把读取到的内容当作一个字符串看待,所以要在最结尾添加一个\0,作为字符串结束标志
        buff[s]=0;
        printf("%s\n",buff);
    }
    close(fd);
}

重定向

输出重定向

echo "hello world">log.txt

我们可以理解为把echo的1关掉,在把log打开,再把所有内容打印到里面

追加重定向

void  Fd_Alloc_Base()
{
    close(1);
    // close(2);//把0对应的标准输入给关闭
    int fd=open("./g.txt",O_CREAT|O_WRONLY|O_APPEND,0644);
    printf("fd=%d \n",fd);
    printf("hello world");
    printf("hello world");
    printf("hello world");
    printf("hello world");
    printf("hello world");
    // close(fd);
}

只在open的时候把append选项加上去

输入重定向

就是把原来从键盘里面读入的东西,现在从一个文件里面读

void Fd_Redefout()
{
    close(0);//把输入给关掉
    int fd=open("./g.txt",O_RDONLY);
    char line[128];
    while(fgets(line,sizeof(line)-1,stdin))//因为现在g.txt的fd为0,所以stdin就是g.txt
    {
        //原本应该从键盘读取的内容,现在是从文件里面读取了
        //输入重定向
        printf("%s\n",line);
    }
}

验证文件描述符

void Verify_IO()
{
   printf("stdin-> %d",stdin->_fileno);
   printf("stdout-> %d",stdout->_fileno);
   printf("stderr-> %d",stderr->_fileno);
}

dup2

newfd是oldfd的一份拷贝,数组内容的拷贝,指针的拷贝

所以全部变成old,

输出重定向

dup2(fd,1)

shell中的重定向

echo “hello” > file.c

fork->child->dup2(fd,1)->exec("echo,“echo”)


fork创建之后 子进程也有fd,而且文件描述符和父进程都一样

但是打开的那些文件不会新建,因为我们是在创建进程,


如果父进程打开了标准输入,输出,错误,子进程也会继承下去

因为bash是所有进程的父进程,而bash打开了标准输入,输出,错误,所以所有的子进程也都继承下去了


缓冲区

标准输出和标准错误

int main()
{
    const char* msg="hello stdout\n";
    write(1,msg,strlen(msg));
    const char* msg2="hello stderr\n";
    write(2,msg2,strlen(msg2));
    return 0;
}

我们发现只有标准输出重定向到了文件里面,但是标准错误仍然打印在屏幕里面了

因为重定向只有fd=1的被弄进去,而fd=2不会被弄进去

 ./redir >log.txt 2>&1

把标准输出和标准错误都重定向进去

$ cat log.txt
hello stdout
hello stderr

./redir >log.txt 2>&1

先执行前面第一条语句,此时把fd=1原本指向显示器改为指向一个特定文件,而fd=2的文件原本指向了显示屏,把1里面的内容拷贝到2里面,所以2也指向1指向的文件


缓存区


1.

int main()
{
    const char* msg="hello stdout\n";
    write(1,msg,strlen(msg));
    const char* msg2="hello stderr\n";
    write(2,msg2,strlen(msg2));
    printf("hello printf\n");
    fprintf(stdout,"hello fprintf\n");
    close(1);
    return 0;
}


第一次可以显示出fprintf

close后,第二次不能输出这些内容


2.

void  Fd_Alloc_Base()
{
    close(1);
    // close(2);//把0对应的标准输入给关闭
    int fd=open("./g.txt",O_CREAT|O_WRONLY|O_APPEND,0644);
    printf("fd=%d \n",fd);
    printf("hello world");
    printf("hello world");
    printf("hello world");
    printf("hello world");
    printf("hello world");
    // close(fd);
}


把fd关闭之后就没有显示内容了


c语言本身也

我们曾经说的缓冲区都是用户级缓冲区,都是语言层面

printf是向stdout写入(FILE*)–》(struct file)

我们使用printf和fprintf,我们并没有写到OS里面,而是写到c语言缓冲区,把c语言的缓存区刷新到操作系统,

我们在特定的情况下才会把数据刷新到内核缓冲区,


遇到\n的时候,会刷新到显示器上面,

进程退出的时候,会刷新FILE 内部的数据到OS,没有进程退出的时候数据还会在C缓冲区里面,

用户------>OS


刷新策略


立即刷新(不缓冲)

行刷新(行缓冲\n):如显示器打印,

全缓冲区满了才刷新(也就是不会溢出),比如,往磁盘文件里面写入

OS—> 硬件,也是同样使用的


如果发生了重定向,

显示器 --> log.txt


原本是行刷新(行缓冲),现在就变成了全缓冲

void  Fd_Alloc_Base()
{
    close(1);
    // close(2);//把0对应的标准输入给关闭
    int fd=open("./g.txt",O_CREAT|O_WRONLY|O_APPEND,0644);
    printf("fd=%d \n",fd);
    printf("hello world");
    printf("hello world");
    printf("hello world");
    printf("hello world");
    printf("hello world");
    // close(fd);
}

对于这个代码


如果不close的话,所有的信息都打印到了文件当中


没有close的话,所有的消息都直接输出到了c语言缓冲区里面,然后因为没有close掉fd,这批数据就会在进程退出的时候,数据就会刷新到内核,


把close放开,文件里面什么都没有,


所有的消息,刷新变成了全缓冲,有可能并没有被写满,说明可能没有立即被刷新到文件里头,而进程退出之前调用了close,就把文件描述符给关了,进程退出的时候数据还在缓冲区里面,来不及刷新到内核当中,所以文件里面就没有看到对应的内容


如果把close(1)关掉,那么printf把数据都刷新到用户缓冲区里头,行刷新,立马刷新了


如果在close之前

fflush(stdout),强制刷新缓冲区里面,就可以了写到文件里面了,


FILE*里面是有包含缓冲区的

int main()
{
    const char *msg = "hello stdout\n";
    write(1, msg, strlen(msg));
    const char *msg2 = "hello stderr\n";
    write(2, msg2, strlen(msg2));
    printf("hello printf\n");
    fprintf(stdout, "hello fprintf\n");
    close(1);
    return 0;
}

我们发现我们即使close(1),仍然能够打印在显示屏上,因为三行刷新,有\n就刷新了


我们把内容重定向到文件里面,

发现只有write的1被写进去了




这是因为我们close(1),当重定向的时候原本要写到1里面的内容,显示到文件中,就从行刷新,变成了全缓冲,不立即刷新,而write是系统调用没有经过c语言缓冲区,就可以打印到文件里面,

#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
    const char *msg = "hello stdout\n";
    write(1, msg, strlen(msg));
    // const char *msg2 = "hello stderr\n";
    // write(2, msg2, strlen(msg2));
    printf("hello printf\n");
    fprintf(stdout, "hello fprintf\n");
    fputs("hello puts\n",stdout);
    // close(1);
    fork();
    return 0;
}

我们如果2往显示器上打印,大家都正常,如果往文件里面打印,c接口会重复,系统接口不受影响

刷新策略变了,

写入文件里面的时候,就变成了全缓冲,数据就先写到了c语言缓冲区里面(不是操作系统提供的),


fork之后发生了写时拷贝,父进程写到了缓冲区里面,子进程也刷新到了缓冲区里面,当副进程退出的时候,就把数据i刷新出去,子进程也要刷新,所以因为写实拷贝的问题,出现了重复刷新


而如果在fork之前就把数据全部fflush出去的话,fork之后就不会发生写实拷贝,因为缓冲区里面没有数据了,可是write没有打印两个,

相关文章
|
2月前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
99 0
|
2月前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
115 1
Linux C/C++之IO多路复用(aio)
|
4月前
|
缓存 安全 Linux
Linux 五种IO模型
Linux 五种IO模型
|
2月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
38 0
Linux C/C++之IO多路复用(poll,epoll)
|
4月前
|
小程序 Linux 开发者
Linux之缓冲区与C库IO函数简单模拟
通过上述编程实例,可以对Linux系统中缓冲区和C库IO函数如何提高文件读写效率有了一个基本的了解。开发者需要根据应用程序的具体需求来选择合适的IO策略。
38 0
|
4月前
|
存储 IDE Linux
Linux源码阅读笔记14-IO体系结构与访问设备
Linux源码阅读笔记14-IO体系结构与访问设备
|
5月前
|
Linux 数据处理 C语言
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(下)
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(下)
84 0
|
5月前
|
Linux 编译器 C语言
【Linux】基础IO----理解缓冲区
【Linux】基础IO----理解缓冲区
82 0
【Linux】基础IO----理解缓冲区
|
5月前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
276 2
|
6月前
|
Linux 编译器 C语言
【Linux】基础IO_4
【Linux】基础IO_4
34 3