Linux-基础IO

目录

1、第一个IO程序

2、默认IO

3、IO库函数与内核函数

1、输入重定向

2、write与fputs

3、read

4、close

5、dup2


1、第一个IO程序

        我们在编写了一个C程序之后,可以看到显示器上打印的内容,也可以从键盘向显示器上输入内容,甚至可以最后将获取到的文本内容存储到文件里面。而这些操作在Linux里面称为IO操作,也就是输入输出操作。

        那么接下来我们写一段简单的向文件写入字符串的小程序

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

int main()
{
    int fd = open("./log.txt", O_RDWR | O_CREAT, 0666);
    if (fd < 0) {
        perror("open");
        exit(1);
    } 
    char* buffer = "abcdef";
    write(fd, buffer, strlen(buffer));
	
	close(fd);
    return 0;
}

 

        通过这段代码,如果可执行程序所在位置处没有log.txt就会创建文件,有就进行写入内容。

        那么在这段代码中,可以看见我们使用了open() ,write(),close()两个函数,这便是进行IO操作的两个常用内核级函数,与之同时还存在着read()。

2、默认IO

C程序默认会打开3个文件IO,那么我们来验证一下这句话的正确性,在刚刚我们打开了一个文件,有一个返回值fd,那么我们将fd打印出来看一下值是多少呢?

         那么为什么是从3开始的呢?刚刚我们说C程序会默认打开3个IO,会不会和着有关呢?是的,你们没有猜错,确实和着3个文件IO有关,默认打开的IO如下:

fd012
名字stdinstdoutstderr
对应文件键盘显示器显示器

        在Linux中,每当我们打开一个可执行程序,是不是就需要键盘输入,显示器打印,如果出错了需要直到出错的原因?因此在Linux中,默认会打开这3个IO。

        那么在OS内可不可能只有3个文件被打开?不可能,那么多的已打开的文件是不是就需要去管理?那么在进程中已经打开的文件我们就可以根据以前学习的PCB来推导处,管理的方式也一定是先描述后组织,一定有一个结构体是用来描述这个文件的,而且这个结构体的一个实例一定作为了PCB的一个成员,而其结构体内部一定会有像pid类似的描述符来描述这个文件,那么下面就是我们的草图,博主画风比较抽象,请接受一下博主的抽象画风,耐心观看,大家也可以根据以上这段文字描述结合PCB,地址空间,页表,物理地址来自己画一下。

 

3、IO库函数与内核函数

        在继续往后阐述之前,我们可以先来想一想以下三个问题:进行IO操作,是从哪里读、写数据?为什么要进行IO操作?是如何进行读、写的?

        先来从感性的层面认识这三个问题

        1、读数据就是将文件里面的数据读出来;写数据就是将我们键盘打出来的字写到文件里面去

        2、为了让这些数据产生价值,存储一些重要数据

        接下来我们从理性的层间认识一下这三个问题

        1、读数据:我们要先有这个文件,然后再通过某个函数调用打开这个文件并获取其中的字符等数据

        写数据:要先有这个文件,如果没有我们就创建一个文件,然后打开这个文件,以覆盖或者追加的形式向其中写入数据

        2、程序在执行的时候,数据往往不单单只是我们进行输入的,有可能是提前准备好的一串数据,或者我们的执行结果不仅仅是要展示在显示器上,也有可能要进行存储供之后查看,比如日志

        3、读数据可以通过read,fgets等函数接口来获取文件流内容

        写数据可以通过write,fputs等函数接口来实现向文件流写数据

        总结来说,我们进行IO操作,是为了让数据产生更大的价值。当然以上这几个问题的答案还不够正确,这里是为了让大家能够以更简单的方式来理解IO操作,在之后,会再一次进行总结。

        在回答了以上三个问题之后,我们再来通过实例,来深入理解IO的相关操作。

1、输入重定向

我们平时在命令行中使用重定向就是使用 >  

 如果是追加则是 >>

那么能不能编写一个程序实现这种功能呢?当然是可以的,让我们继续往下看。

2、write与fputs

ssize_t write(int fd, const void *buf, size_t count);
// 向fd中写入长度为count个字节的数据buf,返回值为真实写进的字节大小

int fputs(const char *s, FILE *stream);
// 向文件流stream中写入字符串s
int fd = open("./log.txt", O_WRONLY | O_CREAT, 0664);
if (fd < 0) {                                         
    perror("open faild::");  
    return 1;  
}  

const char* buffer = "can you see me?\n";  
int cnt = 5;  
while (cnt--) {  
     write(fd ,buffer, strlen(buffer));  
}  

        看到这里,我们就发现buffer的内容被写到了文件里面去,实现了输出重定向

FILE* fd = fopen("./log.txt", "w+");       
if (fd == NULL) {
    perror("open faild::");
    return 1;
}

const char* buffer = "can you see me?\n";
int cnt = 5;
while (cnt--) {
     fputs(buffer, fd);
}

        这段代码也同样能实现将字符串输入到文件中,那他们有什么区别呢?

        这两段都能实现向文件的写数据,但是write是内核级函数调用接口,fputs是C语言级函数调用接口,他们之间存在什么差别呢?在回答这个问题之前,我们要向文件里面写入数据,文件是存储在哪里的?磁盘里面,磁盘本身就是硬件,然而我们能通过函数直接对硬件进行写入读取操作吗?其实不是的,在这期间经历了哪些呢?如果我们通过C语言级函数调用接口来对文件进行写入,内容会先被存储在C语言提供的缓冲区里面,我们之后来查看这个缓冲区是如何存储内容的,然后再由C语言函数调用接口去调用内核级函数调用接口将内容刷新到内核,最后再由内核级函数调用接口将内容刷新到磁盘上去,这就是这两者函数之间的差别,而后还有许多这样类似的函数,也便不再对其做过多解释了。

 

