http://blog.csdn.net/linuxmichael/article/details/5156790#

 

Linux系统可以看成是一个由文件组成的系统,在linux系统中,基本上所有的设备,硬件,资源都被看成一个文件,比如,在/dev下面我们可以看到我们的硬盘sda1(我的机器是安装的SCIS硬盘的虚拟机),终端设备ttyn,在/proc目录下面我们可以看到当前的内存信息,cat/proc/meminfo,可以查看CPU的信息: cat /proc/cpuinfo, 这些文件都是被linux系统抽象成了一个个的文件,通过对文件的操作来,对具体的硬件操作。

       对一个文件的操作可以分为,打开, 读取,写入,关闭等,因此我们对硬件设备文件的操作也抽象成了,对其文件的读取,写入,比如:我们要打印一个字符串到屏幕上,在Linux里面屏幕这个硬件被抽象成了一个文件,我们可以通过往那个文件里面写入字符串,来实现打印。再比如现在有一个串口,他要和其它的一个PC通过串口通信,这个串口在系统里也被抽象成了一个文件,我们可以通过从文件读来取出串口上发过来的数据,通过写入,来向另外一个PC发送数据。看下面的程序:

        #include <stdio.h>

  #include<unistd.h>

  int main(void)

  {

          if(write(STDOUT_FILENO,"hello world", 11) < 0)

                   perror("write");

          return0;

  }

上一节已经讲过这个例子,它就是向STDOUT_FILENO这个文件里面写入helloworld 这11个字符,这个STDOUT_FILENO是什么东西呢?我们先找到它的定义再说,

grep –R ’STDOU_FILENO’ /usr/include/

上面的命令就是在/usr/include/这个目录及其子目录里的文件里查找含有STDOUT_FILENO这个字符串的行,找到就打印出来,我们来看结果

只有在条,通过后面的注释可以看出,这个宏定义表示的是标准输出,也就是屏幕,那它为什么是1呢。我们引出了下面的术语,文件描述符(Filedescriotor)

对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,用open或creat返回的文件描述符标识该文件,将其作为参数传送给read或write。

有三个特殊的文件描述符,每个进程在创建时,都默认打开三个文件描述符。

standard input(0), standard output(1), standarderror(2)

上面的STDOUT_FILENO就是标准输出文件描述符,它和我们标准C库里的文件指针很相似,只是相似,完全不一样(后面我们有例子来分析两个的不相同之处)。

       其实如果你分析了内核的源码后会发现,文件描述符,其实是当前程序打开文件结构数组的下标,当一个程序启动时,系统默认的让他打开了三个文件,就是标准输入,标准输出,标准错误输出,它们的在打开文件结构数据的下标就是0,1,2,因此,如果我的程序再打开一个新的文件,那么应该这个下标(文件描述符)就应该是3,下面我们证明下。

#include <stdio.h>

#include <fcntl.h>

int main(void)

{

        intfd;

        fd= open("/tmp/tmp.txt", O_RDONLY);

        printf("fd:%d/n",fd);

        return0;

}

下面是执行的结果:

 

因此我们的猜测是正确的。看下图左边的用户文件描述符表,其实就是当前程序打开的文件的一个数组,其文件描述符就是其下标。

 

注意下标准输入,标准输出,标准错误输出这三个描述符的定义在<unistd.h>

头文件中,我们可以通过grep来找出来,具体用法见前面。

文件描述符的范围是0 ~ OPEN_MAX 。早期的UNIX版本采用的上限值是1 9(允许每个进程打开20个文件),现在很多系统则将其增加至256。这个好理解,一个程序不可能无休止的打开文件,有个限制,这个限制每个系统可能都不太一样,要是想查看你自己系统的最大打开文件数,也就是文件描述符范围,也用grep去找一个OPEN_MAX这个字符串。

open函数

前面一直有提到这个函数,看其字面意思就是打开一文件的意思。下面看它的具体用法。

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

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

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

功能:打开文件

返回值:若成功为文件描述符,若出错为- 1 (与fopen区别开NULL,因为他返回的是FILE指针)

pathname是要打开或创建的文件的名字。

oflag参数可用来说明此函数的多个选择项。

对于open函数而言,仅当创建新文件时才使用第三个参数。

用下列一个或多个常数进行或运算构成oflag参数(这些常数定义在<fcntl.h>头文件中):

O_RDONLY 只读打开。  (互斥)

             O_WRONLY 只写打开。 (互斥)

O_RDWR 读、写打开。   (互斥)

O_APPEND 每次写时都加到文件的尾端。

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

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

O_TRUNC 如果此文件存在,则将其长度截短为0。

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

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

O_SYNC 使每次write都等到物理I / O操作完成。

 

上面三个头文件是用open这个系统调用时用到的从这儿也能区分出标准C和系统调用的区别,头文件不一样。

       mode对应的文件权限宏:

S _ IS U ID          执行时设置-用户-I D

S _ IS G ID          执行时设置-组-I D

S _ IS V TX         保存正文

S _IRWXU           用户(所有者)读、写和执行

S _ IRSR              用户(所有者)读

S _IWUSR            用户(所有者)写

S _IXUSR            用户(所有者)执行

S _IRWXG           组读、写和执行

S _IRGRP             组读

