一、文件描述符

对于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. MYSQL 表左连接 ON AND 和ON WHERE 的区别

    首先是针对左右连接,这里与inner join区分 在使用left join时,on and 和on where会有区别 1. on的条件是在连接生成临时表时使用的条件,以左表为基准 ,不管on中的条 ...

  2. Socket网络编程详解

    一,socket的起源 socket一词的起源 在组网领域的首次使用是在1970年2月12日发布的文献IETF RFC33中发现的, 撰写者为Stephen Carr.Steve Crocker和Vi ...

  3. 打造适合你的ABP(1)---- 完善日志系统

    最近使用Abp开发了一个项目,对abp有一个大概的了解,第一个小项目接近尾声,新的项目马上开始,针对开发第一个项目中发现的问题及不方便的地方,本人做一些修改,特此记录,请大家多多指正! 本人的开发环境 ...

  4. JS中的DOM— —节点以及操作

    DOM操作在JS中可以说是非常常见了吧,很多网页的小功能的实现,比如一些元素的增删操作等都可以用JS来实现.那么在DOM中我们需要知道些什么才能完成一些功能的实现呢?今天这篇文章就先简单的带大家入一下 ...

  5. swing JTable

    JTable 实例 import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayo ...

  6. ACM Tempter of the Bone

    小狗在古老的迷宫(maze)中发现了一个骨头,这使它非常着迷.然而,当他把它捡起来的时候,迷宫开始摇晃,狗狗可以感觉到地面下沉(sinking).他意识到这块骨头是一个陷阱(trap),他拼命地想摆脱 ...

  7. Node.js NPM 使用介绍

    NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题,常见的使用场景有以下几种: 允许用户从NPM服务器下载别人编写的第三方包到本地使用. 允许用户从NPM服务器下载并 ...

  8. SpringMVC总结(SSM)

    Day1 1. springMvc:是一个表现层框架: 作用:就是从请求中接收传入的参数, 将处理后的结果数据返回给页面展示2. ssm整合: 1)Dao层 pojo和映射文件以及接口手动编写(或使用 ...

  9. Mac小技巧:快速查看指定应用程序的所有窗口

    我们知道在Mac中快速在系统所有程序中切换得快捷键为: cmd + tab 不过有时我们需要快速查看某一个程序的所有窗口,那又该如何呢? 以下方法在MacOS 10.12中测试成功! Mac默认该功能 ...

  10. 硬件模块化机器人操作系统 Hardware Robot Operating System (H-ROS)

    原文网址:http://www.ros.org/news/2016/10/hardware-robot-operating-system-h-ros.html 推荐网址:https://h-ros.c ...