Linux C 环境下不带缓冲的I/O操作函数

来源:互联网 发布:淘宝美即面膜牛奶白滑 编辑:程序博客网 时间:2024/06/05 08:27

摘要:本文主要介绍Linux C 环境下不带缓冲的文件I/O操作函数——open(),creat(),read(),write(),lseek(),close()。结合实例,简单地利用单进程和多进程对同一个文件的操作,加深对这些函数的理解。

正文:

一、Open(): 打开或创建一个文件。

1、  简介(摘自《Unix环境高级编程》第3章 文件I/O,以下几个函数的简介都来源于此)

/*头文件*/#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>/*原型*/int open(const char *pathname, int oflag, /*mode_t mode*/);               

返回:若成功为文件描述符,若出错为- 1并置errno的值。

参数介绍:

Const char *pathname:文件的路径(即文件所在目录+文件名);

Int oflag:文件打开标志选项,实际是一个或多个可作位或运算的宏(定义在<fcntl.h>头文件中),下面介绍这些宏:

O_RDONLY 只读打开。

 O_WRONLY 只写打开。

O_RDWR 读、写打开。

    很多实现将 O_RDONLY定义为0,O_WRONLY定义为1,O_RDWR定义为2, 以与早期的系统兼容。 在这三个常数中应当只指定一个。下列常数则是可选择的:O_APPEND 每次写时都加到文件的尾端。 3.11节将详细说明此选择项。

O_CREAT 若此文件不存在则创建它。使用此选择项时,需同时说明第三个参数 mode,用其说明该新文件的存取许可权位。 

O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则出错。这可测试一个文件是否存在,如果不存在则创建此文件成为一个原子操作。 3.11节将较详细地说明原子操作。

O_TRUNC 如果此文件存在,而且为只读或只写成功打开,则将其长度截短为 0。

O_NOCTTY 如果pathname指的是终端设备,则不将此设备分配作为此进程的控制终端。9.6节将说明控制终端。

O_NONBLOCK 如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的 I / O操作设置非阻塞方式。 1 2 . 2节将说明此工作方式。

O_SYNC 使每次write都等到物理I/O操作完成。3.13节将使用此选择项。

由open返回的文件描述符一定是最小的未用描述符数字。

       mode_t mode:文件权限位,仅当打开文件不存在,创建文件时才起作用。

       参数 mode 有下列数种组合
