【Linux系统编程】文件IO操作

简介:

文件描述符

在 Linux 的世界里,一切设备皆文件。我们可以系统调用中 I/O 的函数(I:input,输入;O:output,输出),对文件进行相应的操作( open()、close()、write() 、read() 等)。


打开现存文件或新建文件时,系统(内核)会返回一个文件描述符,文件描述符用来指定已打开的文件。这个文件描述符相当于这个已打开文件的标号,文件描述符是非负整数,是文件的标识,操作这个文件描述符相当于操作这个描述符所指定的文件


程序运行起来后(每个进程)都有一张文件描述符的表,标准输入、标准输出、标准错误输出设备文件被打开,对应的文件描述符 0、1、2 记录在表中。程序运行起来后这三个文件描述符是默认打开的

#define STDIN_FILENO  0 //标准输入的文件描述符

#define STDOUT_FILENO 1 //标准输出的文件描述符

#define STDERR_FILENO 2 //标准错误的文件描述符


在程序运行起来后打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件描述符记录在表中。Linux 中一个进程最多只能打开 NR_OPEN_DEFAULT (即1024)个文件,故当文件不再使用时应及时调用 close() 函数关闭文件。


常用 I/0 函数

需要的头文件:

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>


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

功能:

打开文件,如果文件不存在则创建。

参数:

pathname: 文件的路径及文件名。

flags: 打开文件的行为标志,如,以只读方式(O_RDONLY,第一个为字母不是数据)打开,以读写或新建新文件的方式(O_RDWR|O_CREAT)打开。


mode: 这个参数,只有在文件不存在时有效,指新建文件时指定文件的权限(文件权限详情,请点此链接)。


返回值:

成功:成功返回打开的文件描述符

失败:-1


int close(int fd);

功能:

关闭已打开的文件

参数:

fd: 文件描述符,open()的返回值

返回值:

成功:0

失败:-1


ssize_t write(int fd, const void *addr, size_t count);

功能:

把指定数目的数据写到文件(fd)

参数:

fd: 文件描述符

addr: 数据首地址

count: 写入数据的长度(字节),一般情况下,数据有多少,就往文件里写多少,不能多也不能少

返回值:

成功:实际写入数据的字节个数

失败:-1


ssize_t read(int fd, void *addr, size_t count);

功能:

把指定数目的数据读到内存(缓冲区)

参数:

fd: 文件描述符

addr: 内存首地址

count: 读取的字节个数

返回值:

成功:实际读取到的字节个数

失败:-1


实战示例

接下来,我们使用以上 4 个系统调用 I/O 函数写一个程序,能实现像系统命令 cp 的功能:



使用 open() 打开源文件,使用 read() 从文件读数据,使用 write() 向目的文件写数据,示例代码如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. #include <sys/types.h>  
  3. #include <sys/stat.h>  
  4. #include <fcntl.h>  
  5.   
  6. int main(int argc, char *argv[])  
  7. {  
  8.     if((argc == 3) && (strcmp(argv[1], argv[2]) != 0))  
  9.     {// 保证有 3 个参数,而且源文件和目的文件名字不能一样  
  10.       
  11.         int fd_src, fd_dest, ret;  
  12.           
  13.         //只读方式打开源文件  
  14.         fd_src = open(argv[1], O_RDONLY);   
  15.         if(fd_src < 0)  
  16.         {  
  17.             perror("open argv[1]");  
  18.             return -1;  
  19.         }  
  20.           
  21.         // 新建目的文件  
  22.         fd_dest = open(argv[2], O_WRONLY|O_CREAT, 0755);  
  23.         if(fd_dest < 0)  
  24.         {  
  25.             close(fd_src);  
  26.             perror("open argv[2]");  
  27.             return -1;  
  28.         }  
  29.           
  30.         do  
  31.         {  
  32.             char buf[1024] = {0};  
  33.             // 从源文件读取数据  
  34.             ret = read(fd_src, buf, sizeof(buf));  
  35.               
  36.             // 把数据写到目的文件,注意最后一个参数,有多少写多少  
  37.             write(fd_dest, buf, ret);  
  38.         }while(ret > 0);  
  39.           
  40.         // 关闭已打开的文件  
  41.         close(fd_src);  
  42.         close(fd_dest);  
  43.     }  
  44.       
  45.     return 0;  
  46. }  

运行结果如下:



文件IO


