【Linux】文件基础 IO 操作

18 篇文章 0 订阅

库函数 IO

打开文件 fopen

fopen 返回一个文件的操作句柄,有了句柄才能对指定文件进行操作
FILE fopen(char pathname,char* mode);

mode—文件操作方式:
(1)r----只读打开(文件不存在报错)
(2)w----只写打开(文件不存在会创建,存在则截断长度为 0 ,丢弃原内容)
(3)a----追加写打开(文件不存在创建,存在则写入数据总是写入文件末尾)
(4)r+ -----读写打开(文件不存在报错)
(5)w+ ----读写打开(文件不存在创建,丢弃原内容)
(6)a+ ---- 读写打开(不存在创建,数据写入末尾)
(7)b ---- 以二进制形式打开(默认不使用 b ,则使用文本方式打开-----对特殊字符处理会有不同)

以文本形式打开,有可能会造成读到的数据与文件实际的数据有差别

在这里插入图片描述

向文件写入数据 fwrite

fwrite
size_t fwrite(char* data,int bsize,int nmem,FILE*fp);
(1)bsize:文件块个数
(2)nmem:文件块大小

在这里插入图片描述

从文件读取数据 fread

fread
size_t fread(char* buf,int bsize,int nmem,FILE* fp);

在这里插入图片描述

关闭文件 fclose

fclose
int fclose(FILE* fp);

在这里插入图片描述

跳转当前读写位置 fseek

fseek
int fseek(FILE* fp,int offset,int whence);
(1)offset:偏移地址
(2)whence:文件起始地址
从 whence 位置进行偏移,whence 可能取值:SEEK_SET(文件起始地址)、SEEK_END(文件末尾)、SEEK_CUR(指定起始位置)

在这里插入图片描述

练习

   1 #include<stdio.h>                                                         
    2 #include<string.h>
    3 
    4 int main()
    5 {
    6   FILE* fp=fopen("./test.txt","w+");   //w+ 读写方式打开
    7   if(NULL==fp){
    8     perror("fopen error");
    9     return -1;
   10    }
   11 
W> 12   char *data="天黑了!\n";
   13   //fwrite(数据地址,块大小,块个数,句柄)
   14   size_t ret=fwrite(data,1,strlen(data),fp);   //返回实际写入的完整块个数
   15 
   16   //其实就是写入的字节长度,因为块大小为 1
   17 
   18   if(ret!=strlen(data)){
   19     perror("fwrite error!");
   20     fclose(fp);
   21     return -1;
   22   }
   23 
   24 
   25   fseek(fp,0,SEEK_SET);  //从文件起始位置开始跳转
   26 
   27 
   28   char buf[1024]={0};
   29   ret=fread(buf,1,1023,fp);  //块大小 1,块个数=想要读取的长度,这样返回值      就是实际读取到的字节长度
   30   if(ret==0){
   31    if(feof(fp))    //判断是否读到文件末尾
   32       printf("读取到了文件末尾,end of file");
   33     if(ferror(fp))    //判断上一次对 fp 的操作是否出错
   34     {
   35       perror("fread error");
   36       fclose(fp);
   37       return -1;
   38     }   
   39   }
   40   
   41   printf("%s\n",buf);
   42   fclose(fp);
   43 
   44   return 0;                                                               
   45 }

运行结果:

在这里插入图片描述

在这里插入图片描述

以上都是库函数,库函数是对系统调用接口的封装

系统调用接口:
open、write、read、close、lseek

系统调用接口

open、write、read、lseek、close

open

int open(char* pathname,int flag);
int open(char* pathname,int flag,int mode);
(1) pathname:要打开的文件路径

(2) flag:文件的打开方式
必选其一:
O_RDONLY ----00 可读
O_WRONLY ----01 可写
O_RDWR ----- 02 可读可写
可选项:
O_CREAT -----文件不存在会创建
O_TRUNC ----- 截断文件原有内容,丢弃原内容
O_APPEND ---- 追加写

w+:可读可写,文件不存在则会创建,文件已存在则截断内容为0 ======= O_RDWR | O_CREAT | O_TRUNC

a+:可读追加写 ,文件不存在则会创建 ======= O_RDWR | 0=CREAT | O_APPED

(3) mode:当 O_CREAT 被使用时,就一定要设置 mode ,用于设定文件访问权限 0664