S _IWGRP            组写

S _IXGRP            组执行

S _IRWXO           其他读、写和执行

S _IROTH            其他读

S _ IWOTH          其他写

S _ IXOTH           其他执行

Open不仅可以打开一个文件,也可以去创建一个文件,这一点要注意下,其实在系统调用函数里面,还有一个真正的创建文件调用函数,creat函数。

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

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

功能:创建一个新的文件。

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

注意,此函数等效于:

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

creat的一个不足之处是它以只写方式打开所创建的文件不能读

read函数

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

功能: 从打开文件中读数据

返回:读到的字节数,读不到字节返回0,若count 为0返回0,若出错为- 1。

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

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

—        当从终端设备读时,通常以行为单位,读到换行符就返回。

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

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

wirte函数

#include <unistd.h>

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

功能;向打开文件写数据。

返回:若成功为已写的字节数,若出错为- 1。

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

 

对于普通文件,写操作从文件的当前位移量处开始。如果在打开该文件时,指定了O_APPEND选择项,则在每次写操作之前,将文件位移量设置在文件的当前结尾处。在一次成功写之后,该文件位移量增加实际写的字节数。

lseek函数

 

每个打开文件都有一个与其相关联的“当前文件偏移量”。它是一个非负整数,用以度量从文件开始处计算的字节数。通常,读、写操作都从当前文件偏移量处开始,并使偏移量增加所读或写的字节数。按系统默认,当打开一个文件时,除非指定O_APPEND选择项,否则该位移量被设置为0。

#include <sys/types.h>

#include <unistd.h>

off_t lseek(int filesdes, off_t offset, int whence) ;

功能:设置文件内容读写位置

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

对参数offset 的解释与参数whence的值有关。

—        若whence是SEEK_SET,则将该文件的文件位移指针设置为距文件开始处offset 个字节。如果offset是0,就是回到文件首,返回值是0

—        若whence是SEEK_CUR ,则将该文件的位移量设置为其当前值加offset,offset可为正或负。如果offset是0,就是返回当前文件位移指针位置。

—        若whence是SEEK_END ,则将该文件的文件位移指针设置为文件长度加offset,offset可为正或负。如果offset是0,就是返回当前文件位移尾指针位置。

注意:

可以调用lseek显式地定位一个打开文件。

lseek仅记录当前文件的偏移量,而不会对文件进行I/O操作。

lseek的偏移量可以超过当前文件的字节总数,从而在文件中产生一个hole

看下面的例子:

      #include <sys/types.h>

       #include<unistd.h>

       #include<stdlib.h>

       #include<sys/stat.h>

       #include<fcntl.h>

       charbuf1[] = "0123456789";

       charbuf2[] = "ABCDEFGHIJ";

       intmain(void)

       {

               intfd;

               if((fd= creat("file.hole", O_RDWR | O_APPEND)) < 0)

                       perror("Createrror");

               if(write(fd,buf1, 10) != 10)

                       perror("buf1write error");

               if(lseek(fd,30, SEEK_CUR) == -1)

                       perror("lseekerror");

               if(write(fd,buf2, 10) != 10)

                       perror("buf2write error");

               exit(0);

       }

   先创建一个新文件file.hole,然后写入10个字节,再用lseek向后移动文件位置指针30个字节,这样,中间的这30个字节就是没有任何数据的,然后在当前文件位置指针处写入10个字节。这样就产生了一个文件空洞。我们来看下它占有多少磁盘空间。

 

我们通过ls–hl 命令可以看到它占有50个字节的磁盘空间,通过od命令(以8进制形式查看文件内容)看出,中间30个字节是0,而我们用cat命令来查看,中间的30个字节没有显示出来。这说明,空洞文件是产生了,中间的30个字节是不可见字符,没有被显示出来,而其在硬盘里面却真正的占用了30个字节。

 

close函数

#include <unistd.h>

int close (int filedes);

功能:关闭一个打开文件

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

当一个进程终止时,它所有的打开文件都由内核自动关闭。很多程序都使用这一功能而不显式地用close关闭打开的文件。

上面几个系统调用的函数在我们的标准C里基本上都有对应的函数实现,比如fope对应open, fclose对应close,fread对应read, fwrite对应wirte。

       下面再看本节最后一个例子,标准C,与系统调用的区别:

#include <stdio.h>

#include <fcntl.h>

#include <errno.h>

int main(void)

{

        

        intfd;

        fd= open("noexit.txt", O_RDONLY);

        perror("open");

        printf("fd:%d/n",fd);

        printf("errno:%d/n",errno);

 

        

        FILE* fp;

        fp= fopen("noexit.txt", "r");

        perror("fopen");

        printf("fp:%d/n",fp);

        printf("errno:%d/n",errno);

        return0;

}

分别用标准C的库fopen和系统调用的open打开两个相同的文件,这个文件在我这儿是不存在的,分别打印出它们的出错信息和出错号,和它们的文件描述符和文件指针。下面是结果;

 

 

系统调用open打开出错返回-1,而fopen返回类型是指针,因此返回是NULL,其类型是(void*)0,也就是0, 注意它们的出错信息,和错误号,都是一样的,因为标准C的函数最终也是调用的系统调用实现其功能的。


Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