while(1)
{
	printf("11sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss111111111111111111");
	sleep(1);
}

         注意我们在文本结尾没有加\n 或者\r这种可以立即刷新换成区的结束,诸位可以一试,在一开始不会输入内容,会等很久然后会输出一长串内容,这原理就是C语言缓冲区的刷新机制是全刷新,也就是当缓冲区满了的时候才会进行刷新,而我们平时正是因为结尾加上了\n 或者\r或者使用了fflush()函数强制刷新。

3、read

ssize_t read(int fd, void *buf, size_t count);
// 从fd中读取最大长度为count字节的数据到buf中,返回值为真实读取的字节长度

        刚刚了解了通过fd文件描述符将内容写入到文件中去,现在我们来尝试以下反向操作,将文件里面的内容获取出来

 int fd = open("./log.txt", O_RDONLY);

// read 测试
char buffer[1024] = {0};
ssize_t fs = read(fd, buffer, sizeof(buffer) - 1);
buffer[fs - 1] = '\0';   // 在本实验中,将字符串输入进去的时候,末尾有一个\n ,而其在读出的时候,位置是最后一个,fs是当前读取的字节数,因此fs  - 1 就指向了末尾\n
printf ("%s\n", buffer);

         就可以发现将文件内容获取出来了,实现了输入重定向,其原理是如何实现的呢?当open打开文件后,会将文件加载到内存,然后从文件流里面读取文件内容,读取sizeof(buffer) - 1个长度的数据到buffer里面去,接下来就可以输出了。

         通过以上的两个简单的操作,可以初步理解文件的IO操作是如何写进去的,如何读出来的。         

4、close

 int close(int fd);
 // 关闭fd文件流

        close(fd)就是在将文件用完了之后进行及时的关闭。

        但是我们来看下面这段代码,大家来猜一猜此时fd的值会是多少呢?

close(0);
int fd = open("./log.txt", O_WRONLY, 0664);
printf("fd = %d\n", fd);
close(fd);                                   

        大家可以,此刻的fd怎么变成0了,之前不是说默认从3开始吗?这是怎么回事呢?因为文件描述符有一个分配规则,那就是从fd_array[]中找一个最小的值并且没有被使用的文件描述符来进行分配。这段代码里面,我们close(0),那么此刻0就被关闭了,那么再次申请时,下一个新的fd,就可能会替代其位置赋值为0,而默认情况下是从stdin里面获取文本内容,那么此刻将标准输入关闭了,是否是从fd = 0指向的新文件进行获取内容呢?又或者是close(1),向该文件进行输出内容呢?

        那么之前的重定向可不可以通过关闭标准输入或者标准输出来达到呢?答案是可以的,也是肯定的。

close(1); // 如果将1提前关闭了,那么标准输出就被关闭了,取而代之的是新创建的fd,那么现在的1号fd指向着新fd,那么此时printf默认是向标准输出进行打印字符,那么此时就会向此刻fd所指    向的文件流打印,因此就会出现fd = 1 被打印在了文件lod.txt中
// fd 的分配机制:是从fd_array[]中找一个下标最小的,未被使用的fd分配给他
// 输出重定向
int fd = open("./log.txt", O_WRONLY | O_CREAT, 0664);
printf("fd = %d\n", fd);
char buffer[1024] = {0};
ssize_t fs = read(fd, buffer, sizeof(buffer) - 1);
buffer[fs - 1] = '\0';
printf("%s\n", buffer);
close(0);
// 输入重定向
int fd = open("./log.txt", O_WRONLY);
const char* buffer = "hhhhjjjj";
printf("fd = %d\n", fd);
while (fgets(buffer, sizeof(buffer) - 1,stdin)) {
   printf("%s", buffer);
}

 

         我们看到这里,就可以发现,通过关闭相应的标准输入或者标准输出就可以实现重定向了。但是此种方法位面过于麻烦,那么有没有什么简单的办法实现这种重定向呢?既不用手动关闭又可以直接实现重定向,就像替换一样。有的

 

5、dup2

int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
// 将格式化的数据输出到文件流stream中

        通过dup2函数就可以实现

        输出重定向,dup2(oldfd, 1);

//使用dup2(fd, 1);将标准输出替换成一个文件
int fd = open("./log.txt",O_RDWR | O_TRUNC);      
dup2(fd, 1);
printf("hello printf\n");
fprintf(stdout, "hello fprintf\n");
fputs("hello fputs\n", stdout);

        输入重定向,dup2(oldfd, 0);

// 使用dup2(fd, 0); 将标准输入替换成一个文件
int fd = open("./log.txt", O_RDWR, 0664);
dup2(fd, 0);                                       
char buffer[1024] = {0};
while(fgets(buffer, sizeof(buffer) - 1, stdin)) {
    fputs(buffer, stdout);                       
}

        可以发现dup2旱数的本质是将oldfd所指向的文件拷贝到newfd的文件中去,也就是将fd指针拷贝到newfd中去。

        IO的总结:是进程与文件进行交流的一种方式,可以是进程向文件写入数据,进程从文件中读取数据;也可以是文件向进程中的某个数据块写入数据,文件从进程中读取数据。但是这一切都是由我们来决定的,可以通过open,write,read,close四个内核函数调用接口来实现以上的操作,也可以通过C语言函数调用接口来实现。总之在我看来,IO操作就是将数据与进程进一步关联起来,使数据变得更有价值的一种方式,因为一个程序不可能不使用其他文件里面的数据, 同时也能够对文件里面的内容进行增删改查操作。

        本期的分享就到此结束了,感谢大家的观看,喜欢的可以点赞三连支持以下,看到必回!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值