0664 --------- 前边的 0 不能省略,否则会涉及特殊权限位的设置

给定权限会受到 umask 影响:

实际权限=给定权限 & (~umask),默认 umask=0002

mode_t umask(mode_t mask); 将当前调用的进程掩码设置位 mask ,并不会影响外部的 umask

返回值:打开成功则返回一个文件描述符(非负整数)作为文件操作句柄;失败返回 -1

write

ssize_t write(int fd,char* buf,size_t len);
(1) fd: open 打开文件时返回的操作句柄-文件描述符
(2) buf: 要写入的数据所在空间首地址
(3) len: 要写入数据的长度(字节为单位)
返回值:成功返回实际写入数据长度,失败返回 -1

read

ssize_t read(int fd,char* buf,size_t len);
(1) fd : open打开文件时的操作句柄
(2) buf:一块空间首地址,用于存放读取到的数据
(3) len:读取长度,不能大于buf空间的大小,避免越界
返回值:成功返回实际读取到的数据长度(字节为单位),失败返回 -1,0表示读到文件末尾

lseek

off_t lseek(lint fd,off_t offset,int whence);
(1)fd:文件描述符
(2)offset:偏移量
(3)whence:偏移的起始位置
SEEK_SET; SEEK_CUR; SEEK_END
返回值:当前跳转后,读写位置相当于文件起始位置位置的偏移量(接口有一种另类用法,跳转到文件末尾,通过返回值确定文件大小)

close

int close(int fd);
fd:文件描述符

说明:

size_t :无符号整型
ssize_t :有符号整型
off_t:整型

使用练习:

 1 #include<stdio.h>
  2 #include<string.h>
  3 #include<sys/stat.h>
  4 #include<fcntl.h>
  5 #include<unistd.h>                                                                       
  6 
  7 int main()
  8 {
  9   umask(0);      //采用默认设置的权限值,只在当前文件内有效
 10   int fp=open("./tmp.txt",O_RDWR|O_CREAT|O_TRUNC,0777);
 11   if(fp<0){
 12     perror("open error");
 13     return -1;
 14   }
 15 
 16   //打开成功
 17 
 18   const char *str="hello world!\n";
 19   ssize_t ret=write(fp,str,strlen(str));
 20   if(ret<0){
 21     perror("write error");
 22     close(fp);
 23     return -1;
 24   }
 25 
 26   //写入数据之后,文件标识到达文件末尾
 27   lseek(fp,0,SEEK_SET); //起始位置开始
 28 
 29 
 30   //写入成功,读取数据
 31   char buf[1024];
  32   ret=read(fp,buf,strlen(str));
 33 
 34   if(ret<0){
 35       perror("read error");
 36       close(fp);
 37       return -1;
 38   }
 39   printf("%s",buf);
 40   close(fp);
 41   return 0;
 42 }               

运行结果:
在这里插入图片描述
在这里插入图片描述

库函数是对系统调用接口的一个封装,一般来说对于系统调用接口来说是没有缓冲区的,而库函数封装了缓冲区以及系统调用接口(还有文件描述符)--------- 类似于前边所说的 exit 与 _exit

重定向

将原本要写入 A 位置的数据不写入 A ,而是写入 B

ls > a.txt 标准输出重定向,将原本要写入标准输出打印的数据写入 a.txt 文件

>:清空重定向,清空原来内容
>>:追加重定向,追加到原内容末尾

在这里插入图片描述


./main > /dev/null 2>&1:

将标准输出与标准错误都重定向到 /dev/null 文件中
(1) > /dev/null :将标准输出重定向到 /dev/null 文件
(2) 2>&1 : &1表示标准输出,相当于将标准错误重定向到标准输出 ,2 是标准错误

在这里插入图片描述

./main 2>&1 /dev/null:
将标准错误打印到标准输出,再将标准输出写入到 /dev/null 文件当中

在这里插入图片描述

文件描述符

在这里插入图片描述

0-标准输入
1-标准输出
2-标准错误

文件描述符是按照最小未使用原则进行分配的,因此应该从 3 开始进行分配:

在这里插入图片描述

#include<stdio.h>
#include<fcntl.h>          //open 头文件
#include<unistd.h>

