Linux文件I0编程
Linux文件I0概述
POSIX 规范
POSIX(Portable Operating System Interface)表示可移植操作系 统 接 口 规 范 。 该 标 准 最 初 由 IEEE 制 定 , 目 的 是为了提高UNIX 环 境 下 应 用 程
序 的 可 移 植 性 。 POSIX 已 发 展 成 一 个 庞 大 的 标 准 族 , 其 中 的 POSIX.1 提 供 了
源 代 码 级 别 的C语言应 用 程 序 编 程 接 。 通 俗 地 讲 , 为 一 个 POSIX 兼 容 的 ,操 作 系 统 编 写 的 程 序 , 可 以 在 其 他 任 何 POSIX 操 作 系 统 上 编 译 执 行 。 不 仅 仅是 UNIX, 很 多 操 作 系 统 如 Linux 也 支 持 POSIX 标 准 。
虚拟文件系统
Linux 系 统 成 功 的 关 键 因 素 之 一 就 是 具 有 与 其 他 操 作 系 统 和 谐 共 存的.Linux 的 文 件 系 统 由 两 层 结 构 构 建 ; 第 一 层 是 虚 拟 文 件 系 统(VFS), 第二 层 是 各 种 不 同 的 具 体 的 文 件 系 统 。
VFS 就 是 把 各 种 具 体 的 文 件 系 统 的 公 共 部 分 抽 取 出 来 , 形 成 一 个 抽 象 层 , 是 系 统 内 核 的 一 部 分 。 它 位 于 用 户 程 序 和 具 体 的 文 件 系 统 之 间 。 它 对 用 户 程 序 提 供 了 标 准 的 文 件 系 统 调 用 接 口 , 对 具 体 的 文 件 系 统 ( 比 如 : Ext2、| FATS2 等), 它 通 过 一 系 列 的 对 不 同 文 件 系 统 通 用 的 函 数 指 针 来 调 用 对 应 的 ˇ文 件 系 统 函 数 , 完 成 相 应 的 操 作 。 任 何 使 用 文 件 系 统 的 程 序 必 须 通 过 这 层 接 口 来 访 问 。 通 过 这 样 的 方 式 ,VFS 就 对 用 户 屏 敲 了 底 层 文 件 系 统 的 实 现 细节和差异
VFS 不 仅 可 以 对 具 体 文 件 系 统 的 数 据 结 构 进 行 抽 象 , 以 一 种 统 一 的 数 据接 口 进 行 管 理 , 还 可 以 接 受 用 户 层 的 系 统 调 用 , 如 open()、read() 、write()、 stat(),link() 等 。 此 外 , 它 还 支 持 多 种 具 体 文 件 系 统 之 间 的 相 互 访 问 , 接 受内 核 其 他 子 系 统 的 操 作 请 求 , 例 如 , 内 存 管 理 和 进 程 调 度 。VFS 在 Linux 系统 中 的 位 置 如
查看支持文件系统cat /proc/filesystems

