对于unix系统而言,一切皆“文件”,所以我们就可以使用一套基本的IO函数,来对所有的“文件”进行读写操作!现在我们来看下文件操作的几个底层IO
1.open函数用来打开或创建一个文件,其函数原型如下:
int open(const char *pathname, int oflag, ... /* mode_t mode */);
第一个参数pathname用来指定需要打开的文件名;
第二个参数oflag用来指定打开文件的方式以及其他一些附加属性,常用的值有:
O_RDONLY | 只读 |
O_WRONLY | 只写 |
O_RDWR | 读写 |
以上三个属性必须指定一个,但是下列属性是可选的
O_APPEND | 该参数表示每次写操作都是将内容添加到文件末尾(注意:使用该标记位后,每次调用write函数时,首先将file_table中current file offset字段(文件偏移)设置为文件末尾,然后在进行写操作,并且这一系列操作都是原子操作) |
O_CREAT | 该参数表示当需要打开的文件不存在时,则创建(当指定这个标记位的时候,需要使用第三个参数mode,来指定创建文件的默认权限) |
O_EXCL | 该参数表示如果需要打开的文件已经存在,并且指定了O_CREAT标记,则报错。该参数的作用可以参考下列讨论 http://bbs.csdn.net/topics/350001997 |
O_TRUNC | 该参数表示如果打开的文件已经存在,则删除文件中的内容(将文件大小截断为0) |
如果打开或创建文件成功,open会返回一个文件描述符(int型值),否则返回-1表示调用失败
2.在较老的版本中,open函数只能用来打开一个已存在的文件,所以当文件不存在时,open函数会报错;若我们希望创建一个新的文件,则必须调用一个creat函数:
int creat(char *pathname, mode_t mode);
该函数等价于: open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode_t)
creat函数有一个不足,就是对于新创建的文件
只能够进行写操作,若是你想对这个刚创建的文件进行读操作,就只能先close掉,然后再open进行读操作,非常繁琐,但是如果我们使用最新的open函数(对于不存在的文件,可以先创建然后再打开),则可以很简单的实现这一功能:open(pathname, O_RDWR | O_CREAT | O_TRUNC, mode_t)
所以,基本上我们可以使用open函数来替代creat函数了
3.当我们成功调用open或者是creat函数时,会得到一个文件描述符(用来代表我们需要操作的文件),接着我们就可以拿着这个文件描述符对文件进行读写操作
a.read函数原型如下:
ssize_t read(int fileds, void *buf, size_t nbytes);
第一个参数fileds是文件描述符,第二个和第三个参数用来指定读取数据的存放位置以及每次read函数允许读取数据的最大数量(nbytes),read函数的返回值为此次调用读取到的字节数,若读取失败返回-1;
默认情况下,只有读取完nbytes个字节后,read函数才会返回,但是当遇到以下几种情况时,在未读取足够的字节时(< nbytes),read函数也会返回:
1.如果读取的是一个普通文件,当遇到EOF时,read函数立即返回;
2.如果读取的是一个终端设备(譬如标准输入),当遇到一个换行符时,read函数立即返回;
3.其余情况参考《apue》(自己尚未理解,暂不罗列);
b.write函数原型:
ssize_t write(int fileds, void *buf, size_t nbytes);
该函数返回值表示成功写入的字节数,若返回值等于nbytes表示写操作成功;
该函数比较简单,但是要注意的是,当文件打开标记含有O_APPEND时,每次写操作(调用write函数)首先会将当前的文件的偏移设置为文件默认,然后再写入输出数据;
4.当我们完成读写操作后,需要调用close函数关闭刚才打开的文件描述符(释放资源),close函数原型为:
int close(int fileds);
注意,即使我们没有主动关闭文件描述符,当一个进程终止时,也会关闭所有还未关闭的文件描述符;说了这么多,我们来看一个例子(会用上我们之前说的所有函数):
#include <apue.h>
#include <fcntl.h>
#define BUF_SIZE 4096
#define FILE_MODE 0666
int main(void)
{
int fd, n;
char buf[BUF_SIZE];
if ((fd = open("out.tmp", O_WRONLY | O_CREAT | O_TRUNC, FILE_MODE)) < 0) {
printf("Fail to open out.tmp\n");
}
while ((n = read(STDIN_FILENO, buf, BUF_SIZE)) > 0) {
if (write(fd, buf, n) != n) {
printf("Fail to write out.tmp\n");
}
}
close(fd);
return 0;
}
这样我们就用最底层文件IO实现了将标准输入内容输出到文件的功能!
ps:
一个进程中默认有三个已打开的文件描述符:STDIN_FILENO(标准输入),STDOUT_FILENO(标准输出),STDERR_FILENO(标准错误输出)
5.通常我们是从文件开始处读写数据,但有的时候我们可能需要在文件中间某个位置读写数据,要实现这样的功能,需要使用lseek函数,其函数原型如下:
off_t lseek(int filedes, off_t offset, int wherece);
第二个参数offset表示相对于
指定位置wherece的偏移量,wherece有以下3个值:
SEEK_SET | 文件起始处 |
SEEK_CUR | 文件当前位置 |
SEEK_END | 文件末尾 |
--------------------------------------------------------------------------华丽风格线--------------------------------------------------------------------------
unix使用三种数据结构表示一个打开的文件,这三种结构是:
1.file_descripter_table(文件描述符表): 每个进程都维护了一个文件描述符表来操作和管理该进程打开的所有文件,该表的每条记录主要有两个字段:
a.文件描述符
b.文件指针
(指向另一个数据结构file_table)
2.file_table(文件表):该表由操作系统维护,用来记录被打开文件的相关信息,其主要字段有:
a.文件标记(譬如我们使用open函数打开文件所指定的标记位)
b.文件偏移
c.v-node指针
(指向另一个数据结构v-node_tabe)
3.v-node_table:关于它,只需要知道每一个打开的文件有且仅对应一个v-node即可
这三张表的大体结构我们已经说完,从文字描述中,我们也可以得知,这三表存在很强的关联性,现在我们通过下面这张图来更清晰的看下这几张表的关系:
当我们调用lseek函数时,其实只是移动了相关file_table中的current file offset,而未进行实际的IO操作(如果current file offset的值超过current file size,则更新v-node的current file size)
现在根据上图,我们来思考下,多个进程是如何实现文件享的,譬如A进程打开了文件hello.c,此时假如B进程也打开了hello.c文件 ,那么B进程能否读取hello.c文件并且B进程的读取是否会影响到A进程的读取?
要回答上面的问题,我们先看下面的图片
可以发现,当多个进程打开同一个文件时,只有v-node_table这一张表是共享的,其他都是独立的,
这样每个进程就可以独立的记录自己的文件偏移,因此B进程的读取不会影响到A进程!通过这样的表结构组织,我们就实现了文件共享读!
最后我们在看下dup,dup2这两个函数,这两个函数时用来复制文件描述符的,那么复制文件描述符有什么作用呢?我们先看一个linux命令:
echo "hello, world" > out.tmp
该命令用来把"hello, world"从标准输出重定向到out.tmp文件中,那么这个重定向是如何实现的呢? 这里就需要使用dup或者dup2函数,把标准输出的文件指针指向指向out.tmp文件的file table, 如下图:
我们看下相关代码:
#include <apue.h>
#include <fcntl.h>
int main(void)
{
int fd, stdout_fd;
char *str = "hello, world\n";
if((fd = open("out.tmp", O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) {
printf("Fail to create out.tmp\n");
exit(1);
}
stdout_fd = dup(STDOUT_FILENO);
dup2(fd, STDOUT_FILENO);
write(STDOUT_FILENO, str, strlen(str));
dup2(stdout_fd, STDOUT_FILENO);
write(STDOUT_FILENO, str, strlen(str));
close(fd);
return 0;
}
执行结果:
可以发现终端只输出了一次"hello, world",而原因是:第一次调用write输出时,标准输出文件描述符被重定向到文件(此时"hello,world"被输出到out.tmp文件),第二次调用write函数时,标注输出文件描述符已经恢原来设置(指向终端),所以此时我们看到在屏幕上输出了"hello,world"
![点击查看原始大小图片](http://dl2.iteye.com/upload/attachment/0080/7836/8ca9168d-3b6b-3cc7-9bf2-de900572f97e-thumb.jpg)
- 大小: 41.1 KB
![点击查看原始大小图片](http://dl2.iteye.com/upload/attachment/0080/7842/5068d747-76d1-312f-b2c8-50dc48847a72-thumb.jpg)
- 大小: 47.2 KB
![点击查看原始大小图片](http://dl2.iteye.com/upload/attachment/0080/7848/28a191cc-c4ec-330c-881f-68d70517dc1b-thumb.jpg)
- 大小: 27.4 KB
![点击查看原始大小图片](http://dl2.iteye.com/upload/attachment/0080/8172/811ac800-a925-31be-817d-3f7e9ffeaa64-thumb.jpg)
- 大小: 13.5 KB
分享到:
相关推荐
学习LINUX环境编程的见证,一笔一画,脉络清楚,结构清晰,自己再看一目了然,传上来与众分享
自己的读书笔记,2012-10-16,只读了部分章节。
unix网络编程-第三版读书笔记unix网络编程-第三版读书笔记
《UNIX环境高级编程》这本书是UNIX系统开发人员的必读之作,主要涵盖了UNIX系统的底层机制和高级编程技术。以下是对第一章“UNIX基础知识”的详细解读。 首先,登陆UNIX系统需要输入登录名和口令。口令文件通常是/...
这是Unix环境高级编程所做的笔记。在学习这本书的时候有参考价值。
该APUE读书笔记为《UNIX环境高级编程第二版》的读书心得,整理的非常详细,很适合当工具书和参考,基本囊括了证书的内容
在阅读《Unix环境高级编程》的过程中,搭建环境的详细笔记将有助于更好地理解和实践上述概念。这些笔记可能包括安装必要的编译工具、配置环境变量、解决依赖问题以及实际编程示例等步骤,确保读者能够顺利地在实践中...
网上找了份UNIX环境高级编程的笔记,觉得内容太少了,自己看了一遍书(没看全),再作了一下补充
笔记_UNIX环境高级编程(第二版)
笔记_UNIX环境网络编程卷一套接字联网API_中文第三版(第一轮)
UNIX 网络编程读书笔记 ...本篇读书笔记涵盖了 UNIX 网络编程的基础知识、并发服务器设计思想、TCP/IP 协议、Socket 编程、高级知识点和网络服务器设计等多个方面,对于学习 UNIX 网络编程的读者非常有价值。
本文档为《unix高级环境编程》即APUE的总结笔记版,适用于为初学者提纲挈领!
Java I/O详细笔记
"Unix Shell编程第三版笔记"是铁道出版社出版的一本教材,它深入浅出地讲解了Unix Shell编程的基础和高级技巧。这份笔记涵盖了从基本的命令行操作到复杂的脚本编写,帮助学习者掌握这一强大的自动化工具。 Unix ...
笔记_UNIX环境网络编程卷二进程间通信_中文第二版
本文将基于《LINUX与UNIX SHELL编程指南》这本书的读书笔记,深入探讨Linux与Unix Shell编程的核心知识点。 一、Shell概述 Shell是一个命令解释器,它接收用户输入的命令并执行。在Linux和Unix系统中,常见的Shell...
只包含部分章节1,3,4,5,7,8,9,10,14
UNIX 高级编程笔记 PDF格式71页 对学习同学来说,是一个索引,学习就有方向。
想学移动开发,unix高级编程是必修课,笔记有助于你抓住知识的主线