int main()
{
	int fd=open("tmp.txt",O_RDWR);
	if(fd<0){
		perror("open error");
		return -1;
	}

//打开文件成功
printf("fd = %d\n",fd);    
//当再打开文件之前并未关闭标准输入、标准输出、标准错误描述符时,会按照最小未使用原则进行,则此时打开的文件描述符为 3
close(fd);
return 0;
}

(1)close(0); 关闭标准输入

当关闭了 0 号描述符之后,打开一个新文件,会默认按最小未使用原则,新文件的描述符就会成为 0:

在这里插入图片描述

当在打开文件前关闭了标准输出,则printf 不会打印出来内容:

(2)close(1) ; 关闭标准输出

printf 是向 stdout 写入数据,stdout 本质是封装了 1 号文件描述符信息,printf 打印信息,实际是将数据信息写入了 stdout 缓冲区,但是 close 关闭文件时会直接将缓冲区的信息直接释放。

在这里插入图片描述

在这里插入图片描述

假如在最后对标准输出进行刷新:
fflush(stdout) ;
则会将要打印的数据写入到 1 号文件描述符位置,但是当前 1 号文件描述符对应的是 tmp.txt 文件,即将要写入的数据写入 tmp.txt 文件当中。

在这里插入图片描述

重定向本质原理:

将一个描述符所对应位置的文件描述信息地址,给替换成另一个文件的描述--------实现了在不改变其他逻辑的情况下,改变了所操纵的文件。

int dup2(int oldfd, int newfd);
让 newfd 对应位置,保存 oldfd 对应位置的信息
将 newfd 重定向到 oldfd
若 newfd 本身代表了一个已经打开的文件,则重定向前会把文件关闭释放

dup2(fd,1); 将标准输出的内容输入到 fd 文件中

在 minishell 中实现重定向 > 与 >>

(1)捕捉用户输入,并进行分割;
(2)判断输入的命令是否存在重定向符号,一般重定向符号之后内容不是指令内容,而是要重定向到的文件信息;

   1  #include<stdio.h>                                                                    
    2 #include<stdlib.h>
    3 #include<string.h>
    4 #include<unistd.h>
    5 #include<sys/wait.h>
    6 #include<fcntl.h>        //open
    7 #include<sys/stat.h>   //open
    8 
    9 
   10 int main()
   11 {
   12   while(1){
   13     printf("【user@host~ 】$ ");
   14     fflush(stdout);    //刷新缓冲区
   15 
   16     char cmd[32]={0};            //用于存储键盘输入的字符串
   17     fgets(cmd,1023,stdin) ;      //获取键盘输入的字符
   18 
   19     cmd[strlen(cmd)-1]='\0';   //将键盘输入的字符后加上 \0 表示结束
   20 
   21     printf("[%s]\n",cmd);   //打印输入的字符内容
   22 
   23     int argc=0;
   24     char* argv[32]={NULL};    //用于存储分割后的字符内容
W> 25     char* ptr=cmd;   //保存获取到的字符信息
   26     argv[argc++]=strtok(cmd," ");        //以空格对字符串进行分割处理
   27 
28 
   29     while((argv[argc]=strtok(NULL," "))!=NULL){  //strtok 第一个参数 NULL ,表示默认>      以上一次终止位置作为新分割的起始位置进行分割
   30       printf("[%s]\n",argv[argc]);
   31       argc++;
   32     }
   33 
   34 //打印分割后的字符内容
   35     int i=0;
   36     for(;argv[i]!=NULL;++i){
   37       printf("[%s]\n",argv[i]);
   38     }
   39 //如果当前输入的第一个字符为 cd,则更改工作路径
   40     if(strcmp(argv[0],"cd")==0){
   41       chdir(argv[1]);      //更改当前工作路径
   42       continue;
   43     }
   44 
   45 
   46       
   47     //判断是否存在重定向符号                                                         
   48     // ls -l -a > a.txt              //默认重定向符号之后有个空格
   49     //
   50     int redir_flag=0;            //0-没有重定向,1-清空重定向,2-追加重定向
   51     char *redir_file=NULL;   //标记文件
   52     for(i=0;i<argc;++i){
53       if(strcmp(argv[i],">")==0){
   54         //清空重定向
   55         redir_flag=1;
   56         argv[i]=NULL; 
   57         break;          //重定向之后的内容为文件名,而不是指令内容
   58         redir_file=argv[i+1];
   59       }
   60       else if(strcmp(argv[i],">>")==0){
   61         //追加重定向
   62         redir_flag=2;
   63         argv[i]=NULL;
   64         break;
   65         
   66         redir_file=argv[i+1];
   67       }
   68     }
   69                                                                                      
   70 
   71 
   72     //创建子进程
   73     pid_t cpid=fork();
   74     if(cpid<0){
 75       perror("fork error!");
   76       continue;                                                                      
   77     }
   78     else if(cpid==0){
   79       //子进程实现程序替换
   80       //
   81       if(redir_flag==1){
   82         //清空重定向
   83         int fp = open("tmp.txt",O_RDWR|O_CREAT|O_TRUNC,0664);    //清空打开文件
   84         dup2(fp,1);
   85       }
   86       else if(redir_flag==2){
   87         //追加重定向
   88         int fp=open("tmp.txt",O_RDWR|O_CREAT|O_APPEND,0664);
   89         dup2(fp,1);    //将标准输出重定向到 fp 文件
   90 
   91       }
   92 
   93       execvp(argv[0],argv);
   94       perror("execvp perror");
   95       exit(-1);  //只有程序替换失败才会执行这行代码
   96     }
   97     wait(NULL); //每一个子进程运行完毕后,才能开始捕捉下一个输入进行操作
  98   }
   99 
  100   return 0;
  101 }


