一、文件描述符

对于Linux而言,所有对设备或文件的操作都是通过文件描述符进行的。当打开或者创建一个文件的时候,内核向进程返回一个文件描述符(非负整数)。后续对文件的操作只需通过该文件描述符,内核记录有关这个打开文件的信息(file结构体)。
一个进程启动时,默认打开了3个文件,标准输入、标准输出、标准错误,对应文件描述符是0(STDIN_FILENO)、1(STDOUT_FILENO)、2(STDERR_FILENO),这些常量定义在unistd.h头文件中。

二、open系统调用

(1)函数原型        int open(const char *path, int flags);

参数
       path :文件的名称,可以包含(绝对和相对)路径
       flags:文件打开模式
返回值:
       打开成功,返回文件描述符;打开失败,返回-1

(2)函数原型     int open(const char *path, int flags,mode_t mode);

参数
      path :文件的名称,可以包含(绝对和相对)路径
      flags:文件打开模式
      mode:  用来规定对该文件的所有者,文件的用户组及系 统中其他用户的访问权限
返回值:
       打开成功,返回文件描述符;打开失败,返回-1

打开文件的方式:

O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读写打开

O_APPEND 写入的所有数据将被追加到文件的末尾
O_CREAT 打开文件,如果文件不存在则建立文件。需要第三个参数mode,用来指定该文件的访问权限。
O_EXCL 如果已经置O_CREAT且文件存在,则会出错。用此用于检测文件是否存在,如果不存在创建此文件。
O_TRUNC 如果此文件存在,而且为只写或读写成功打开,则将其长度截短为0。

三、creat系统调用

创建一个新文件

函数原型 int creat(const char *pathname, mode_t mode);

注意,此函数等效于: open(pathname,O_WRONLY | O_CREAT | O_TRUNC,mode);

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

四、close()系统调用

函数原型: int close(int fd);
函数参数:
fd :要关闭的文件的文件描述符
返回值:
如果出现错误,返回-1;调用成功返回0

五、read系统调用

一旦有了与一个打开文件描述相关连的文件描述符,只要该文件是用O_RDONLY或O_RDWR标志打开的,就可以用read()系统调用从该文件中读取字节 。

读操作从文件的当前偏移量处开始,在成功返回之前,该偏移量将增加实际读到的字节数。
函数原型: ssize_t read(int fd, void *buf, size_t count);
参数:
fd :想要读的文件的文件描述符
buf : 指向内存块的指针,从文件中读取来的字节放到这个内存块中
count : 从该文件复制到buf中的字节个数
返回值:
如果出现错误,返回-1;读文件结束,返回0;否则返回从该文件复制到规定的缓冲区中的字节数。

六、write系统调用

用write()系统调用将数据写到一个文件中 
    其返回值通常与参数count的值相同,否则出错。write出错的一个常见原因是:磁盘已写满,或者超过了一个给定进程的文件长度限制。
    对于普通文件,写操作从文件的当前偏移量处开始。在一次成功写之后,该文件偏移量增加实际写的字节数。

函数原型: ssize_t write(int fd, const void *buf, size_t count);
函数参数:
fd:要写入的文件的文件描述符
buf: 指向内存块的指针,从这个内存块中读取数据写入 到文件中
count: 要写入文件的字节个数
返回值: 如果出现错误,返回-1;如果写入成功,则返回写入到文件中的字节个数

七、文件的随机读写

到目前为止的所有文件访问都是顺序访问。这是因为所有的读和写都从当前文件的偏移位置开始,然后文件偏移值自动地增加到刚好超出读或写结束时的位置,使它为下一次访问作好准备。
有个文件偏移这样的机制,在Linux系统中,随机访问就变得很简单,你所需做的只是将当前文件移值改变到有关的位置,它将迫使一次read()或write()发生在这一位置。(除非文件被O_APPEND打开,在这种情况下,任何write调用仍将发生在文件结束处)

lseek系统调用:

功能说明:通过指定相对于开始位置、当前位置或末尾位置的字节数来重定位,这取决于 lseek() 函数中指定的位置
函数原型:off_t lseek (int  fd,    off_t offset,   int base);

函数参数:

fd:需要设置的文件描述符

offset:偏移量

base:偏移基位置

返回值:返回新的文件偏移值。文件的当前位置是允许为负数的,所以判断是否成功,不要测试是否小于0,而要测试她是否等于-1.

base 表示搜索的起始位置,有以下几个值:(这些值定义在<unistd.h>)

SEEK_SET  从文件开始处计算偏移
SEEK_CUR  从当前文件的偏移值计算偏移
SEEK_END  从文件的结束处计算偏移

注意:如果文件描述符引用的是一个管道、FIFO或网络套接字,则lseek返回-1,并将errno设置为ESPIPE。lseek只对常规文件有效。

    文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞,这一点是允许的。位于文件中但没有写过的字节都被读为0.文件中的空洞并不要求磁盘上占用存储区。

