中嵌之文件I/O

本文介绍了Linux系统下的文件I/O操作,包括open、read、write等基本文件操作函数,以及lseek、close等高级文件操作。此外还讨论了文件描述符的概念、fcntl和ioctl的用途,最后讲解了select函数在I/O多路转接中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ll?

linux的系统调用API主要是通过C库(libc)实现的。

uc-linux的系统调用API主要是通过C库(uc-libc)实现的

进入内核的两种方式:1、通过shell,与内核交互;系统调用。

1、Linux系统调用与文件I/O
1.1、 Linux系统调用
     所谓系统调用是指操作系统提供给用户程序的一组“特殊”接口,用户程序可以通过这组“特殊”接口来获得操作系统内核提供的特殊服务。
    在linux中用户程序不能直接访问内核提供的服务。为了更好的保护内核空间,将程序的运行空间分为内核空间和用户空间,他们运行在不同的级别上,在逻辑上是相互隔离的。
2.1、用户程序接口(API)
       在linux中用户编程接口(API)遵循了在UNIX中最流行的应用编程界面标准—posix标准。这些系统调用编程接口主要通过C库(libc)实现的。
2.1文件I/O介绍
可用的文件I / O函数——打开文件、读文件、写文件等等。大多数linux文件I / O只需用到5个函数:open、read、write、lseek 以及close。
不带缓存指的是每个read和write都调用内核中的一个系统调用。这些不带缓存的I / O函数不是ANSI c(标准C)
组成部分,但是posix组成部分。

2.2 文件描述符
对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,用open或creat返回的文件描述符标识该文件,将其作为参数传送给read或write。
在posix.1应用程序中,整数0、1、2应被代换成符号常数 STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO。O。这些常数都定义在头文件 <unistd.h>中。
      文件描述符的范围是0 ~ OPEN_MAX 。早期的UNIX版本采用的上限值是1 9 (允许每个进程打开2 0个文件),现在很多系统则将其增加至6 3。