文件和文件描述符
Linux 操 作 系 统 是 基 于 文 件 概 念 的 。 文 件 是 以 字 符 序 列 构 成 的 信 息 载 体 。根 据 这 一 点 , 可 以 把 I/0 设 备 当 作 文 件 来 处 理 。 因 此 , 与 磁 盘 上 的 普 通 文 件进 行 交 互 所 用 的 同 一 系 统 调 用 可 以 直 接 用 于 I[/0 设 备 。 这 样 大 大 简 化 了 系 统对 不 同 设 备 的 处 理 , 提 高 了 效 率 。Linux 中 的 文 件 主 要 分 为 6 种 : 兼 通 文 的,目录 文 件 、 符 号 链 接 文 件 、 管 道 文 件 、 套 接 字 文 件 和 设 备 文 件 ,.
那 么 , 内 核 如 何 区 分 和 引 用 特 定 的 文 件 呢 ? 这 里 用 到 了 一 个 重 要 的 概 念文 件 描 述 符 。 对 于 Linux 而 言 , 所 有 对 设 备 和 文 件 的 操 作 都 是 通 过 文 件 描述 符 来 进 行 的 。 文 件 描 述 符 是 一 个 非 负 的 整 数 , 它 是 一 个 索 引 值 , 并 指 向 在 内核 中 每 个 进 程 打 开 文 件 的 记 录 表 。 当 打 开 一 个 现 存 文 件 或 创 建 一 个 新 文 件 时 ,内 核 就 向 进 程 返 回 一 个 文 件 描 述 符 ; 读 写 文 件 时 , 需 要 把 文 件 描 述 符 作 为 参 数 传 递 给 相 应 的 函 数 。
通 常 , 一 个 进 程 启 动 时 , 都 会 打 开 3 个 流 ; 标 准 输 人 、 标 准 输 出 和 标 准错误,这个3个流分别对应文件描述符0,1和2(对应宏STDIN_FILENO,STDOUT_FILENO 和 STDERR _ FILENO)。
基于 文件描述符的 I/O 操作虽然不能直接移植到类 Linux 以 外的系统上去 (如 Windo ws ), 但它往往是实现某些 I/O 操作的唯一途径,如 Linu x 中低层文件操作函数、多路 1/ O 、 TCP/IP 套接字编程接口等。同时,它们也很好地兼容了 POSIX 标准 ,因此, 可以很方便地移植到任何 POSIX 平台上。基于文件描述符的 I/O 操作是 Linux 中最常用的操作之一
文件 I/O 和标准 I/O 的区别
标准 I/O 和文件 1/O 都可以用来访问文件,它们之间有什么区别呢?
文件 I/O 又称为低级磁盘 I/O, 遵循 POSIX 相关标准。 任何兼容 POSIX 标准的操作系统上都支持文件 I/O。标准 I/O 被称为高级磁盘 I/O,遵循 ANSI C 相关标准。 只要开发环境中有标准 C 库,标准 I/O 就可以使用。 ( Linu x 中使用的是 glibc, 它是标准 C 库的超集。 不仅包含 ANSI C 中定义的函数,还包括 POSIX 标准中定义的函数。因此, Linux 下既可 以使用标准 I/O,也可以使用文件 I/O。)
通过文件 I/O 读写文件时,每次操作都会执行相关系统调用。这样处理的好处是直接读写实际文件,坏处是频繁的系统调用会增加系统开销。标准I/O 可以看作在文件 1/O 的基础上封装了缓冲机制。先读写缓冲区,必要时再访问实际文件,从而减少了系统调用的次数。
文件 I/O 中用文件描述符表示一个打开的文件, 可以访问不同类型的文件如普通文件、设备文件和管道文件等。 而标准 I/O 中用 FILE ( 流)表示一个打开的文件,通常只用来访问普通文件。
文件 I/O 操作
本节主要介绍文件 I/ O 相关函数: open ()、 close()、 read()、 w rite() 、 lseek()和 fcntl()。 这些函数的特点是不带缓冲 , 直接对文件 ( 包括设备 )进行读写操作。 这些函数不是 ANSI C 的组成部分, 而是由 POSIX 相关标准来定义。
2.2.1 文件打开和关闭
1 . 函数说明
open()函数用于创建或打开文件 ,在打开或创建文件时可 以指定文件打开方式及文件的访问权限。
close()函数用于关闭一个被打开的文件。当一个进程终止时,所有打开的文件都由内核自动关闭。 很多程序都利用这 一特性而不显式地关闭一个文件。
2 . 函数格式
open(函数的语法要点如表 2. 1 所示 。
表 2 . 1 open () 函 数 语 法 要 点

在 open (函 数中, f la g s 参数可通过 " | " 组合构成, 但前 3 个标志常量 ( O _ RDONLY 、O _ W RONL Y 以及 O _ RD W R )不能相互组合。 per m s 是文件的存取权限 , 既可以用宏定义表示法,也可以用八进制表示法。
close()函数的语法要点如表 2.2 所示。

- 示例代码
c
/ * samplel. c * /
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
int main ()
int fd ;
if ((fd = open(" test.txt " , O_RDWR|O_CREAT|O_T_RUNC,0666))<0){
perror("fail to open");
return -l;
}
close(fd);
return O;
}
文件读写
- 函数说明
read()函数从文件中读取数据存放到缓存区中,并返回实际读取的字节数。若返回 0,则表示没有数据可读,即已达到文件尾。 读操作从文件的当前读写位置开始读取内容,当前读写位置自动往后移动。
write()函数将数据写入文件中 ,并返回实际写入的字节数。写操作从文件的当前读写位置开始写入。对磁盘文件进行写操作时,若磁盘已满, write () 函数返回失败。 - 函数格式
read0函数的语法要点如表 2.3 所示 。

在读普通文件时, 若读到要求的字节数之前已到达文件的尾部,则返回的字节数会小于指定读出的字节数。
write()函数的语法要点如表 2.4 所示 。

- 示例代码
c
/ * sample2.c */
#include<stdio.h>
#include<unistd.h>
#include <sys/stat.h>
#include < fcntl.h >
#define N 64
int main (
int fd, nbyte,sum = O;
char buf[N] ;
if ((fd = open("test.txt", O_RDONLY)) < 0 ){
perror("fail to open")
return -l;
}
while ((nbyte = read(fd,buf,N))>0) // 循环从文件中读取数据,直到文件末尾
{
sum + = nbyte;//累加每次读取的字节数
}
printf("the length of test.txt is %d\n",sum);
close(fd);
return 0;
}
文件定位
-
函数说明
lseek()函数对文件当前读写位置进行定位。 它只能对可定位 ( 可随机访问)文件操作。 管道、套接字和大部分字符设备文件不支持此类操作。
-
函数格式
lseek()函数的语法要点如表 2.5 所示。

-
示例代码
下面示例中的 open () 函数带有 3 个 flags 参数: O _C REAT、 O TRUNC 和 O WRONLY,这样就可以对不同的情况指定相应的处理方法。
实现的功能是从一个文件(源文件)中读取最后 10KB 数据并复制到另一个文件(目标文件)。源文件以只读方式打开,目标文件以只写方式打开,若目标文件不存在,可以创建并设置权限的初始值为 644,即文件所有者可读可写,文件所属组和其他用户只能读。
c
/*sample3.c*/
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#inlucde <stdio.h>
#define BUFFER_SIZE 1024
#define SRC_FILE_NAME "src_file"
#define DEST_FILE_NAME "dest_file"
#define OFFSET 10240
int main(){
int fds,fdd;
unsigned char buf(BUFFER_SIZE);
int read len ;
/*以只读方式打开源文件 *
if ((fds = open(SRC_FILE_NAME, O_RDONLY))<0){
perror("fail to open src _file");
return -1;
}
/*以只写方式打开目标文件:若此文件不存在则创建该文件,访问权限值为644*/
if ((fdd = open ( DEST _ FILE _ NAME, O _ W RONLYIO _ CREATIO_ TRUNC. 0644)) < 0 ){
perror("fail to open dest _ file");
return -1 :
}
/*将源文件的读写指针移到最后10KB的起始位置* /
lseek ( fds , -OFFSET, SEEK _ END );
/* 读取源文件的最后10KB数据井写到目标文件中, 每次读写1KB */
while ((read _ len = read ( fds , buff , sizeof(buff))) > 0 ){
write(fdd , buff , read_len);
}
close ( fds ) :
close ( fdd ) :
return 0;
}
文件锁
- fcntl()函 数说明
前面介绍的 5个基本函数实现了文件的打开、读写等基本操作。这一节将讨论的是在文件已经共享的情况下如何操作,也就是多个程序共同操作一个文件的情况。 Linu x 中通常采用的方法是给文件上锁,来解决对共享的资源的竞争。
文件锁包括建议性锁和强制性锁。 建议性锁要求每个相关程序在访问文件之前检查是否有锁存在,并且尊重已有的锁。 一般情况下,不建议使用建议性锁, 因为无法保证每个程序都自动检查是否有锁。 而强制性锁是由内核执行的锁, 当一个文件被上锁,进行写入操作的时候, 内核将阻止其他任何程序对该文件进行读写操作。采用强制性锁对性能的影响较大,每次读写操作内核都检查是否有锁存在。
在 Linux 中,实现文件上锁的函数有 lockf()和 fcntl(), 其中 lockf()用于对文件施加建议性锁,而 fcntl()不仅可以施加建议性锁,还可以施加强制性锁。同时, fcntl o还能对文件的某一记录上锁,也就是记录锁。
记录锁又可分为读取锁和写入锁,其中读取锁又称为共享锁,多个同时执行的程序允许在文件的同一部分建立读取锁。而写入锁又称为排斥锁,在任何时刻只能有一个程序在文件的某个部分上建立写入锁。 显然,在文件的同一部分不能同时建立读取锁和写人锁。
fcntl()函数具有丰富的功能 , 它可以对已打开的文件进行各种操作。不仅能够管理文件锁, 还可以获取和设置文件相关标志位以及复制文件描述符等。在本节中,主要介绍利用它建立记录锁的方法。有兴趣的读者可以查看 f cntl 手册了解其他用法。 - 函数格式
用于建立记录锁的 fcntl0函数的语法要点如表 2.6 所示。



若要加 锁整个文件, 可 以将 1_start 设置为 O , I_ whence 设置为 SEEK _ SET , 1_len 设置为 0。
- fcnt()使用实例
下面给出了使用 fcntl()函数对文件加记录锁的代码。 首先给 flock 结构体赋予相应的值, 接着调用两次fcntl()函数。 第一次用 F_GETLK 命令判断是否可以执行 flock 结构所描述的锁操作:若成员 1_ty pe 的值为 F_UNLCK ,表示文件当前可以执行相应锁操作;否则成员 1_type 的值表示当前已有的锁类型,并且成员 1_pid 被设置为拥有当前文件锁的进程号。
第二次用 F_SETLK 或 F_SETLKW 命令设置 flock 结构所描述的锁操作,后者是前者的阻塞版本。使用后者时,当不能执行相应上锁/解锁操作时,程序会被阻塞,直到能够操作为止。
文件记录锁的代码具体如下。
c
/ * lock _set.c */
int lock_set( int fd, int type ){
struct flock old _lock , lock ;
lock.l_whence = SEEK _SET ;
lock.l_start = 0;
lock.1_len = 0 ;
lock.l_type = type ;
lock.l_pid = -1 ;
/* 判断文件是否可以上锁 */
fcntl(fd,F_GETLK, &lock);
if(lock.1_type != F_UNLCK ){
/*判断文件不能上锁的原因 */
if(lock.l_type == F_RDLCK){ /*该文件 已有读取锁*/
printf ("Read lock already set by %d \n",lock.l_pid);
}
else if ( lock.l_ t y pe = = F _ WRLCK { /* 该文件已有写 入锁 */
printf ("Write lock already set by %d\n", lock.l_pid ) :
}
}
/* 1_ ty pe 可 能 已 被F _ GE T L K修改过 */
lock.l_type = type;
/*根据不同的 ty pe值进行阻塞式上锁或解锁*/
if ((fcntl(fd , F_SETLKW,&lock )) < 0 ){
printf ( " Lock failed : type = &d \n ". lock.l_ty pe ) ;
return -l ;
}
switch ( lock.l_type ){
case F_RDLCK :
{
printf("Read lock set by %d\n",getpid());
break :
}
case F_WRLCK :
{
printf ("write lock set by %d\n ",getpid());
break;
}
case F_UNLCK :
{
printf ("Release lock by %d\n",getpid());
return l ;
}
break ;
}/* end of switch中 return 0*/;
}
下面的示例是文件写 入锁的测试用例 , 这里首先创建了 一个 hello 文件之后对其上写人锁, 最后释放写入锁。 代码如下。
C
/*write lock.c*/
#include <unistd.h>
#include <sy s/file.h>
#include <sys/stat.h>
#include <st dio.h>
#include <stdlib.hs
int lock_set(int fd , int type);\
int main(void){
int fd :
/*首先打开文件*/
if((fd = open ( "hello" , O _ RD WR)) < 0){
perror("fail to open");
return -1;
}
/*给文件上写入锁*/
lock _set ( fd , F_WRLCK ) ;
getchar(); // 等待用户键盘输人
/*给文件解锁 */
lock _set(fd , F_ UNLCK);
getchar0;
close(fd);
return O :
}
运行如下命令编译程序。
gcc -o write _ lock write _ lock.c lock _ set.c
建议读者开启两个终端,并且在两个终端上同时运行该程序,以达到多个程操作一个文件的目的。
终端一如下。
由此可见,写入锁为互斥锁,同一时刻只能有一个写人锁存在。
接下来的程序是文件读取锁的测试用例,原理和上面的程序一样。
C
/*fcntl_read.c*/
#include <unistd.h>
#include <sys/file.h> #include <sys/types.h> #include <sys/stat.h> #include <st dio. h >
#include <stdlib.h >
#include "lock _ set.c"
int main (void){
int fd;
fd = open("hello", O _ RDwR | O _ CREAT, 0644 ) ;
if ( fd < 0)
{
printf("Open file error \n");
exit (1);
}
/*给文件上读取锁 */
lock _set(fd.F_RDLCK);
getchar();
/*给文件解锁 */
lock _ set(fd,F_UNLCK);
getchar();
close(fd);
exit(0);
}
读者可以将此结果与写人锁的运行结果相比较, 可以看出 , 读取锁为共享锁, 当进程 5009 已设置读取锁后 , 进程 5010 仍然可以设置读取锁。
实验内容一一生产者和消费者
- 实验目的
通过编写文件读写及上锁的程序,进一步熟悉 Linux 中文件 I/O 相关的应用开发,并且熟练掌握 open()、 read()、 write (、 fcntl(等函数的使用方法。 - 实验内容
使用文件来模拟 FIFO (先进先出 )结构以及生产者一消费者运行模型。
实验中需要打开两个虚拟终端, 分别运行生产者程序 ( producer )和消费者程序 ( customer )。 两个程序同时对同 一个文件进行读写操作。 因为这个文件是共享资源,所以使用文件锁机制来保证两个程序对文件的访问都是原子操作。
先启动生产者进程, 它负责创建模拟 FIFO 结构的文件 ( 其实是一个普通文件)并投入生产,即按照给定的时间间隔,向文件写入自动生成的字符(在程序中用宏定义选择使用数字还是使用英文字符)。生产周期以及要生产的资源数通过参数传递给程序。(默认生产周期为 1s,要生产的资源总数为 10个字符,默认生产总时间为 10s。)
后启动的消费者进程按照给定的数目进行消费。首先从文件中读取相应数目的字符并在屏幕上显示,然后从文件中删除刚才消费过的数据。为了模拟 FIFO 结构,此时需要使用两次复制来实现文件内容的前移。 每次消费的资源数通过参数传递给程序,默认值为 10个字符。
(2) 编写代码 。
本实验中的生产者程序的源代码如下, 其中用到的 lock_set()函数可参照第 2.2.4 节。

c
/* producer.c */
2
#include <stdio.h>
#include <unistd .h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include "mylock.h"
/*级冲 区大小最大镇*/
#define MAXLEN 10
#define ALPHABET 1 /*表示使用英文字符 */
#define ALPHABET_START 'a' /*头一个字符, 可以用 A */
#define COUNT_OF_ALPHABET 26 /*字母字符的个数 */
#define DIGIT 2 /*表示使用数字字符*/
#define DIGiT_START '0' /*头一个字符*/
#define COUNT _ OF _ DIGIT 10 /*数字字符的个数*/
#define SIGN_TYPE ALPHABET /*本实例选用英文字符*/
const char * fifo_file = "./ my fifo"; /*仿真 FIFO文件名*/
char buff [MAXLEN]; /*缓冲区*/
/* 功能: 生产一个字符并写入仿真FIFO文件中
int product(void){
int fd;
unsigned int sign_type,sign_start,sign_count,size;
static unsigned int counter = 0;
/*打开仿真FIFO文件*/
if ((fd = open(fifo_file,O_CREATIO_RDWR|O_APPEND, 0644)) < 0 ){
printf("Open fifo file error\n ");
exit(1);
}
sign_type = SIGN _TYPE;
switch(sign_type)
{
case ALPHABET:/*英文字符*/
{
sign_start = ALPHABET_START;
sign_count = COUNT_OF_ALPHABET;
}
break;
case DIGIT :/*数字字符*/
{
sign_start = DIGIT_START;
sign_count = COUNT_OF_DIGIT;
}
break;
default:
{
return -1;
}
}/*end of switch*/
sprintf(buff, "%c",(sign_start + counter))
counter = (counter + 1)%sign_count;
lock_set(fd,F_WRLCK); /*上写人锁*/
if((size = write(fd , buff,strlen(buff))) < 0 )
{
printf("Producer:write error \n ");
return - 1;
}
lock_set(fd.F_UNLCK); /*解锁*/
close(fd);
return 0;
}
int main ( int argc,char *argv [] ){
int time_step = 1 ; /*生产周期*/
int time_life = 10; /*需要生产的资源总数*/
if(argc > 1)
{
/*第一个参数表示生产周期 */
sscanf ( argv[1],"%d",&time.step);
}
if(argc>2)
{
/*第二个参数表示需要生产的资源数*/
sscanf(argv[2],"%d",&time_life);
}
while(time_life--)
{
if(product() < 0)
{
break;
}
sleep(time_step);
}
exit(EXIT_SUCCESS);
}
本实验中的消费者程序的源代码如下。
c
/ * customer.c * /
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <f cntl.h>
#define MA X _ FILE _ SIZE 100 * 1024 * 1024 /*100MB*/
const char *fifo_file = "./ myfifo"; /* 仿真 FIFO文件名 */
const char *tmp_file = "./tmp"; /*临时文件名 /
/*资源消费函数*/
int customing ( const char * my fifo, int need ){
int fd ;
char buff;
int counter = 0 ;
if ( ( fd = open ( myfifo, O_RDONLY)) < 0 )
5
printf ( "Function customing error\n ") ;
return -1;
printf ( "Enjoy;");
Iseek ( fd,SEEK_SET,0);
while ( counter < need ){
while (( read ( fd , &buff . 1 ) = = 1) && ( counter < need)){
fputc (buff,stdout) : /*消费就是在屏幕上简单地显示 * /
counter++;
}
}
fputs( "\n",stdout);
close(fd) ;
return 0;
}
/* 功能 :从 sour_file 文件的offset偏移处开始将 count字节数据复制到 dest_file文件 */
int my filecopy(const char * sour_file,
const char * dest_file, int offset,int count, int copy _mode){
int in_file, out_file;
int counter = 0;
char buff _ unit ;
if ((in_file = open (sour _ file, O_ RDONLY IO _ NONBLOCK) ) < O){
printf(" F unction my filecopy error in source file\n ") ;
return -l ;
}
if ((out _ file = open (dest_ file,
O_CREATIO _ RDWRIO _ TRUNCI0 _ NONBL0CK. 0644)) < 0)
{
printf("Function myfilecopy error in destination file :") ;
return -1 ;
}
Iseek(in_file. off set , SEEK_SET);
while ((read (in_file. &buff_unit . 1) = = 1 ) && (counter < count) )
{
write (out_file . &buff_unir . 1);
counter++:
}
close(in_file);
close(out_file);
return 0:
}
/*功能: 实现FIFO消费者*/
int custom (int need)
{
int fd;
/*对资源进行消费: need 表示该消费的资源数目*/
customing (fifo_file.need);
if(( fd = open( fifo _ file, O _ RD W R)) < 0){
printf( " Function my filecopy error in source_file: ") ;
return -l:
}
/*为了 模拟 FIF O结构, 对整个文件内容进行平行移动 */
lock_set(fd,F_WRLCK);
myfilecopy(fifo_file, tmp_file, need , MAX_FILE_SIZE , 0);
my filecopy( tmp_ file, fifo _file, 0, MAX _ FILE _ SIZE , 0);
lock _set( fd , F_UNLCK);
unlink(tmp_file):
close(fd);
return 0;
}
int main(int argc,char * arg v [])
{
int customer_capacity = 10;
if (argc > 1) /*第一 个参数指定需要消费的资源数目, 默认值为10*/
{
sscanf (argv[1]."%d", &customer_capacity);.
}
if(customer _capacity > 0)
{
custom(customer_capacity);
}
exit(EXIT_SUCCESS);
}