1. 引言
大多数LInux文件IO只需用到5个函数:open read write lseek close.


2. 文件描述符
对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,
用open或create返回的文件描述符标识该文件,将其作为参数传送给read或write。
在POSIX.1应用程序中,幻数0、1、2应被代换成符号常数STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO。这些常数都定义在头文件<unistd.h>中。
每个进程运行时,系统已分配3个文件描述符(0,1,2),分别对应标准输入, 标准输出, 标准错误输出




3. 文件IO中常用的函数


3.1 open: 
打开一个文件或者设备文件, 返回一个文件描述符。当操作此文件描述符时,就是操作相应的文件或设备
int open(char *pathname,int falgs,.../*mode*/)
flags 必须指其中O_RDONLY  O_WRONLY  O_RDWR唯一项
可选项:
O_APPEND   每次写操写都将文件指针定位文件尾处
O_CREAT    如果文件不存在创建新文件
O_EXCL     如指定O_CREAT时文件存在 出错返回
O_TRUNC    必须以O_WRONL或O_RDWR进行操作,把文件清空

O_NONBLOCK 以非阻塞的方式打开 
O_NOCTTY   如果打开文件为终端设备,不将该设备分配为此进程的控制终端
O_SYNC     每次write都等到I/O操作完成,并等文件的属性更新
O_RSYNC    作为read操作等侍,直到任何对同文件部分未决的写入完成
O_DSYNC    每次write都等到I/O操作完成,不等文件的属性更
mode指定创建文件的权限
//创建一个文件,假设这个文件存在时要清空
open("文件", O_RDWR|O_CREAT|O_TRUNC, 0777);
fd = open("txt", O_RDONLY | O_CREAT | O_EXCL, 0644);


3.2 creat
   int creat(char *pathname,mode_t mode)
   等价于:open(char *,O_WRONLY | O_CREAT | O_TRUNC, mode)


3.3 read
读取已经打开的文件中的数据。读了文件以后,文件描述符对应文件的offset会自动偏移。
ssize_t read(int fd, void *buf, size_t count);
从文件描述符fd指向文件里读取最大为count字节的数据放到buf指定的地址上去.
返回值: 为实际上读取的数值, 为0时读到文件尾, 为-1时错误


3.4 write
向指定的文件中写数据。成功写以后, 文件描述符的offset自动偏移
size_t  write(int fd, const void *buf, size_t count);
把在buf地址指定的数据写到fd指向的文件里,最大写count字节.
成功返回写了多少字节, -1失败。write出错的一个常见原因是:磁盘已写满,或者超过了对一个给定进程的文件长度限制。

3.5 lseek
每个打开文件都有一个与其相关联的“当前文件位移量”。它是一个非负整数,用以度量
从文件开始处计算的字节数。就是改变文件描述符的偏移位置。
off_t lseek(int fildes, off_t offset, int whence);
whence:
SEEK_SET : 把偏移量设为offset, 从文件头开始.
SEEK_CUR : 把当前偏移量加上offset的值
SEEK_END : 先从文件尾开始偏移offset的值

返回值:成功返回定位之后的文件指针偏移 失败返回 -1
返回当前文件的偏移量
off_t currpos = lseek(fd, 0, SEEK_CUR);
3.7 close
把没用的文件描述符关掉,把此文件描述符重新分配.
int close(int fd);

3.8 dup
可用来复制一个现存的文件描述符.
int dup(int oldfd);
int dup2(int oldfd, int newfd);

dup2(oldfd, newfd) //让newfd成为oldfd的一个副本
dup2(fd, 1); //让fd替代标准输出

int ret=dup2(old,new)
如果new 打开的,则关闭new 返回新的文件描述符 失败返回-1. du2是一个原子操作。
dup2可以用newfd参数指定新描述符的数值。如果newfd当前已经打开,则先将其关闭再做dup2操作,如果oldfd等于newfd,则dup2直接返回newfd而不用先关闭newfd再复制。

3.9 fcntl 
用于改变已打开的文件的性质 只能改变O_APPEND, O_NONBLOCK,O_ASYNC,  O_DIRECT
int fcntl(int fileds,int cmd,.../*int arg */)
第三个参数除了cmd用于记录锁时为一个结构指针之外,其余均为整数

fcntl五种功能如下:
.复制一个现有的描述符 cmd=F_DUPFD
复制的新文件描述符清掉文件描述符标志 并且共享同一个文件表项