2.3 open函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int oflag, …/*, mode_t mode * / ) ;
返回:若成功为文件描述符,若出错为- 1

  1. pathname参数:pathname是要打开或创建的文件的名字。
  2. oflag参数:可用来说明此函数的多个选择项。
  3. open参数:对于open函数而言,仅当创建新文件时才使用第三个参数。
     用下列一个或多个常数进行或运算构成oflag参数(这些常数定义在<fcntl.h>头文件中):
  O_RDONLY 只读打开。
  O_WRONLY 只写打开。
  O_RDWR 读、写打开。
 O_APPEND 每次写时都加到文件的尾端。
 O_CREAT 若此文件不存在则创建它。使用此选择项时,需同时说明第三个参数mode,用其说明该新文件的存取许可权位。
  O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则出错。这可测试一个文件是否存在,如果不存在则创建此文件成为一个原子操作。
 O_TRUNC 如果此文件存在,而且为只读或只写成功打开,则将其长度截短为0。
 O_NOCTTY 如果p a t h n a m e指的是终端设备,则不将此设备分配作为此进程的控制终端。
 O_NONBLOCK 如果p a t h n a m e指的是一个F I F O、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I / O操作设置非阻塞方式。
 O_SYNC 使每次w r i t e都等到物理I / O操作完成。
2.4 creat函数
可用creat函数创建一个新文件。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat(const char * pathname, mode_t m o d e) ;
返回:若成功为只写打开的文件描述符,若出错为- 1。
注意,此函数等效于:
open (pathname, O_WRONLY | O_CREAT | O_TRUNC, mode) ;
     c r e a t的一个不足之处是它以只写方式打开所创建的文件。
2.5 close函数
可用close函数关闭一个打开文件:
#include <unistd.h>
int close (int filedes);
返回:若成功为0,若出错为- 1
当一个进程终止时,它所有的打开文件都由内核自动关闭。很多程序都使用这一功能而不显式地用c l o s e关闭打开的文件。
如:例open.c
2.6 lseek函数
每个打开文件都有一个与其相关联的“当前文件偏移量”。它是一个非负整数,用以度量从文件开始处计算的字节数。通常,读、写操作都从当前文件偏移量处开始,并使偏移量增加所读或写的字节数。按系统默认,当打开一个文件时,除非指定O_APPEND选择项,否则该位移量被设置为0。
可以调用l s e e k显式地定位一个打开文件。

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int filesdes, off_t offset, int whence) ;
返回:若成功为新的文件位移,若出错为- 1。
对参数offset 的解释与参数w h e n c e的值有关。
若whence是SEEK_SET,则将该文件的位移量设置为距文件开始处offset 个字节。
若whence是SEEK_CUR ,则将该文件的位移量设置为其当前值加offset,offset可为正或负。
若whence是SEEK_END ,则将该文件的位移量设置为文件长度加offset,offset可为正或负。

若l s e e k成功执行,则返回新的文件位移量,为此可以用下列方式确定一个打开文件的当前位移量:
off_t curr_pos;
Curr_pos = lseek(fd, 0, SEEK_CUR);


2.7  read函数
用r e a d函数从打开文件中读数据
#include <unistd.h>
ssize_t read(int feledes, void *buff, size_t nbytes) ;
返回:读到的字节数,若已到文件尾为0,若出错为- 1。
     如r e a d成功,则返回读到的字节数。如已到达文件的尾端,则返回0。

     有多种情况可使实际读到的字节数少于要求读字节数:
 读普通文件时,在读到要求字节数之前已到达了文件尾端。例如,若在到达文件尾端之前还有3 0个字节,而要求读1 0 0个字节,则r e a d返回3 0,下一次再调用r e a d时,它将返回0 (文件尾端)。
 当从终端设备读时,通常一次最多读一行(第11章将介绍如何改变这一点)。
 当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数。
 某些面向记录的设备,例如磁带,一次最多返回一个记录。
     读操作从文件的当前位移量处开始,在成功返回之前,该位移量增加实际读得的字节数。
2.8  write函数
    用w r i t e函数向打开文件写数据。
#include <unistd.h>
ssize_t write(int filedes, const void * buff, 
size_t nbytes) ;
返回:若成功为已写的字节数,若出错为- 1。
     其返回值通常与参数nbytes的值不同,否则表示出错。w r i t e出错的一个常见原因是:磁盘已写满,或者超过了对一个给定进程的文件长度限制。


对于普通文件,写操作从文件的当前位移量处开始。如果在打开该文件时,指定了O _ A P P E N D选择项,则在每次写操作之前,将文件位移量设置在文件的当前结尾处。在一次成功写之后,该文件位移量增加实际写的字节数。
见例:write.c
2.9 fcntl函数
     fcntl函数可以改变已经打开文件的性质。
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fcntl(int filedes, int cmd, ... ) ;
返回:若成功则依赖于cmd(见下),若出错为- 1。




      f c n t l函数有五种功能:
 复制一个现存的描述符, 新文件描述符作为函数值返(c m d=F_DUPFD)。
 获得/设置文件描述符标记,对应于filedes 的文件描述符标志作为函数值返回.(c m d = F_GETFD或F_SETFD)。
 获得/设置文件状态标志,对应于filedes 的文件状态标志作为函数值返回。(c m d = F_GETFL或F_SETFL)。
 获得/设置异步I / O有权(c m d = F_GETOWN或F_SETOWN)。
 获得/设置记录锁(c m d = F_SETLK , F_SETLKW)。




 F_SETFL 将文件状态标志设置为第三个参数的值(取为整型值)。可以更改的几个标志是:O _ A P P E N D,O _ N O N B L O C K,O _ S Y N C和O _ A S Y N C。
 F_GETOWN 取当前接收S I G I O和S I G U R G信号的进程I D或进程组I D。
 F_SETOWN 设置接收S I G I O和S I G U R G信号的进程I D或进程组I D。正的a rg指定一个进 程I D,负的a rg表示等于a rg绝对值的一个进程组I D。
2.9.2 用fcntl给文件加锁
当多个用户共同使用、操作一个文件的时候,linux通常采用的方法是给文件上锁,来避免共享资源产生竞争的状态。
文件锁包括建议锁和强制性锁。建议性锁要求上锁文件的进程都要检测是否有锁存在,并尊重已有的锁。强制性锁由内核和系统执行的锁。
Fcntl不仅可以实施建议性锁而且可以实施强制性锁。
2.9.3 fcntl函数格式
#include <sys/types.h>
#include <unistd.h>
#include <fcnt1.h>
int fcnt1(int filedes, int cmd,... struct flock flockptr ) ;
struct flock 结构
2.9.4 f l o c k结构说明:
 所希望的锁类型:F_RDLCK(共享读锁)、F_WRLCK(独占性写锁)或F_UNLCK(解锁一个区域)
 要加锁或解锁的区域的起始地址,由l_start和l_whence两者决定。l_stat是相对位移量(字节),l_whence则决定了相对位移量的起点。
 区域的长度,由l_len表示。   


关于加锁和解锁区域的说明还要注意下列各点:
 该区域可以在当前文件尾端处开始或越过其尾端处开始,但是不能在文件起始位置之前开始或越过该起始位置。
 如若l_len为0,则表示锁的区域从其起点(由l_start和l_whence决定)开始直至最大可能位置为止。也就是不管添写到该文件中多少数据,它都处于锁的范围。
 为了锁整个文件,通常的方法是将l_start说明为0,l_whence说明为SEEK_SET,l_len说明为0。
2.10 ioctl函数
      ioctl 函数是I / O操作的杂物箱。不能用本章中其他函数表示的I / O操作通常都能用i o c t l表示。终端I / O是ioctl 的最大使用方面,主要用于设备的I / O控制。
#include <unistd.h> /* SVR4 */
#include <sys/ioctl.h> /* 4.3+BSD * /
int ioctl(int filedes, int request, . . . ) ;
返回:若出错则为- 1,若成功则为其他值。
3、select 实现I/O复用
3.1 I/O处理的五种模型
阻塞I/O模型:若所调用的I/O函数没有完成相关的功能就会使进程挂起,直到相关数据到达才会返回。如:终端、网络设备的访问。
非阻塞模型:当请求的I/O操作不能完成时,则不让进程休眠,而且返回一个错误。如:open、read、write访问。
I/O多路转接模型:如果请求的I/O 操作阻塞,且他不是真正阻塞I/O,而且让其中的一个函数等待,在这期间, I/O还能进行其他操作。如:select函数。


