2024年最全基础IO(上),2024年最新价值2000元的学习资源泄露

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

{
umask(0);
int fd1 = open(“log1.txt”, O_WRONLY | O_CREAT | O_TRUNC, 0666);
int fd2 = open(“log2.txt”, O_WRONLY | O_CREAT | O_TRUNC, 0666);
int fd3 = open(“log3.txt”, O_WRONLY | O_CREAT | O_TRUNC, 0666);
int fd4 = open(“log4.txt”, O_WRONLY | O_CREAT | O_TRUNC, 0666);
printf(“%d %d %d %d\n”, fd1, fd2, fd3, fd4);
close(fd1);
close(fd2);
close(fd3);
close(fd4);
return 0;
}


运行截图:


![image-20221024102435751](https://img-blog.csdnimg.cn/img_convert/372be008a718ca84e072902412dbe60c.png)


此时查看open的返回值描述:


![image-20221024102553844](https://img-blog.csdnimg.cn/img_convert/c28fae7c609979ba70f1441eb0779a32.png)


如果返回-1就说明错误发生了,只有当fd>=0才说明文件打开成功。



> 
> 为什么从3开始?0 1 2是什么?
> 
> 
> 0 1 2被默认打开了:
> 
> 
> 0:标准输入:键盘
> 
> 
> 1:标准输出:显示器
> 
> 
> 2:标准输出:显示器
> 
> 
> 上面的可以类比C语言中的stdin,stdout,stderr
> 
> 
> 两者有什么区别呢?0 1 2是针对系统接口的概念,而stdin,stdout,stderr是C语言中的概念。
> 
> 
> C语言中有FILE\*类型的文件指针,那么FILE是什么呢?FILE是一个结构体,封装了很多成员,其中就封装了fd。
> 
> 
> 



> 
> 下面进行验证:
> 
> 
> 
> ```
> #include<stdio.h>
> #include<string.h>
> #include<sys/types.h>
> #include<sys/stat.h>
> #include<fcntl.h>
> #include<unistd.h>
> int main()
> {
> //先验证0 1 2就是标准IO
> char buffer[1024];
> ssize\_t s = read(0, buffer, sizeof(buffer)- 1);//从标准输入中读数据到buffer中
> if(s > 0)
> {
>  buffer[s] = '\0';
>  printf("echo:%s", buffer);
> }
> return 0;
> }
> 
> ```
> 
> 运行截图:
> 
> 
> ![image-20221024104622830](https://img-blog.csdnimg.cn/img_convert/fdf6dbcca3e97b3e5bb14c3946b51b96.png)
> 
> 
> 
> ```
> #include<stdio.h>
> #include<string.h>
> #include<sys/types.h>
> #include<sys/stat.h>
> #include<fcntl.h>
> #include<unistd.h>
> int main()
> {
> const char\* str = "hello world\n";
> write(1, str, strlen(str));//将str字符串中的内容写到标准输出(stdout)中去
> write(2, str, strlen(str));//将str字符串中的内容写到标准错误(stderr)中去
> return 0;
> }
> 
> ```
> 
> ![image-20221024105112972](https://img-blog.csdnimg.cn/img_convert/c4dee6ed84e96c27bd67e5d769925d0a.png)
> 
> 
> 



> 
> 下面再次进行验证stdin stdout stderror和0 1 2之间的对应关系:
> 
> 
> 代码:
> 
> 
> 
> ```
> #include<stdio.h>
> #include<string.h>
> #include<sys/types.h>
> #include<sys/stat.h>
> #include<fcntl.h>
> #include<unistd.h>
> int main()
> {
> printf("stdin:%d\n", stdin->_fileno);
> printf("stdout:%d\n", stdout->_fileno);
> printf("stderr:%d\n", stderr->_fileno);
> return 0;
> }
> 
> ```
> 
> ![image-20221024105750404](https://img-blog.csdnimg.cn/img_convert/546dcd2f5ee35f05d837a6099eeb8ee7.png)
> 
> 
> 



> 
> 总结:
> 
> 
> 函数接口的对应:
> 
> 
> fopen/fwrite/fread… -> open/write/read/…
> 
> 
> 数据类型的对应:
> 
> 
> FILE -> fd
> 
> 
> 


### 理解0 1 2 3 4…


一个进程可以打开文件,包括标准输入、标准输出、标准错误还有其它文件(打开的文件在内存中),进程 : 打开的文件 = 1 : n ,所以系统在运行中有大量被打开的文件,OS要对这些文件进行管理,所以就要先描述后组织。所以一个文件被打开,在内核中就要创建该被打开的文件的内核数据结构,这是描述的过程。



struct file
{
//包含了文件的大部分内容和属性
struct file* next;
struct file* prev;
//只是为了方便描述,实际上内核数据结构有自己的链接方式和结构,但是相互链接是确定的
}


![image-20221024114542771](https://img-blog.csdnimg.cn/img_convert/54242d6816befd25df48a34ae4bfa149.png)


**理解Linux下一切皆文件**


首先先理解一下C语言是如何实现面向对象中的多态的?


![image-20221024135921419](https://img-blog.csdnimg.cn/img_convert/2f6372d264e6b6b0d8993acc43c94284.png)


一切皆文件的理解:


![image-20221024141940325](https://img-blog.csdnimg.cn/img_convert/0db78c6434e329fc3ea43af24e83df15.png)


**注意:磁盘、显示器、键盘、网卡等的read和write的具体方法是驱动负责的。**


**注意:OS内的文件系统也叫做VFS,即虚拟文件系统。**


我们可以通过`ulimit -a`指令来查看打开文件的个数:


![image-20221024143202456](https://img-blog.csdnimg.cn/img_convert/5c671c67ca974f936ef1677be7ed332b.png)


### 文件描述符的分配规则


**从头遍历fd\_array数组,找到一个最小的且没有被使用的下标分配给新的文件**。


代码:



#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
close(0);
int fd = open(“log.txt”, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if(fd < 0)
{
perror(“open\n”);
return 1;
}
printf(“fd:%d\n”, fd);
close(fd);
}


运行截图:


![image-20221024144654507](https://img-blog.csdnimg.cn/img_convert/35e889951c121c8bf34d8c557f5e3bfc.png)


## 重定向的本质及相关操作


### 认识重定向


下面以一个小例子来了解重定向:



#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
close(1);
int fd = open(“log.txt”, O_WRONLY | O_CREAT | O_TRUNC, 0666);
//此时fd等于1,即文件log.txt的文件描述符是1,stdout默认就是1,所以此时如果我们向stdout中进行输出,其实是输出到了文件log.txt中
if(fd < 0)
{
perror(“open\n”);
return 1;
}
//本来应该要往显示器打印,最终变成了向log.txt文件中打印
printf(“fd:%d\n”, fd);
fflush(stdout);//刷新缓冲区
close(fd);
return 0;
}


运行截图:


![image-20221024164002340](https://img-blog.csdnimg.cn/img_convert/c28f01bad491f13ce652aa20644704c6.png)


![image-20221024163658882](https://img-blog.csdnimg.cn/img_convert/e778d1e41b79495a1718e4b9cbff6d4d.png)


如果我们要进行重定向,上层只任0,1,2,3,4,5这样的fd,我们可以在OS内部,,通过这一方式调整数组的特定下标的内容(指向),我们就可以完成重定向操作。


**总结:本来应该向显示器打印,最终变成了向指定文件打印,这就是重定向**。


### 重定向的具体原理


![image-20221024165501034](https://img-blog.csdnimg.cn/img_convert/5eb3f43bb80e1568c31a5d760bd0d360.png)


**如果我们要进行重定向,操作系统只认识0 1 2 3…这样的fd,我们可以在OS内部,通过一定的方式调整数组特定下标的内容(指向),我们就可以完成重定向操作**。


在上面的例子中,就像下面这样进行改变:


![image-20221024165622477](https://img-blog.csdnimg.cn/img_convert/7e139a2314d1d69264d9d71d6b5a15c6.png)


printf是向1(log.txt文件)中进行写入,操作系统仍然认为1是stdout,所以程序运行的结果是向log.txt文件中进行写入。


### 重定向的操作


![image-20221024170522850](https://img-blog.csdnimg.cn/img_convert/a2969dc45e3dd2915f99779e0e4c3701.png)


dup2作用:让新的fd称为旧的fd的拷贝,最终只剩下了旧的fd,如果必要的话,新的fd会被关闭。


**注意:拷贝的不是数组下标,而是相应的数组下标所存储的内容,即struct file**\*。


![image-20221024173239706](https://img-blog.csdnimg.cn/img_convert/a9a2b984266a6346c887a417bc9d1d02.png)


**返回值**:


![image-20221025103832592](https://img-blog.csdnimg.cn/img_convert/2dbd260e0ebbd500029e8a1c2a141fb0.png)


**返回的是新的,即new文件描述符**。



> 
> 问:一个文件是怎么做到被打开多次的呢?
> 
> 
> 答:是通过类似引用计数的方式,当被打开一次的时候,引用计数就+1,close之后,引用计数就-1,当引用计数变成0的时候,文件才会彻底关闭。
> 
> 
> 


### 追加重定向和输入重定向


#### 追加重定向


代码:



#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
int fd = open(“log.txt”, O_WRONLY | O_CREAT | O_APPEND, 0666);//把O_TRUNC换成O_APPEND即可实现追加重定向
if(fd < 0)
{
perror(“open\n”);
return 1;
}
dup2(fd, 1);
printf(“fd:%d\n”, fd);
fflush(stdout);
close(fd);
return 0;
}


运行截图:


![image-20221025105910554](https://img-blog.csdnimg.cn/img_convert/485f2d0d67f44b61b63f159504918d4f.png)


#### 输入重定向


代码:



#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
int fd = open(“log.txt”, O_RDONLY);
if(fd < 0)
{
perror(“open\n”);
return 1;
}
dup2(fd, 0);//输入重定向
char str[64];
while(fgets(str, sizeof(str) - 1, stdin))
{
printf(“%s”, str);
}
close(fd);
return 0;
}


运行截图:


![image-20221025111012382](https://img-blog.csdnimg.cn/img_convert/b0c7a139118c4acdc37e31b3ca521c7a.png)


## 缓冲区的理解


### 什么是缓冲区


缓冲区的本质:就是一段内存。


### 为什么要有缓冲区


* **解放当前使用缓冲区的进程的时间**(即解放当前进程的时间,因为如果当前进程要直接将数据传输到外设中的话,,这个过程要花费很多的时间,且无法处理后面的代码)
* **缓冲区的存在可以集中处理数据刷新,减少IO的次数,从而达到提高整机的效率**


### 缓冲区在哪里


代码测试:



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

int main()
{
printf(“hello printf\n”);
const char* str = “hello write\n”;
write(1, str, strlen(str));
sleep(5);
return 0;
}


sleep前:


![image-20221025114148893](https://img-blog.csdnimg.cn/img_convert/4c1b03aaf402f76170c3439b36e3110d.png)


sleep后:


![image-20221025114544287](https://img-blog.csdnimg.cn/img_convert/7e40491f0abddada8792ee67f292d59d.png)


将上面的代码进行如下修改:



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

int main()
{
printf(“hello printf”);
const char* str = “hello write”;
write(1, str, strlen(str));
sleep(5);
return 0;
}


运行截图:


sleep前:


![image-20221025114428708](https://img-blog.csdnimg.cn/img_convert/94ef93f044b43e2ce89697be9186d1cf.png)


sleep后:


![image-20221025114456027](https://img-blog.csdnimg.cn/img_convert/ea49212cc1057e713d5151b839fa8047.png)


分析:首先printf封装了write,write是立即刷新的,即一旦有数据传给write就会立即在显示器上打印出来,`hello printf`没有先显示的原因是被存放到了缓冲区中,缓冲区没有被刷新,即缓冲区中的数据没有被传给write函数,所以没有被打印出来,当进程结束的时候,会将数据直接传给write函数,数据被立即刷新,所以最后`hello printf`被打印了出来。


![image-20221025123701624](https://img-blog.csdnimg.cn/img_convert/08b06719bc63f30549ba7577bc41e880.png)


总结:缓冲区在哪里呢,只能是由特定的语言提供的,**stdout的类型是FILE类型的,缓冲区就封装在这个结构体中**,**缓冲区不是内核级别的**。缓冲区在FILE内部,在C语言中,每一次打开一个文件,都要有一个FILE\*会返回,意味着每一个文件都有一个fd和属于它自己的语言级别的缓冲区。所以open函数在打开文件的时候创建了一个FILE类型的结构体对象。



//在 / usr / include / libio.h
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
//缓冲区相关
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char* _IO_save_base; /* Pointer to start of non-current get area. */
char* _IO_backup_base; /* Pointer to first valid character of backup area */
char* _IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker* _markers;
struct _IO_FILE* _chain;
int _fileno; //封装的文件描述符
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it’s too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t* _lock;
#ifdef _IO_USE_OLD_IO_FILE
};



> 
> 问:在上面的例子中,如果关闭了1会发生什么情况?
> 
> 
> 答:
> 
> 
> 代码:
> 
> 
> 
> ```
> #include<stdio.h>
> #include<string.h>
> #include<sys/types.h>
> #include<sys/stat.h>
> #include<fcntl.h>
> #include<unistd.h>
> 
> int main()
> {
>     printf("hello printf");
>     const char\* str = "hello write";
>     write(1, str, strlen(str));
>     close(1);
>     sleep(5);
>     return 0;
> }
> 
> ```
> 
> 运行截图:
> 
> 
> ![image-20221025124713529](https://img-blog.csdnimg.cn/img_convert/77c1fc2bf3a3d058cbcfb9a4cbee6920.png)
> 
> 
> ![image-20221025124728382](https://img-blog.csdnimg.cn/img_convert/e1c94f90fc6b998c0e425c25a8bedc9f.png)
> 
> 
> 问:为什么会出现这样的情况呢?
> 
> 
> 答:因为下标1的file\*指针已经不再指向stdout的file结构体了,自然无法找到之前的缓冲区然后调用write去刷新数据了。
> 
> 
> 


### 刷新策略



> 
> 什么时候刷新?
> 
> 
> 常规:
> 
> 
> * 无缓冲(立即刷新)
> * 行缓冲(逐行刷新):显示器文件
> * 全缓冲(缓冲区写满才刷新):块设备(磁盘文件)
> 
> 
> 特殊情况:
> 
> 
> * 进程退出
> * 用户强制刷新(fflush)
> 
> 
> 注意:exit()和\_exit()的区别,exit()会刷新缓冲区,\_exit会清空缓冲区。
> 
> 
> 


### 奇怪的代码(和子进程相关)


代码:



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

int main()
{
const char* str1 = “hello printf\n”;
const char* str2 = “hello fprintf\n”;
const char* str3 = “hello fputs\n”;
const char* str4 = “hello write\n”;
//C库函数
printf(“%s”, str1);
fprintf(stdout, “%s”, str2);
fputs(str3, stdout);

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

代码:

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

int main()
{
  const char\* str1 = "hello printf\n";
  const char\* str2 = "hello fprintf\n";
  const char\* str3 = "hello fputs\n";
  const char\* str4 = "hello write\n";
  //C库函数
  printf("%s", str1);
  fprintf(stdout, "%s", str2);
  fputs(str3, stdout);


[外链图片转存中...(img-stAvpoSF-1715760653761)]
[外链图片转存中...(img-PCuBLtmg-1715760653761)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618668825)**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值