.获得/设置文件描述符标记 cmd=F_GETFD/F_SETFD
flags = fcntl(fd, F_GETFL); //获取
fcntl(fd, F_SETFL, flags);  //设置

.获得/设置文件状态标志 cmd=F_GETFL/F_SETFL
 忽略 O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC标志
 由于历史原因O_RDONLY O_WRONLY O_RDWR并不各占一位,
 它们之间互斥。因此首先必须用屏蔽字O_ACCMODE 
 取得访问模式然后与这三种flags比较
 value=fcntl(fd,F_GETFL,0);
 switch(value & O_ACCMODE){
case O_RDONLY :
 }

.获得/设置导步I/O所有权cmd=F_GETOWN/F_SETOWN
.获得/设置记录锁 cmd=F_GETLK F_SETLK/F_SETLKW
   
3.10 pread  pwrite  
定位文件进行读写 不影响文件指针 偏移和读写操作为原子操作

3.11 sync
   void sync(void)     
    :函数只是将所有修改过的块缓冲区排入写队列,然后就返回,并不等实际写磁盘完成
   int fsync(int fd) 
    :等待实际磁盘写操作完成,并且同步更新文件的属性,可用于数据库类型的应用程序
   int fdatasync(int fd)
    :类似于fsync,但只影响文件的数据部分不影响文件的属性
   
3.12 ioctl 
int ioctl(int fd, int request, ...);
    称之为I/O操作的垃圾箱 只要其字操函数不能或难于实现在 它都可能很容易做到
==========================================================
4. 某些系统下提供名为/dev/fd/N 等文件。打开文件/dev/fd/N 等效于复制N文件描述符(假定N描述符是打开的)
与其N共享文件表项
也有某些系统为/dev/fd/stdin   /dev/fd/stdout 等,均为同等操作

homework:
1.  实现mycopy拷贝一个文件到另外一个文件(功能相当于 cp a.txt b.txt)
2.  实现mytouch 创建一个文件(功能相当于touch a.txt)。
3.  编写一个同dup2功能相同的函数,要求不调用fcntl函数并且要有正确的出错处理。
4.  在如启用添加标志打开一文件以便读、写,能否用lseek在任一位置开始读?能否用lseek更新文件中任一部分的数据?请写一段程序以验证之。

相关文章
|
5天前
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
53 34
|
2月前
|
Linux API C语言
Linux基础IO
Linux基础IO操作是系统管理和开发的基本技能。通过掌握文件描述符、重定向与管道、性能分析工具、文件系统操作以及网络IO命令等内容,可以更高效地进行系统操作和脚本编写。希望本文提供的知识和示例能帮助读者更深入地理解和运用Linux IO操作。
69 14
|
2月前
|
存储 Java API
【JavaEE】——文件IO(万字长文)
文件路径,文本文件,二进制文件,File类,文件流,字节流(InputStream,OutputStream)字符流(Reader,Writer)
|
2月前
|
Ubuntu Linux Shell
(已解决)Linux环境—bash: wget: command not found; Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
(已成功解决)Linux环境报错—bash: wget: command not found;常见Linux发行版本,Linux中yum、rpm、apt-get、wget的区别;Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
486 68
(已解决)Linux环境—bash: wget: command not found; Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
|
2月前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
121 13
|
5月前
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
321 12
|
6月前
|
Java 数据处理
Java IO 接口(Input)究竟隐藏着怎样的神秘用法?快来一探究竟,解锁高效编程新境界!
【8月更文挑战第22天】Java的输入输出(IO)操作至关重要,它支持从多种来源读取数据,如文件、网络等。常用输入流包括`FileInputStream`,适用于按字节读取文件;结合`BufferedInputStream`可提升读取效率。此外,通过`Socket`和相关输入流,还能实现网络数据读取。合理选用这些流能有效支持程序的数据处理需求。
76 2
|
6月前
|
XML 存储 JSON
【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。
|
6月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
6月前
|
缓存 Java
【IO面试题 一】、介绍一下Java中的IO流
Java中的IO流是对数据输入输出操作的抽象,分为输入流和输出流,字节流和字符流,节点流和处理流,提供了多种类支持不同数据源和操作,如文件流、数组流、管道流、字符串流、缓冲流、转换流、对象流、打印流、推回输入流和数据流等。
【IO面试题 一】、介绍一下Java中的IO流