信号驱动I/O模型:在这种模型下,通过安装一个信号处理程序,系统可以自动捕获特定信号的到来,从而启动I/O。
异步I/O模型:在这种模型下,当一个描述符已准备好,可以启动I/O时,进程会通知内核。由内核进行后续处理,这种用法现在较少。
3.2 select函数
      传向select的参数告诉内核:
(1) 我们所关心的描述符。
(2) 对于每个描述符我们所关心的条件(是否读一个给定的描述符?是否想写一个给定的描述符?是否关心一个描述符的异常条件?)。
(3) 希望等待多长时间(可以永远等待,等待一个固定量时间,或完全不等待)。
      从s e l e c t返回时,内核告诉我们:
(1) 已准备好的描述符的数量。
(2) 哪一个描述符已准备好读、写或异常条件。


#include <sys/types.h>/* fd_set data type */
#include <sys/time.h> /* struct timeval */
#include <unistd.h> /* function prototype might be here */
int select (int numfds, fd_set *readfds,
fd_set *writefds, fd_set *exceptfds, struct timeval * timeout) ;
返回:准备就绪的描述符数,若超时则为0,若出错则为- 1。


timeout值:
 NULL:永远等待,直到捕捉到信号或文件描述符已准备好为止;
 具体值: struct timeval 类型的指针,若等待为timeout时间还没有文件描述符准备好,就立即返回;
 0:从不等待,测试所有指定 的描述符并立即返回;


     先说明最后一个参数,它指定愿意等待的时间。
struct timeval
{
    long tv_sec; /* seconds */
    long tv_usec; /* and microseconds */
};




      select函数根据希望进行的文件操作对文件描述符进行分类处理,这里,对文件描述符的处理主要设计4个宏函数:
FD_ZERO(fd_set *set) 清除一个文件描述符集;
FD_SET(int fd, fd_set *set) 将一个文件描述符加入文件描述符集中;
FD_CLR(int fd, fd_set *set) 将一个文件描述符从文件描述符集中清除;
FD_ISSET(int fd, fd_set *set)  测试该集中的一个给定位是否有变化;


    在使用select函数之前,首先使用FD_ZERO和FD_SET来初始化文件描述符集,并使用select函数时,可循环使用FD_ISSET测试描述符集, 在执行完成对相关的文件描述符后, 使用FD_CLR来清除描述符集。
例,见:select.c

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值