【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更新文件中任一部分的数据?请写一段程序以验证之。

相关文章
|
19天前
|
存储 缓存 Linux
Linux IO的奥秘:深入探索数据流动的魔法
Linux I/O(输入/输出)系统是其核心功能之一,负责处理数据在系统内部及与外界之间的流动。为了优化这一流程,Linux进行了一系列努力和抽象化,以提高效率、灵活性和易用性。🚀
Linux IO的奥秘:深入探索数据流动的魔法
|
20天前
|
存储 安全 数据管理
探索Linux的挂载操作🌈
在Linux这个强大的操作系统中,挂载操作是一个基本而重要的概念。它涉及到文件系统、设备和数据访问,对于理解Linux的工作方式至关重要。那么,挂载操作究竟是什么,为什么我们需要它,如果没有它,我们将面临什么问题呢?让我们一起深入探讨。
探索Linux的挂载操作🌈
|
29天前
|
Linux Windows
Linux之基本指令操作
Linux之基本指令操作
|
5天前
|
机器学习/深度学习 缓存 监控
linux查看CPU、内存、网络、磁盘IO命令
`Linux`系统中,使用`top`命令查看CPU状态,要查看CPU详细信息,可利用`cat /proc/cpuinfo`相关命令。`free`命令用于查看内存使用情况。网络相关命令包括`ifconfig`(查看网卡状态)、`ifdown/ifup`(禁用/启用网卡)、`netstat`(列出网络连接,如`-tuln`组合)以及`nslookup`、`ping`、`telnet`、`traceroute`等。磁盘IO方面,`iostat`(如`-k -p ALL`)显示磁盘IO统计,`iotop`(如`-o -d 1`)则用于查看磁盘IO瓶颈。
|
17天前
|
Linux
Linux操作系统调优相关工具(三)查看IO运行状态相关工具 查看哪个磁盘或分区最繁忙?
Linux操作系统调优相关工具(三)查看IO运行状态相关工具 查看哪个磁盘或分区最繁忙?
21 0
|
1月前
|
存储 算法 Linux
【Linux系统编程】Linux 文件系统探究:深入理解 struct dirent、DIR 和 struct stat结构
【Linux系统编程】Linux 文件系统探究:深入理解 struct dirent、DIR 和 struct stat结构
45 0
|
1天前
|
消息中间件 关系型数据库 MySQL
Linux:开源之魅与编程之道
Linux:开源之魅与编程之道
9 1
|
2天前
|
Ubuntu Linux
Linux(Ubuntu)系统临时IP以及静态IP配置(关闭、启动网卡等操作)
请注意,以上步骤是在临时基础上进行配置的。如果要永久保存静态IP地址,通常还需要修改 `/etc/network/interfaces`文件,以便在系统重启后保持配置。同时,确保备份相关配置文件以防止出现问题。
13 1
|
3天前
|
Ubuntu Linux 数据安全/隐私保护
Linux(24) 如何在Ubuntu中操作rootfs.img文件
Linux(24) 如何在Ubuntu中操作rootfs.img文件
9 0
|
14天前
|
存储 固态存储 Java
文件IO讲解
文件IO讲解
31 0