S_ISUID 04000 文件的(set user-id on execution)位
S_ISGID 02000 文件的(set group-id on execution)位
S_ISVTX 01000 文件的sticky位
S_IRUSR(S_IREAD) 00400 文件所有者具可读取权限
S_IWUSR(S_IWRITE)00200 文件所有者具可写入权限
S_IXUSR(S_IEXEC) 00100 文件所有者具可执行权限
S_IRGRP 00040 用户组具可读取权限
S_IWGRP 00020 用户组具可写入权限
S_IXGRP 00010 用户组具可执行权限
S_IROTH 00004 其他用户具可读取权限
S_IWOTH 00002 其他用户具可写入权限
S_IXOTH 00001 其他用户具可执行权限
比如要将文件test的权限设置为644,那么可以采用以下几种方法:
open("./test.txt", O_RDWR | O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
open("./test.txt", O_RDWR | O_CREAT, 0644);  //0644是八进制
open("./test.txt", O_RDWR | O_CREAT, 420);   //420是十进制

        4(二进制100)代表读权限,2(二进制010)代表写权限,1(二进制001)代表执行权限。

        这几种方法,第三个参数在内存中实际存的值都是一样的。

当要打开2G以上的大文件时,要注意几点:(摘自:http://www.lampchina.net/article/htmls/201006/Mjg4ODkw.html)

1、 包含所有头文件以前,先定义这些宏:

#ifndef __USE_FILE_OFFSET64#define __USE_FILE_OFFSET64#endif#ifndef __USE_LARGEFILE64#define __USE_LARGEFILE64#endif#ifndef _LARGEFILE64_SOURCE#define _LARGEFILE64_SOURCE#endif

2、 使用open打开文件的时候,加上O_LARGEFILE标志:
int fd = open("test.dat", O_RDWR|O_APPEND|O_CREAT|O_LARGEFILE, 0666);
read(), write()等与一般的用法一致,无变化。

3、 注意lseek()函数,文件未超过2G的时候,一切工作良好;
文件超过2G后,调用返回-1,errno为EOVERFLOW (errno=75, msg=Value too large for defined data type)
需要使用lseek64()代替lseek()。

4、注意stat()函数,传入的文件如果在2G内,工作良好;
传入的文件如果超过2G,返回-1, errno为EOVERFLOW (errno=75, msg=Value too large for defined data type)
应该这样使用:struct stat64 st; stat64("file", &st);
此外,还可以用fopen64,也要先加上上面3个宏定义。

二、creat():创建一个文件

#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>Int creat(const char *pahtname, mode_t mode);

返回:若成功为只写打开的文件描述符,若出错为-1

此函数等效于 open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);  所以该函数现在很少使用,一般用open()代替了。

三、write():向已打开的文件写数据

#include <unistd.h>ssize_t write(int filedes, const void *buff, size_t nbytes);

返回:若成功为已写入的字节数,若失败为-1。ssize_t,表示一个带符号整型。
参数说明

Int filedes: 文件描述符(可以是文件的,也可以设备的);

Const void *buff: 指向要写入的数据地址,即写缓存指针;

size_t mbytes: 将要写入数据的字节数,即buff的大小,可用strlen(buff)得到。

其返回值通常与参数 nbytes的值不同,否则表示出错。 write出错的一个常见原因是:没有写权限,磁盘已写满,或者超过了对一个给定进程的文件长度限制 。

四、read() :从已打开文件读数据

#include <unistd.h>ssize_t read(int filedes, void *buff, size_t nbytes);

返回:若成功为读到的字节数。如已到达文件的尾端,则返回 0;若失败为-1。

参数说明

Int filedes: 读取文件的文件描述符;

void *buff: 读取数据的存放地址,即读缓存指针;

sizex_t: 预读取的字节数,实际返回值可能小于它;

有多种情况可使实际读到的字节数少于要求读字节数:

读普通文件时,在读到要求字节数之前已到达了文件尾端。例如,若在到达文件尾端之前还有30个字节,而要求读 100个字节,则read返回30,下一次再调用read时,它将返回 0(文件尾端)。

 当从终端设备读时,通常一次最多读一行 (第11章将介绍如何改变这一点 )。

当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数。

 某些面向记录的设备,例如磁带,一次最多返回一个记录。

 读操作从文件的当前位移量处开始,在成功返回之前,该位移量增加实际读得的字节数。

注意,如果buff指向的是数组,且其内容是要作字符串处理,则在buff的最后要手动加上'\0',否则会出现乱码的情况.。

五、lseek(): 设置文件的当前文件位移量

#include <sys/types.h>#include <unistd.h>off_t lseek(int filedes, off_t offset, int wherece);

返回:若成功为新的文件位移,若出错为-1

参数说明

int filedes: 文件描述符

off_t offset:相对偏移量,可正可负

int wherece: 是预先定义好的宏,SEEK_SET, SEEK_CUR, SEEK_END

若whence是SEEK_SET,则将该文件的位移量设置为距文件开始处 offset 个字节。

若whence是SEEK_CUR,则将该文件的位移量设置为其当前值加offset, offset可为正或负。

若whence是SEEK_END,则将该文件的位移量设置为文件长度加offset, offset可为正或负。

这种方法也可用来确定所涉及的文件是否可以设置位移量。如果文件描述符引用的是一个管道或FIFO,则lseek返回-1,并将errno设置为EPIPE。

六、close():关闭文件。

#include <unistd.h>int close(int filedes);

返回:若成功为0,若出错为-1

参数int filedes为文件描述符.

关闭一个文件时也释放该进程加在该文件上的所有记录锁。 当一个进程终止时,它所有的打开文件都由内核自动关闭。很多程序都使用这一功能而不显式地用close关闭打开的文件。但个人还是主张显式地用close(),这样可以提高程序的可读性,也可减轻内核对进程终止后的后续处理负担。

 

在本文件的开头已经提到以上几个系统函数都是非缓冲的,那么什么是带缓冲的,什么又是不带缓冲的呢?!

“缓冲文件系统”的文件操作先将数据 送到内存中的缓冲区, ANSI C函数库中的fopen()/fread()/fwrite()/fflush()/fclose()等函数操作,只有当缓冲区满或是出现冲冼缓冲区标志时才对缓冲区的数据作作处理。像printf()是行缓冲,当出现’\n’时它才作输出。这样可以减少用户空间与内核空间数据交换次数, 从而降低开销。

非缓冲,即没有上面所提的“缓冲区”,直接调用内核对要操作的数据进行处理。像write(),是直接将参数buff指向的数据写入到文件,而且它的写操作是原子性的,会将buff的数据一次性写入到文件。这样比带缓冲的数据处理更及时。

理论都是浮云,下面进入实践阶段。两个简单的实例:

例一: 单进程

#include <sys/stat.h>#include <sys/types.h>#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <error.h>int main(){         int fd = 0;         char buf[50];
         memset(buf, 0, 50);         if ((fd = open("./hello.txt", O_CREAT | O_RDWR | O_APPEND, 0644)) == -1) {                   perror("open hello.txt");                   return 0;         }         if (3 != write(fd, "aaa", 3)) {                   perror("write");                }         if (3 != write(fd, "bbb", 3)) {                   perror("write");                }         lseek(fd, 3, SEEK_SET);         if (3 != write(fd, "ccc", 3)) {                   perror("write");                }         lseek(fd, 0, SEEK_SET);         if (3 != read(fd, buf, 3)) {                   perror("read");         }
         buf[3] = '\0';         if ( 3 != write(fd, buf, 3)) {                   perror("write");         }         close(fd);         return 0;  }

 结果:  aaabbbcccaaa

   结果分析:文件打开方式是O_CREAT | O_RDWR | O_APPEND,先写入aaabbb,因为有O_APPEND,所以lseek(fd, 3, SEEK_SET);对write(fd, "ccc", 3)不起作用,所以ccc还是从文件结束位置开始写。但是lseek()对read()起作用,把位移量设在文件开头处,然后读取三个字符即aaa,再把aaa写到文件结尾。

