Linux下的C编程实战之文件系统编程

 在Linux平台下对文件编程可以使用两类函数:(1Linux操作系统文件API;(2C语言I/O库函数。前者依赖于Linux系统调用,后者实际上与操作系统是独立的,因为在任何操作系统下,使用C语言I/O库函数操作文件的方法都是相同的。本章将对这两种方法进行实例讲解。


1. 文件IO操作


S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP


  fd=open("/dev/globalvar", O_RDWR, S_IRUSR|S_IWUSR);     //可读写方式打开设备文件

S_IRUSR

Permits the file's owner to read it.
S_IWUSR
Permits the file's owner to write to it.
S_IRGRP
Permits the file's group to read it.
S_IWGRP
Permits the file's group to write to it.
S_ISDIR ( ) 目录文件
S_ISCHR ( ) 字符特殊文件
S_ISBLK ( ) 块特殊文件
S_ISFIFO ( ) 管道或F I F O
S_ISLNK ( ) 符号连接( P O S I X . 1S V R 4无此类型)
S_ISSOC K ( ) 套接字(P O S I X . 1S V R 4无此类型)

S_ISREG ( ) 普通文件



   2.Linux 文件 API

   Linux 的文件操作 API 涉及到创建、打开、读写和关闭文件。

  创建

int creat(const char *filename, mode_t mode);


  参数mode指定新建文件的存取权限,它同umask一起决定文件的最终权限(mode&umask),其中umask代表了文件在创建时需要去掉的一些存取权限。umask可通过系统调用umask()来改变:

int umask(int newmask);


  该调用将umask设置为newmask,然后返回旧的umask,它只影响读、写和执行权限。

  打开

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

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


  open函数有两个形式,其中pathname是我们要打开的文件名(包含路径名称,缺省是认为在当前路径下面)flags可以去下面的一个值或者是几个值的组合:

标志含义

O_RDONLY以只读的方式打开文件

O_WRONLY以只写的方式打开文件

O_RDWR以读写的方式打开文件

O_APPEND以追加的方式打开文件

O_CREAT 创建一个文件

O_EXEC如果使用了O_CREAT而且文件已经存在,就会发生一个错误

O_NOBLOCK以非阻塞的方式打开一个文件

O_TRUNC如果文件已经存在,则删除文件的内容

   


  O_RDONLYO_WRONLYO_RDWR三个标志只能使用任意的一个。

  如果使用了O_CREATE标志,则使用的函数是int open(const char *pathname,int flags,mode_t mode);这个时候我们还要指定mode标志,用来表示文件的访问权限。mode可以是以下情况的组合:

标志含义

S_IRUSR 用户可以读

S_IWUSR 用户可以写

S_IXUSR 用户可以执行

S_IRWXU用户可以读、写、执行

S_IRGRP 组可以读

S_IWGRP 组可以写

S_IXGRP 组可以执行

S_IRWXG 组可以读写执行

S_IROTH 其他人可以读

S_IWOTH 其他人可以写

S_IXOTH 其他人可以执行

S_IRWXO其他人可以读、写、执行

S_ISUID 设置用户执行ID

S_ISGID 设置组的执行ID


  除了可以通过上述宏进行逻辑产生标志以外,我们也可以自己用数字来表示,Linux总共用5个数字来表示文件的各种权限:第一位表示设置用户ID;第二位表示设置组ID;第三位表示用户自己的权限位;第四位表示组的权限;最后一位表示其他人的权限。每个数字可以取1(执行权限)2(写权限)4(读权限)0()或者是这些值的和。例如,要创建一个用户可读、可写、可执行,但是组没有权限,其他人可以读、可以执行的文件,并设置用户ID位。那么,我们应该使用的模式是1(设置用户ID)0(不设置组ID)7(1+2+4,读、写、执行)0(没有权限)5(1+4,读、执行)10705

open("test", O_CREAT, 10705);


  上述语句等价于:

open("test", O_CREAT, S_IRWXU | S_IROTH | S_IXOTH | S_ISUID );


  如果文件打开成功,open函数会返回一个文件描述符,以后对该文件的所有操作就可以通过对这个文件描述符进行操作来实现。

  读写

  在文件打开以后,我们才可对文件进行读写了,Linux中提供文件读写的系统调用是readwrite函数:

int read(int fd, const void *buf, size_t length);

int write(int fd, const void *buf, size_t length);


  其中参数buf为指向缓冲区的指针,length为缓冲区的大小(以字节为单位)。函数read()实现从文件描述符fd所指定的文件中读取length个字节到buf所指向的缓冲区中,返回值为实际读取的字节数。函数write实现将把length个字节从buf指向的缓冲区中写到文件描述符fd所指向的文件中,返回值为实际写入的字节数。

  以O_CREAT为标志的open实际上实现了文件创建的功能,因此,下面的函数等同creat()函数:

int open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);


  定位

  对于随机文件,我们可以随机的指定位置读写,使用如下函数进行定位:

int lseek(int fd, offset_t offset, int whence);


  lseek()将文件读写指针相对whence移动offset个字节。操作成功时,返回文件指针相对于文件头的位置。参数whence可使用下述值:

  SEEK_SET:相对文件开头
  SEEK_CUR:相对文件读写指针的当前位置
  SEEK_END:相对文件末尾

  offset可取负值,例如下述调用可将文件指针相对当前位置向前移动5个字节:

lseek(fd, -5, SEEK_CUR);


  由于lseek函数的返回值为文件指针相对于文件头的位置,因此下列调用的返回值就是文件的长度:

lseek(fd, 0, SEEK_END);


  关闭

  当我们操作完成以后,我们要关闭文件了,只要调用close就可以了,其中fd是我们要关闭的文件描述符:

int close(int fd);


  例程:编写一个程序,在当前目录下创建用户可读写文件“hello.txt”,在其中写入“Hello, software weekly”,关闭该文件。再次打开该文件,读取其中的内容并输出在屏幕上。

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdio.h>

#define LENGTH 100

main()

{

 int fd, len;

 char str[LENGTH]; 

 fd = open("hello.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); /*创建并打开文件 */

 if (fd) 

 {

  write(fd, "Hello, Software Weekly", strlen("Hello, software weekly")); /*写入 Hello, software weekly字符串 */

  close(fd);

 }


 fd = open("hello.txt", O_RDWR);

 len = read(fd, str, LENGTH); /*读取文件内容 */

 str[len] = '\0';

 printf("%s\n", str);

 close(fd);

}


 3.C语言库函数

  C库函数的文件操作实际上是独立于具体的操作系统平台的,不管是在DOSWindowsLinux还是在VxWorks中都是这些函数:

  创建和打开

FILE *fopen(const char *path, const char *mode);


  fopen()实现打开指定文件filename,其中的mode为打开模式,C语言中支持的打开模式如下表:

标志含义

r, rb以只读方式打开

w, wb以只写方式打开。如果文件不存在,则创建该文件,否则文件被截断

a, ab以追加方式打开。如果文件不存在,则创建该文件

r+, r+b, rb+ 以读写方式打开

w+, w+b, wh+以读写方式打开。如果文件不存在时,创建新文件,否则文件被截断

a+, a+b, ab+以读和追加方式打开。如果文件不存在,创建新文件


  其中b用于区分二进制文件和文本文件,这一点在DOSWindows系统中是有区分的,但Linux不区分二进制文件和文本文件。

  读写

  C库函数支持以字符、字符串等为单位,支持按照某中格式进行文件的读写,这一组函数为:

int fgetc(FILE *stream);

int fputc(int c, FILE *stream);

char *fgets(char *s, int n, FILE *stream);

int fputs(const char *s, FILE *stream);

int fprintf(FILE *stream, const char *format, ...);

int fscanf (FILE *stream, const char *format, ...);

size_t fread(void *ptr, size_t size, size_t n, FILE *stream);

size_t fwrite (const void *ptr, size_t size, size_t n, FILE *stream);


  fread()实现从流stream中读取加n个字段,每个字段为size字节,并将读取的字段放入ptr所指的字符数组中,返回实际已读取的字段数。在读取的字段数小于num时,可能是在函数调用时出现错误,也可能是读到文件的结尾。所以要通过调用feof()ferror()来判断。

  write()实现从缓冲区ptr所指的数组中把n个字段写到流stream中,每个字段长为size个字节,返回实际写入的字段数。

  另外,C库函数还提供了读写过程中的定位能力,这些函数包括

int fgetpos(FILE *stream, fpos_t *pos);

int fsetpos(FILE *stream, const fpos_t *pos);

int fseek(FILE *stream, long offset, int whence); 

等。


  关闭

  利用C库函数关闭文件依然是很简单的操作:

int fclose (FILE *stream);


  例程:将第2节中的例程用C库函数来实现。

#include <stdio.h>

#define LENGTH 100

main()

{

 FILE *fd;

 char str[LENGTH];


 fd = fopen("hello.txt", "w+"); /*创建并打开文件 */

 if (fd)

 {

  fputs("Hello, Software Weekly", fd); /*写入Hello, software weekly字符串 */

  fclose(fd);

 }


 fd = fopen("hello.txt", "r");

 fgets(str, LENGTH, fd); /*读取文件内容 */

 printf("%s\n", str);

 fclose(fd);

}


  4.小结

  Linux提供的虚拟文件系统为多种文件系统提供了统一的接口,Linux的文件编程有两种途径:基于Linux系统调用;基于C库函数。这两种编程所涉及到文件操作有新建、打开、读写和关闭,对随机文件还可以定位。本章对这两种编程方法都给出了具体的实例。



带缓冲I/O 不带缓冲I/O详解


 以下是我对这两者的理解:

首先要明白不带缓冲的概念:所谓不带缓冲,并不是指内核不提供缓冲,而是只单纯的系统调用,不是函数库的调用。系统内核对磁盘的读写都会提供一个块缓冲,当用write函数对其写数据时,直接调用系统调用,将数据写入到块缓冲进行排队,当块缓冲达到一定的量时,才会把数据写入磁盘。因此所谓的不带缓冲的I/O是指进程不提供缓冲功能。每调用一次writeread函数,直接系统调用。

而带缓冲的I/O是指进程对输入输出流进行了改进,提供了一个流缓冲,当用fwrite函数网磁盘写数据时,先把数据写入流缓冲区中,当达到一定条件,比如流缓冲区满了,或刷新流缓冲,这时候才会把数据一次送往内核提供的块缓冲,再经块缓冲写入磁盘。

因此,带缓冲的I/O在往磁盘写入相同的数据量时,会比不带缓冲的I/O调用系统调用的次数要少。


下面的东西是我从网上查到的对这两者的理解,我觉得还是很到位的:

以下主要讨论关于open,write等基本系统IO的带缓冲与不带缓冲的差别


      带缓存的文件操作是标准C 库的实现,第一次调用带缓存的文件操作函数时标准库会自动分配内存并且读出一段固定大小的内容存储在缓存中。所以以后每次的读写操作并不是针对硬盘上的文 件直接进行的,而是针对内存中的缓存的。何时从硬盘中读取文件或者向硬盘中写入文件有标准库的机制控制。不带缓存的文件操作通常都是系统提供的系统调用, 更加低级,直接从硬盘中读取和写入文件,由于IO瓶颈的原因,速度并不如意,而且原子操作需要程序员自己保证,但使用得当的话效率并不差。另外标准库中的 带缓存文件IO 是调用系统提供的不带缓存IO实现的。


术语不带缓冲指的是每个readwrite都调用嗯内核中的一个系统调用。所有的磁盘I/O都要经过内核的块缓冲(也称内核的缓冲区高速缓 存),唯一例外的是对原始磁盘设备的I/O。既然readwrite的数据都要被内核缓冲,那么术语不带缓冲的I/O“指的是在用户的进程中对这两个 函数不会自动缓冲,每次readwrite就要进行一次系统调用。“--------摘自<unix环境编程>


程序中用openwrite打开创建并把“hello world“写入文件test.txt,相应用fopenfwrite操作文件test2.txt。程序执行到openfopen之后,sleep 15秒,这时用ls查看生成了文件没,这时用open打开的test.txt出现了,但是fopentest2.txt没有;当程序执行完write fwrite之后,fopentest2.txt仍然没有出现(还是用ls查看),再用cattest.txt,可以看到 “helloworld”;最后再关闭test.txttest2.txt,这时test2.txt出现了,并且其内容也是“hello world“

   该例子证明了openwrite是不带缓冲的,即程序一执行其io操作也立即执行,不会停留在系统提供的缓冲里,不需等到close操作完才执行。与之相比的fopenfwrite则是带缓冲的,(一般)要等到fclose操作完后才会执行。

  

  相关的源码示例如下:

 i nclude <unistd.h>

i nclude <iostream>

i nclude <fcntl.h>

i nclude <string>

i nclude <sys/types.h>

i nclude <sys/stat.h>

using namespace std;


int main(){

 int fd;

 FILE *file;

 char *s="hello,world\n";

 if((fd=open("test.txt",O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1){

  cout<<"Error open file"<<endl;

  return -1;

 }

 if((file=fopen("test2.txt","w"))==NULL){

  cout<<"Error Open File."<<endl;

  return -1;

 }

 cout<<"File has been Opened."<<endl;

 sleep(15);

 if(write(fd,s,strlen(s))<strlen(s)){

  cout<<"Write Error"<<endl;

  return -1;

 }

 if(fwrite(s,sizeof(char),strlen(s),file)<strlen(s)){

  cout<<"Write Error in 2."<<endl;

  return -1;

 }

 cout<<"After write"<<endl;

 sleep(15);

 cout<<"After sleep."<<endl;

 close(fd);

 return 0;

}

详情请见:http://blog.csai.cn/user1/27828/archives/2007/14285.html


ssize_t write(int filedes, const void *buff, size_t nbytes)size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *fp)来讲讲自己对unix系统下带缓存的I/O和不带缓存的I/O的区别。


 

    首先要清楚一个概念,所谓的代缓存并不是指上面两个函数的buff参数,而是指unix系统在内核中所设的缓冲存储器。


    当将数据写到文件上时,内核先将该数据写到缓存,如果该缓存未满,则并不将其排入输出队列,直到缓存写满或者内核再次需要重新使用此缓存时才将其排入输入队列,待其到达对首,在进行实际的I/O操作,也就是此时才把数据真正写到磁盘,这种技术叫延迟写。


    现在假设内核所设的缓存是100个字节,如果你使用write,且buffsize10,当你要把9个同样的buff写到文件时,你需要调用9write,也就是9次系统调用,此时也并没有写到硬盘,如果想立即写到硬盘,调用fsync,可以进行实际的I/O操作。


    标准I/O,也就是带缓存的I/O采用FILE*FILE实际上包含了为管理流所需要的所有信息:实际I/O的文件描述符,指向流缓存的指针(标准I /O缓存,由malloc分配,又称为用户态进程空间的缓存,区别于内核所设的缓存),缓存长度,当前在缓存中的字节数,出错标志等,假设流缓存的长度为 50字节,把以上的数据写到文件,则只需要2次系统调用(fwrite调用write系统调用),因为先把数据写到流缓存,当其满以后或者调用 fflush时才填入内核缓存,所以进行了2次的系统调用write


    fflush将流所有未写的数据送入(刷新)到内核(内核缓冲区),fsync将所有内核缓冲区的数据写到文件(磁盘)。

 

    不带缓存的readwrite是相对于fread/fwrite等流函数来说明的,因为freadfwrite是用户函数(3),所以他们会在用户层 进行一次数据的缓存,而read/write是系统调用(2)所以他们在用户层是没有缓存的,所以称readwrite是无缓存的IO,其实对于内核来 说还是进行了缓存,不过用户层看不到罢了。




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值