在这里插入图片描述
在这里插入图片描述

静态库 & 动态库

静态库

库文件
将已经实现的代码进行打包,并不是为了生成可执行程序,而是为了给其他人提供接口使用。

静态链接

生成可执行程序时,链接静态库,直接将库中所用到的函数实现拿到可执行程序当中,不存在依赖,运行效率高,但是生成的可执行程序很大----------------可能库中代码在内存中会存在冗余

动态库

以位置无关代码打包

动态链接

生成可执行程序时,链接动态库,记录库中符号表,生成程序小,并且多个程序运行时在内存中可以共享动态库内容,运行时依赖动态库的存在

生成库

生成库:把大量代码打包起来

(1)先把所以源码进行编译汇编生成各自的二进制指令 gcc *.c -o *.o
(2)把所有生成的二进制文件打包在一起

:vnew 文件名

新开一个 vim 窗口 ,命名为 当前给定的文件名

在这里插入图片描述
在这里插入图片描述

ctrl + ww :进行两个 vim 文件的跳转

静态库: ar -cr

Linux 下静态库命名:以 lib 为前缀,以 .a 为后缀

gcc -c child.c -o child.o
ar -cr libmychild.a child.o
生成一个名为 mychild 的静态库

在这里插入图片描述

动态库

linux 下动态库命名:以 lib 作为前缀,以 .so 作为后缀,中间是库名。

gcc -c child.c -o child.o

gcc -fPIC -c child.c -o child.o
gcc --shared child.o -o libmychild.so
生成一个名为 mychild 的动态库

在这里插入图片描述

-fPIC:告诉编译器,在编译生成指令的时候产生与位置无关代码(变量指令的地址都是相对偏移量)
一个动态库被映射到不同进程的虚拟地址空间时,映射的位置也不一定相同,所以在 main 中调用的库函数都只是记录一个相对偏移量,最后根据实际映射位置的起始地址进行计算。--------更加灵活

-c:只进行预处理,编译,汇编完毕后退出,不进行链接

–shared:告诉编译器要生成的是一个库文件,而不是可执行程序

使用库

直接运行 main 程序时会报错,因为未找到 printf_child 的定义:

在这里插入图片描述

使用 -l 来告诉编译器要使用哪个库

gcc main.c -o main -lmychid
mychild 是要链接的库文件

但是会报错,因为编译器会到系统指定目录下(/usr/lib64)来寻找库文件

在这里插入图片描述

因此我们可以

(1)将库放到指定位置下 :

x64------/usr/lib64 ; x32-----------/usr/lib

sudo cp libmychild.so /usr/lib64
//但是一般不推荐,因为这样会污染 /usr/lib64 库文件 

(2)设置环境变量:

export LIBRARY_PATH=${LIBRARY_PATH}:./
//将库文件所在目录添加到环境变量
//当前要使用的库文件位于 ./  当前目录下 
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:./

//添加运行加载库文件的路径

在这里插入图片描述