例二:多进程

#include <sys/stat.h>#include <sys/types.h>#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <error.h> int main(){         int fd = 0;         pid_t pid;         char buf[50];                  memset(buf, 0, 50);         pid = fork();         if (pid < 0) {             perror("fork failed");             exit(1);             }          if (pid == 0) {     /*child process*/                              if ((fd = open("./hello.txt", O_CREAT | O_RDWR | O_APPEND, 0644)) == -1) {                 perror("open hello.txt");                 return 0;             }             write(fd, "aaa", 3);  //为了代码简洁,把这里的perror()语句去掉了。               write(fd, "bbb", 3);             write(fd, "ccc", 3);             close(fd);         }                 else {              /*parent process*/                           sleep(3);             if ((fd = open("./hello.txt", O_CREAT | O_RDWR, 0644)) == -1) {                 perror("open hello.txt");                 return 0;             }             lseek(fd, 3, SEEK_SET);                 write(fd, "ddd", 3);             close(fd);        }        return 0;  }

结果:  aaadddccc

结果分析:这个例子是两个父子进程对同一个文件进行写操作。先让父进程休息3秒,以便子进程先执行完再让父进程执行。子进程写入aaabbbccc,子进程open()的打开模式是O_CREAT | O_RDWR | O_APPEND;然后,父进程执行,其open()的打开模式是O_CREAT | O_RDWR,先让sleek()把文件位移量设置在距离文件开始处3个字节,再写入ddd,从结果来看,ddd把子进程写的bbb覆盖掉了,所以最终结果是aaadddccc。

       多个进程对同一个文件进行操作时,每个进程都拥有自己的文件描述符、文件表,它们最终指向的都是同一个文件的V节点表(文件信息),如下图。

 

                     图 1   两个进程打开同一个文件

      为了节约系统资源,有些系统对它作了改进,采用了写时复制(copy on write)的技术,即父子进程在打开文件时拥有同一个文件表,当某进程要改变文件表中的某一项时,才把这项复制到另一个内存空间,让该进程独享,例如,上面例子的当前文件位移量。

 

小结与拓展:

         本文的主要工作是对linux下不带缓冲的文件操作函数进行了简单的总结,第一次写技术博客,有不足或错误的地方还望各位大虾批评指正,小辈感激不尽!

         还有一些与文件操作相关的函数,这里就没写了,有需要的朋友可以参考查阅:

int dup (int oldfd):用来复制参数oldfd所指的文件描述词,并将它返回。此新的文件描述词和参数oldfd指的是同一个文件,共享所有的锁定、读写位置和各项权限或旗标。例如,当利用lseek()对某个文件描述词作用时,另一个文件描述词的读写位置也会随着改变。不过,文件描述词之间并不共享close-on-exec旗标。返回值  当复制成功时,则返回最小及尚未使用的文件描述词。若有错误则返回-1,errno会存放错误代码。

int dup2(int odlfd,int newfd):dup2()用来复制参数oldfd所指的文件描述词,并将它拷贝至参数newfd后一块返回。若参数newfd为一已打开的文件描述词,则newfd所指的文件会先被关闭。dup2()所复制的文件描述词,与原来的文件描述词共享各种文件状态,详情可参考dup()。返回值  当复制成功时,则返回最小及尚未使用的文件描述词。若有错误则返回-1,

int fcntl(int fd , int cmd);

int fcntl(int fd,int cmd,long arg);

int fcntl(int fd,int cmd,struct flock * lock);

函数说明  fcntl()用来操作文件描述词的一些特性。参数fd代表欲设置的文件描述词,参数cmd代表欲操作的指令。

int   fileno(FILE *stream)返回stream对应的文件描述符。

FILE* fdopen(int filedes,const char* mode)从文件描述符fd 变换到文件流 FILE* ;

int rename(const char *oldpath, const char *newpath);将文件名改名;

int ftruncate(int fd, off_t length); 把描述符fd引用的文件缩短到length指定的长度。成功返回0,失败返回-1并设置errno。

int fstat(int fd, struct stat *buf); 把描述符fd引用的文件的相关信息保存到buf指向的stat结构中。成功返回0,失败返回-1并设置errno。

int fsync(int fd); 把在fd上执行的写入操作同步到真正的磁盘或其它下层设备文件中。成功返回0,失败返回-1并设置errno。

int flock(int fd,int operation); flock()会依参数operation所指定的方式对参数fd所指的文件做各种锁定或解除锁定的动作。此函数只能锁定整个文件,无法锁定文件的某一区域。

 

PS: 本文亦可在下面链接可见

http://dev.jizhiinfo.net/?post=27

 

 

原创粉丝点击