#include "apue.h"
#include <fcntl.h> char buf1[] = "abcdefghij";
char buf2[] = "ABCDEFGHIJ"; int
main(void)
{
int fd; if ((fd = creat("file.hole", FILE_MODE)) < 0)
err_sys("creat error"); if (write(fd, buf1, 10) != 10)
err_sys("buf1 write error");
/* offset now = 10 */ if (lseek(fd, 16384, SEEK_SET) == -1)
err_sys("lseek error");
/* offset now = 16384 */ if (write(fd, buf2, 10) != 10)
err_sys("buf2 write error");
/* offset now = 16394 */ exit(0);
}

运行该程序得到:

huangcheng@ubuntu:~$ ./a.out
huangcheng@ubuntu:~$ ll file.hole 检查其大小
-rwxr-xr-x 1 huangcheng huangcheng 16394 2013-07-04 14:33 file.hole*
huangcheng@ubuntu:~$ od -c file.hole 观察其内容
0000000 a b c d e f g h i j \0 \0 \0 \0 \0 \0
0000020 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0040000 A B C D E F G H I J
0040012

八、sync、fsync和fdatasync函数
      传统的unix实现在内核中设有缓冲区高速缓存或页面高速缓存,大多数磁盘I/O都通过缓冲进行。当将数据写入文件时,内核通常先将该数据复制到其中一个缓冲区中,如果该缓冲区尚未写满,则并不将其排入输出队列,而是等待其写满或者当内核需要重用该缓冲区以便存放其他磁盘块数据时,再将该缓冲排入输出队列,然后待其到达队首时,才进行实际的I/O操作。这种输出方式被称为延迟写。
      为了保证磁盘上实际文件系统与缓冲区高速缓存中内容的一致性,UNIX系统提供了sync、fsync和fdatasync三个函数。

int fsync(int fd);
int fdatasync(int fd);
void sync(void);

sync函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等于实际写磁盘操作结束
 fsync函数只对由文件描述符fd指定的单一文件起作用,并且等待写磁盘操作结束,然后返回
 fdatasync函数类似于fsync,但它只影响文件的数据部分。而除数据外,fsync还会同步更新文件的属性。

十、打开文件内核数据结构

1、一个进程打开两个文件

文件状态标志:读、写、追加、同步、非阻塞等

2、一个进程两次打开同一文件

3、两个进程打开同一文件

示例程序:

#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h> #define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0) int main(int argc, char *argv[])
{
int fd1, fd2;
char buf1[1024] = {0};
char buf2[1024] = {0};
/* 进程控制块PCB
* struct task {
* ...
* struct files_struct *files;
* }
* 同一个进程两次打开同一个文件,一个进程拥有的一个文件描述符表其中一个fd索引对应的指针指向一个
* 文件表(包括文件状态(读写追加同步非阻塞等),当前文件偏移量,
* 文件引用次数(当有两个fd指向同个文件表时引用计数为2,见dup,也可用于重定向),
* 文件操作指针, V节点指针等)不共享,
* V节点表(包括V节点信息(struct stat), i节点信息等)共享
*/
/* 两个进程打开同一个文件的情况与上类同*/
fd1 = open("test.txt", O_RDONLY);
if (fd1 == -1)
ERR_EXIT("open error");
read(fd1, buf1, 5);
printf("buf1=%s\n", buf1); fd2 = open("test.txt", O_RDWR);
if (fd2 == -1)
ERR_EXIT("open error");
read(fd2, buf2, 5);
printf("buf2=%s\n", buf2);
write(fd2, "AAAAA", 5); memset(buf1, 0, sizeof(buf1));
read(fd1, buf1, 5);
printf("buf1=%s\n", buf1);
close(fd1);
close(fd2); return 0;
}

假设test.txt文件的内容是 ABCDEhello

测试如下:

huangcheng@ubuntu:~$ ./a.out
buf1=ABCDE
buf2=ABCDE
buf1=AAAAA
huangcheng@ubuntu:~$ cat test.txt
ABCDEAAAAA

分析:由上图分析可知,一个进程两次打开同一文件,文件表是不共享的,即各有自己的文件偏移量和打开文件标志,所以两次read不同的fd都是从头开始读取,但V节点表是共享的,在fd2写入(同个文件表的read和write是共享偏移的)更改了inode指向的硬盘数据块,再次read fd1得到的也是更改后的值。

十一、I/O重定向

当我们执行了dup(3)之后,系统选择一个空闲的文件描述符即4,这样就有两个文件描述符指向同个文件表,所以引用计数为2。利用dup等函数可以进行重定向的步骤是先close输入输出文件描述符,然后执行dup(fd), 这样输入输出文件描述符也指向fd指向的文件,这样就实现了重定向。此外dup2, fcntl 函数也可以实现,其实不使用这些函数,而直接close(0/1/2)完再open也可以实现。如下使用cat命令实现复制文件的功能:

#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0) int main(int argc, char *argv[])
{
close(0);
open("Makefile", O_RDONLY);
close(1);
open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664); execlp("cat", "cat", NULL); return 0;
}

