Linux--IO
Posted 水澹澹兮生烟.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux--IO相关的知识,希望对你有一定的参考价值。
目录
一.C语言当中文件操作接口(文件输入/输出)
有时,需要程序从文件中读取到信息或把信息写入文件。这种程序与文件交互的形式就是文件重定向。在C语言中我们学习了一些文件操作接口,可以在程序中打开文件,然后使用特殊的I/O函数读取文件中的信息,或把文件信息写入文件。C提供了两种文件模式:文本模式和二进制模式。
1.1.fopen()
File* fopen(const char* path,const char* mode);
参数:
- path:带打开文件的路径
- mode:以何种方式进行打开(r,w,a)
r 可读 r+ 可读可写 w
可写 w+ 可读可写(如果文件不存在,则创建文件如果是打开文件已存在,则截断文件,清空文件内容) a 追加(追加写,但是不可读,如果文件不存在则创建文件,存在则在文件末尾写入) a+ 可读可追加
返回值:
- 成功:返回文件流FILE
- 失败:返回NULL
1.2fread()
Size_t fread(void* ptr , size_t size , size_t nmemb , FILE* stream);
参数:
- ptr:将文件读到的内容保存在ptr指向的空间当中
- size:定义读文件时,一个块的大小,单位为字节
- nmemb:期望可以从文件的多少个块
- size *nmemb:相当于期望从文件当中都多少个字节
- stream:文件流指针
返回值:返回的是真正读取到的块的个数
常用方法:将一块的大小定义成1字节,nmemb就可以代表我们期望的多少字节,返回含义也是读到的字节数。
1.3fwrite()
Size_t fwrite(const void* ptr , sizr_t size , size_t nmemb , FILE* stream);
参数:
- ptr:想往文件当中写入的内容
- size:定义往文件当中写的块的大小,单位为字节
- nmemb:期望写多少块
- stream:文件流指针
返回值:返回的是成功写入到文件当中的块的个数
常用方法: 定义的块的大小为1字节,nmemb则为想要写入的字节数量,返回值则为成功写入的字节数量
二.系统调用文件接口
操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问。从数据操作方式这个角度来叔,Linux系统中的文件,可以看作是数据流。对文件进行操作之前,必须先调用标准I/O库函数fopen()将数据流打开,打开数据流之后,就可以对数据流进行输入输出操作。
2.1open()
//需要的头文件
#include<sys/type.h>
#include<sys.stat.h>
#include<fcntl.h>
//形式
int open(const char* pathname, int flags);
int open(const char* pathname, int flags, mode_t mode);
int open(const char* pathname, mode_t mode);
参数:
- pathname:待要打开的文件
- flags:以何种方式打开
要将上面的选项进行组合,应该使用按位或的方式进行组合。例如:O_RDWR|O_CREAT|O_APPEND。有且只能存在一种,并且必须有
O_RDONLY 只读方式 O_WRONLY 只写方式 O_RDWR 读写方式 可选项 O_TRUNC 截断文件(清空文件内容) O_CREAT 文件不存在则创建文件 O_APPEND 追加的方式 O_EXCL 如果文件存在,则打开文件失败 - mode:当文件是新打开的一个文件,就需要给文件设置权限,设置权限的时候,传递8进制数字就可以了。
返回值:成功返回一个文件描述符,失败则返回-1。
注意:open()返回的文件描述符一定是该进程尚未使用的最小描述符。由于程序其定时自动打开标准输入,标准输出,和标准错误输出,一次文件描述符0,1,2会存在,那么第一次调用open()函数打开文件时返回文件描述符通常是3,在调用open()函数就会返回4。可以利用这一点在标准输出,标准输入和标准错误输出上打开一个新未见,实现重定向功能。比如说:首先调用一个close()函数关闭标准描述符1,然后调用open()函数打开一个常规文件,则一定会返回文件描述符1,这是标准输出就不再是终端,而是一个常规文件,再调用printf()函数就不会打印到屏幕上,而是笑道这个文件当中。
2.2read()
ssize_t read(int fd,void *buf, size_t count);
参数:
- fd:文件描述符
- buf:将文件内容读到buf指向的空间当中
- count:期望读多少字节;返回的字节数有时候会小于参数count的值,这几种情况分别是:督导常规文件时在读到count个字节之前已经到达文件末尾;从终端设备读,通常以行为单位,读到换行符就返回;从网络读,根据不同的传输层协议和内核缓存机制,返回值可能小于请求字节数。
返回值:返回读到的字节数
注意:这个读写位置和C标准I/O库值的读写位置可能不同,这个读写位置时记载内核当中的,而C中读写位置时用户空间I/O缓冲区中的位置。
2.3write()
#include<unistd.h>
ssize_t wirte(int fd,const void *buf,size_t count);
参数:
- fd:代表想要写入数据的文件描述符
- buf:参数buf指向写入文件的数据的缓冲区
- count:代表的是写入文件的数据的字节数
返回值:如果函数调用成功还会写入的字节数,否则返回-1,并设置适当的errno值
注意:当向常规文件写入数据时,返回值回事字节数count,但是当向终端设备或者网络中写入数据时,返回值则不一定为写入的字节数。
2.4lseek()
每个打开文件都记录着当前读写位置,打开文件时读写位置为0,表示文件的开头,通常读写多少个字节就会将读写位置往后移多少个字节。
#include<ysy/types.h>
#include<unistd.h>
off_t lseek(int fd,off_t offset,int whence);
参数:
- fd:文件描述符偏移量
- offset:
- whence: 参数whence代表偏移值的相对位置
SEEK_SET 从文件开头的位置计算偏移量 SEEK_CUR 从当前位置开始计算偏移量 SEEK_END 从文件的末尾位置开始计算偏移量
2.5close()
#inlcude<unistd.h>
int close()int fd;
close()函数主要用于关闭一个已经打开的文件,如果函数调用成功,返回值为0,如果调用失败,返回值为-1,并设置成errno值。参数fd是要关闭的文件包描述符。
注意:当一个进程终止时,内核对该进程所有尚未关闭的文件描述符调用close()函数关闭,所以即使用户程序不调用close()函数,在终止时内核也会自动关闭他打开的所有文件。但是对于网络服务器这种一直在运行的程序,文件描述符一定要及时关闭,负责随着打开的文件越多,会占用大量的文件描述符和操作系统资源。
2.6代码
通过上述函数来对文件进行操作
#include<stdio.h>
#include<sys/types.h>
#include<sys/stateh>
#include<fcntl.h>
#include<unistd.h>
int main(){
int fd = open("./bite.txt",O_RDWR | O_CREAT,0664);
if(fd < 0){
perror("open");
return 0;
}
printf("fd = %d \\n",fd);
write(fd, "i like linux!", 20);
lseek(fd, 0 ,SEEK_SET);
char buf[1024] = {0};
//-1的作用时预留\\0位置,防止访问越界
read(fd,buf,sizeof(buf)-1);
printf("buf: %s\\n",buf);
return 0;
}
三.文件描述符
3.1文件描述符的本质
我们要从内核当中其进行分析,所谓的文件描述符就是进程与打开文件的一个桥梁,通过它,才可以在进程中对这个文件进行读写操作。
现在我们要理解文件描述符是如何定义的,如下图所示:
3.2文件描述符的分配方式
代码验证:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
//close(0);
int fd = open("./1.txt", O_RDWR | O_CREAT, 0664);
if(fd < 0){
perror("open:");
return 0;
}
printf("fd = %d\\n", fd);
while(1){
sleep(1);
}
return 0;
}
最小未分配原则
3.3文件描述符和文件流指针的区别
一个文件流指针只能保存一个文件描述符。
文件流指针的本质如下图:
注意:
- 一个文件流指针只能保存一个文件描述符,但是一个文件描述符可以被多个文件流指针打开。
- C标准库对应的缓冲区是读缓冲区和写缓冲区。
- 文件流指针和文件描述符的关系是文件流至真当中包含文件描述符。
- exit()和_exit()之间的区别是exit()函数比他多了回调函数和刷新缓冲区。
四.重定向
4.1重定向的本质
4.1.1重定向的符号
- > 清空重定向
- >> 追加重定向
4.1.2从文件描述符的角度理解重定向
我们可以参考文件描述符的本质的图进行参考,在这个图里面,当前我们创建了这个进程之后,就有了三个文件描述符,标准输入,标注输出和标准错误输出,有了这三个文件描述符后,当我们再次在文件当中用open()打开一个文件,我们就创建了一个3号描述符,而三号描述符也对应了struct file*这个结构体,而当前的这些结构体在磁盘当中与某个文件与之对应,而此时标准输入,标注输出和标准错误输出对应的是设备文件,而此时我们想要让标准输出的内容从定向到文件当中,也就是说将1好文件描述符原本输出到设备当中改成输出到3号文件当中,也就可以说让1号文件描述符的struct file*指针指向3号文件描述符对4应的struct file结构体。这就是重定向的本质。
4.2重定向的接口
int dup(int oldfd ,int newfd);
newfd拷贝oldfd的值,将newfd重定向为oldfd。如果要成功,则要关闭newfd,且让newfd指向oldfd。
失败的情况:
- oldfd是一个非法的文件描述符,或者不存在的文件描述符。函数调用失败,并且没有关闭newfd。
- newfd和oldfd的值是相等的,则dup2函数什么事情都没有做。
4.3重定向的实现代码
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(){//将标准输出重定向成一个txt文件
int fd = open("./bite.txt", O_RDWR | O_CREAT, 0664);//打开一个文件
if(fd < 0){//失败
perror("open");
return 0;
}
dup2(fd, 1);
printf("hello world...\\n");
return 0;
}
五.静态库和动态库
静态库和动态库都是程序代码的集合,将代码集合封装在了库文件当中,提供给调用者使用。
5.1动态库
1.特征:在windows操作系统下,动态库文件后缀为.dll;在linux操作系统下,前缀为lib,后缀为.so;例如libxyz.so。
2.生成动态库:使用gcc/g++编译命令,需要用到两个命令行参数
- -FPIC:产生位置无关的代码
- -shared:生成共享库
3.使用:使用gcc/g++的命令行参数
- -L [path]:指定当前链接库文件所在路径
- -L [库文件名称(去掉前缀和后缀的名称)]:连接某个库文件
4.动态库的环境变量:LD_LIBRARY_PATH
5.可执行程序找到自己依赖的动态库的方式
- 将动态库放到可执行程序的路径下
- 配置LD_LIBRARY_PATH环境变量
- 放到系统库的路径下,/lib64
5.2静态库
1.特征:在windows操作系统下,动态库文件后缀为.lib;在linux操作系统下,前缀为lib,后缀为.a;例如libxyz.a。
2.生成静态库:注意点使用目标程序进行编译生成
- 第一段:使用gcc/g++将源代码文件编译成目标文件(.o文件)
- 第二段:使用ar -rc命令编译目标文件生成静态库
六.软硬链接
6.1软连接
1.生成链接文件: ln -s
2.特征:
软链接文件就是源文件快捷方式。用命令ll -i查看文件信息的时候,inode节点号本质上对应的是文件系统当中的概念,在inode当中保存了文件在磁盘当中的存储位置。注意的是,在删除软链接文件对应的源文件的时候,需要将软链接文件也删除掉!原因是他会重新创建原文件,并且inode相同。
6.2硬链接
1.生成: ln
2.特征:源文件与硬链接文件inode相同,说明硬链接文件是源文件的一个拷贝,而且还是浅拷贝,此时存在一个风险,就是用户在使用完硬链接文件后直接将内存释放掉,那么空间直接不存在,那么再次要使用其源文件的时候就会非法,此时我们就使用了一个技术引用计数。当引用计数大于等于2的时候,不会真正free()掉。
以上是关于Linux--IO的主要内容,如果未能解决你的问题,请参考以下文章