(3)使用 gcc -L 选项,指定库文件所在路径

gcc main.c -o main -L./  -lmychild

在这里插入图片描述

这种方式只适合在指定路径下,链接静态库,因为无法设置运行程序时动态库的加载路径

ps:
有任何疑问欢迎评论
留言~
~

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux IO 模型是指 Linux 操作系统中的 IO 处理机制。它的目的是解决多个程序同时使用 IO 设备时的资源竞争问题,以及提供一种高效的 IO 处理方式。 Linux IO 模型主要分为三种:阻塞 IO、非阻塞 IOIO 多路复用。 阻塞 IO 指的是当程序进行 IO 操作时,会被挂起直到 IO 操作完成,这种方式简单易用,但是对于高并发环境不太适用。 非阻塞 IO 指的是程序进行 IO 操作时,如果无法立即完成,会立即返回一个错误码,程序可以通过循环不断地进行 IO 操作来实现轮询的效果。非阻塞 IO 可以提高程序的响应速度,但是会增加程序的复杂度。 IO 多路复用指的是程序可以同时监听多个 IO 设备,一旦有 IO 事件发生,就会立即执行相应的操作IO 多路复用可以提高程序的效率,但是需要程序员手动编写代码来实现。 Linux IO 模型还有其他的实现方式,比如信号驱动 IO 和异步 IO 等。但是这些方式的使用比较复杂,一般不常用。 ### 回答2: Linux中的IO模型是指操作系统在处理输入输出的过程中所遵循的一种方式。它主要包括阻塞IO、非阻塞IO、多路复用IO和异步IO四种模型。 阻塞IO是最简单的IO模型,当一个IO操作发生时,应用程序会被阻塞,直到IO操作完成才能继续执行。这种模型的特点是简单直接,但是当有多个IO操作时会造成线程的阻塞,影响系统的性能。 非阻塞IO是在阻塞IO基础上发展而来的,应用程序在发起一个IO操作后可以继续执行其他任务,不必等待IO操作的完成。但是需要通过轮询来不断地检查IO操作是否完成,效率相对较低。 多路复用IO使用select、poll、epoll等系统调用来监听多个IO事件,当某个IO事件就绪时,应用程序才会进行读写操作,避免了前两种模型的效率问题。多路复用IO模型适用于连接数较多时的场景,如服务器的网络通信。 异步IO是最高效的IO模型,应用程序发起一个IO操作后,立即可以执行其他任务,不需要等待IO操作的完成。当IO操作完成后,操作系统会通知应用程序进行后续处理。异步IO模型常用于高吞吐量、低延迟的应用,如高性能服务器和数据库等。 总之,Linux IO模型提供了多种不同的方式来处理输入输出,每种模型都有其适用的场景和特点。选择合适的IO模型可以提高系统的性能和效率。 ### 回答3: Linux IO模型是指操作系统中用于处理输入输出操作的一种方法或机制。在Linux中,常见的IO模型有阻塞IO、非阻塞IOIO多路复用和异步IO。 阻塞IO是最基本的IO模型,当应用程序发起一个IO请求时,它将一直阻塞等待直到IO操作完成,期间无法做其他任务。虽然简单易用,但是对资源的利用不高。 非阻塞IO在发起一个IO请求后,不会阻塞等待IO操作完成,而是立即返回并继续做其他任务。应用程序需要不断地轮询IO操作状态,直到操作完成。由于需要不断轮询,对CPU的占用较高,但可以提高资源的利用率。 IO多路复用是通过一个线程同时监听多个IO事件,从而实现并发处理多个IO操作。在IO多路复用模型中,应用程序不需要进行轮询,而是通过调用select、poll或epoll等系统调用监听多个文件描述符的IO事件。这样可以在单个线程中处理多个IO操作,提高并发性能。 异步IO模型在发起一个IO请求后,应用程序不需要等待IO操作完成,而是继续做其他任务。当IO操作完成后,操作系统会通知应用程序。异步IO模型需要操作系统的支持,效率较高,但实现较为复杂。 通过选择合适的IO模型,可以根据不同的应用场景来提高IO操作的效率和性能。例如,对于需要同时处理大量连接的服务器应用,IO多路复用是一种常见的选择;而对于需要处理大量IO操作的高性能服务器,则可以考虑使用异步IO模型。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值