现在标准输入是文件Makefile,标准输出是文件Makefile2,将当前进程替换成cat,则cat会从标准输入读而后输出到标准输出,即完成了copy的功能。

dup/fcntl 函数示例程序如下:

#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h> #define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0) /* dup dup2 fcntl */
int main(int argc, char *argv[])
{
int fd;
fd = open("test2.txt", O_WRONLY);
if (fd == -1)
ERR_EXIT("open error");
/*
close(1);
dup(fd);
*/
// dup2(fd, 1); close(1);
if (fcntl(fd, F_DUPFD, 0) < 0) //从0开始搜索可用的fd
ERR_EXIT("fcntl error");
printf("hello\n"); // 输出重定向到test2.txt
return 0;
}

UNIX环境高级编程——文件I/O的更多相关文章

  1. UNIX 环境高级编程 文件和目录

    函数stat , fstat , fstatat , lstat stat函数返回与此文件有关的信息结构. fstat函数使用已打开的文件描述符(而stat则使用文件名) fstatat函数 为一个相 ...

  2. UNIX环境高级编程——文件和目录

    一.获取文件/目录的属性信息 int stat(const char *path, struct stat *buf); int fstat(int fd, struct stat *buf); in ...

  3. UNIX环境高级编程 文件I/O

    大多数文件I/O 只需要用到 5个函数 :    open , read , write , lseek , close 本章描述的都是不带缓冲的I/O(read write 都调用内核中的一个系统调 ...

  4. UNIX环境高级编程---标准I/O库

    前言:我想大家学习C语言接触过的第一个函数应该是printf,但是我们真正理解它了吗?最近看Linux以及网络编程这块,我觉得I/O这块很难理解.以前从来没认识到Unix I/O和C标准库I/O函数压 ...

  5. (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  6. (三) 一起学 Unix 环境高级编程 (APUE) 之 文件和目录

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  7. (四) 一起学 Unix 环境高级编程(APUE) 之 系统数据文件和信息

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  8. UNIX环境高级编程笔记之文件I/O

    一.总结 在写之前,先唠几句,<UNIX环境高级编程>,简称APUE,这本书简直是本神书,像我这种小白,基本上每看完一章都是“哇”这种很吃惊的表情.其实大概三年前,那会大三,我就买了这本书 ...

  9. [置顶] 文件和目录(二)--unix环境高级编程读书笔记

    在linux中,文件的相关信息都记录在stat这个结构体中,文件长度是记录在stat的st_size成员中.对于普通文件,其长度可以为0,目录的长度一般为1024的倍数,这与linux文件系统中blo ...

随机推荐

  1. FastDFS+Nginx安装配置

    下载相关包: libevent-2.0.22-stable.tar.gz => https://github.com/libevent/libevent/releases/download/re ...

  2. Kinect2.0 MultiSourceFrameReader 的 AcquireLatestFrame 方法获取不到帧的解决方案

    先把大致要写的东西写一下,手里的活忙完了再完善. 在代码中使用下边的语句,获取Kinect中,colorFrame, depthFrame, bodyIndex三种帧,但是经常会遇到在后边的程序中处理 ...

  3. PHP 文件

    PHP 文件处理 fopen() 函数用于在 PHP 中打开文件. 打开文件 fopen() 函数用于在 PHP 中打开文件. 此函数的第一个参数含有要打开的文件的名称,第二个参数规定了使用哪种模式来 ...

  4. spring-boot配置静态资源映射的坑:properties文件不能添加注释

    如此博文所述,Spring Boot 对静态资源映射提供了默认配置 默认将 /** 所有访问映射到以下目录:classpath:/staticclasspath:/publicclasspath:/r ...

  5. Ruby 2.x 命名参数特性简介

    我以前曾有一个梦想,就是我的爹是李嘉诚-,那个-,不是啦,我的梦想是ruby像ObjC,或是现在的swift那样给方法提供命名参数. 之前的ruby只能用hash来模拟这个行为,不过你没法很容易的定义 ...

  6. Linux Shell编程参考大全

    本文记录Linux Shell编程中常用基本知识,方便快速入门以及查询使用. 本文主要分为以下几个部分: 一.Shell中的变量 任何编程语言中,有关变量的定义,作用范围,赋值等都是最最基础的知识. ...

  7. Bootstrap3 栅格系统-栅格参数

    通过下表可以详细查看 Bootstrap 的栅格系统是如何在多种屏幕设备上工作的. -–下面有个"顶"字,你懂得O(∩_∩)O哈哈~ -–乐于分享,共同进步! -–更多文章请看:h ...

  8. AndroidStudio中导入SlidingMenu报错解决方案

    ----------------------------------------------------------------------------------------------[版权申明: ...

  9. CountDownLatch使用

    分享牛原创,CountDownLatch类的使用,CountDownLatch是一个工具类,运行主线程开启子线程的时候,子线程还没有结束的时候,主线程可以一直等待,直到初始化的现成的计数器count为 ...

  10. TensoFlow实现条件语句

    import tensorflow as tf a = tf.constant(20) b = tf.constant(10) result1 = tf.cond(a > b, lambda: ...