发布时间:2014-09-05 13:18:07作者:知识屋
个人学习整理,如有不足之处,请不吝指教。转载请注明:@CSU-Max
系列博文:
Linux&Unix学习第一弹 -- 文件描述符与权限Linux&Unix学习第二弹 -- exec 与 fock 系统调用
Linux&Unix学习第三弹 -- open/close/write/read系统调用
在 Unix/Linux 系统中,文件是一个很重要的概念,本文将介绍 Linux 中和文件相关的几个重要的系统调用--open-close-write-read 系统调用。
<span style="font-family:Courier New;font-size:18px;"><span style="font-family:Courier New;font-size:18px;">//open -- 打开或创建文件int open ( const char *path, /*pathname*/ int flags, /*flags*/ mode_t perms /*permissions (when creating)*/) ;</span></span>
调用 open可以打开一个已经存在的文件(普通文件、特殊文件或命名管道),或创建一个新文件,但它只能创建普通文件(创建特殊文件需要使用 mknod,命名管道使用 mkfifo)。open返回是打开已存在的文件或创建新文件的文件描述符。文件一旦打开,read、 write、 lseek、 close以及其他调用就可以使用其返回的文件描述符。
首先我们来看一下 open函数的三个参数,path是已经存在的文件的路径;至于 flags参数,若值为 O_RDONLY ,就以只读方式打开文件,若值为 O_WDONLY,就以只写方式打开文件,若值为 O_RDWR,就以读写方式打开文件;而对于一个已经存在的文件,参数 perms是没有用的,通常将其省略,因此此种情况下 open调用只需两个参数。
open失败的原因很多,常见的有如下两种:
1.没有相应的文件访问权限
2.路径所指向的文件不存在
前面已经说到,当文件不存在时,open会创建一个新文件(仅能是普通文件),我们只需要用 or操作向 open的 flags参数中加入标志 O_CREAT即可。这样可以创建一个新的只读文件,但是这没有任何意义,因为所创建的新文件没有任何可读内容。因此一般需要 O_CREAT与 O_WRONLY或 O_RDWR一起使用,此时就需要 perms参数了。
例如:
ec_neg1( fd = open(“/home/marc/newfile”,O_RDWR | O_CREAT, PERM_FILE) )
参数 perms仅在创建新文件时有效,对于一个已经存在的文件,它没有任何作用。
用户有时需要一个新的、没有任何数据的文件,即当文件已经存在,需要将其所有数据清除,并设置文件偏移量为0。标志 O_TRUNC可以实现此功能:
ec_neg1( fd = open(“/home/marc/newfile”,O_WRONLY | O_CREAT | O_TRUNC, PERM_FILE) )
因为 O_TRUNC能够破坏数据,所以只要进程具有写权限,就可以清除已存在文件的数据,因为它是写形式的一种。但对于具有 O_RDONLY标志的文件,它就不起作用了。
O_WRONLY| O_CREAT | O_TRUNC这个组合是很常见的(创建或截短一个具有只写权限的文件),也有专门的相关的系统调用,即 creat系统调用。
<span style="font-family:Courier New;font-size:18px;"><span style="font-family:Courier New;font-size:18px;">//creat -- 创建或清空文件int creat ( const char *path, /*pathname*/ mode_t perms /*permissions*/); </span></span>
采用 open打开一个已经存在的文件只需要第一个参数和第二个参数(path和 flags);使用 creat创建新文件仅需要第一个参数和第三个参数(path和 perms),实际上, creat仅仅是一个宏:
#define creat(path, perms) open(path,O_WRONLY | O_CREAT |O_TRUNC, perms)
当然我们也可以仅使用open 而放弃使用 creat,但是在早期,open只有两个参数,那时creat则无可替代。
除了以上介绍的 open标志外,open还有许多标志,具体的如下表所示:
标志 | 解释 |
O_RDONLY | 只读方式打开 |
O_WRONLY | 只写方式打开 |
O_RDWR | 读写方式打开 |
O_APPEND | 每次写都追加到文件的尾端 |
O_CREAT | 若文件不存在则创建文件 |
O_DSYNC | 设置同步I/O方式 |
O_EXCL | 如果文件已存在,则出错;必须与O_CREAT一起使用 |
O_NOCTTY | 不将此设备作为控制终端 |
O_NONBLOCK | 不等待命名管道或特殊文件准备好 |
O_RSYNC | 设置同步I/O方式 |
O_SYNC | 设置同步I/O方式 |
O_TRUNC | 将其长度截短为0 |
<span style="font-family:Courier New;font-size:18px;"><span style="font-family:Courier New;font-size:18px;">//close -- 关闭文件描述符 int close ( int fd /*file descriptor*/);</span></span>
通过对 close进行分析,我们会发现close并没有做什么实质工作,它没有刷新任何内核缓冲区,而仅仅是使文件描述符可以重用。
如上图,当指向一个打开文件描述的所有文件描述都关闭时,将删除该打开文件描述。同样的,当指向一个信息节点的所有打开文件描述都被删除时,将删除该内存信息节点。
<span style="font-family:Courier New;font-size:18px;"><span style="font-family:Courier New;font-size:18px;">//write -- 向文件描述符写ssize_t write ( int fd, /*file descriptor*/ const void *buf, /*data to write*/ size_t nbytes /*amount to write*/ ); </span></span>
write系统调用将 buf所指向的缓冲区的 n字节写入 fd 所描述ude打开文件中。
写操作从文件偏移量的当前位置开始执行,并且在完成之后,文件偏移量将增加所写入的字节数。若写入成功,返回值为已写入的字节数,出错则为 -1。
若设置了O_APPEND标志,写入前文件偏移量自动定位到文件的结尾。
本文仅讨论普通文件的写操作。
注:write也用与向管道,特殊文件和套接字写入数据,但是情况会有些不同,这些写操作可以阻塞(如它们可能正在等待可用数据)。如果阻塞了写操作,那么到达的信号会中断其操作。这种情况下写操作将返回-1,并将 errno设置为 EINTR。
看了前面的介绍,write似乎只是写入数据,接着返回结果。实际上并非如此。当用户调用 write系统调用时并不执行写操作,紧接着返回数据,它仅仅是将数据传递给内核的缓冲区。
当接收到 write请求时,先确保传入的文件描述符可以使用,接着将数据复制到内核中的缓冲区。以后,在某个方便的时候,系统会设法把这部分的数据写入磁盘中。若发现错误,就会设法在控制台输出错误,但是该进程不会返回这个错误(可能此时其已经终止运行了)。若在系统向磁盘写出这些数据之前,该进程或其他进程试图要读取这些数据,那么系统将从内核缓冲区为你读取这些数据。
总而言之,该进程不知道系统什么时候完成了请求,也不知道是否完成了请求。如果在该部分缓冲区的数据写出磁盘之前,有磁盘错误,或者由于某种原因内核停止了,那么你会发现要写的数据根本没有写到磁盘上,即使 write 没有报错。
由此我们可以看出,这是一种延迟写,那么延迟写有哪些我们需要关心的问题呢?
1.不能确定什么时候发生物理写操作
2.一个调用写操作的进程没有得到写错误的通知
3.物理写操作的顺序是无法操作的
writeall是一个非常方便实用的函数,当需要确保写入所有的内容时,可以使用此函数。
<span style="font-family:Courier New;font-size:18px;"><span style="font-family:Courier New;font-size:18px;">//writeallssize_t writeall(int fd, const void *buf, size_t nbyte){ ssize_t nwritten = 0; //总共写出的字符数 sszie_t n; //每次 write 操作写出的字符数 do{ if((n = write(fd, &((const char *)buf)[nwriten], nbyte - nwritten)) == -1) { if(errno == EINTR) //阻塞时持续循环写出 continue; else return -1; } nwritten += n; }while(nwritten < nbyte); return nwritten;}</span></span>
<span style="font-family:Courier New;font-size:18px;"><span style="font-family:Courier New;font-size:18px;">//read -- 从文件描述符中读入ssize_t read ( int fd, /*file descriptor*/ void *buf, /*address to receive data*/ size_t nbytes /*amount to read*/ ); </span></span>
read系统调用与 write相反,它是从 fd所描述的打开文件中读取 buf所指缓冲区中的 n个字节。read从当前文件偏移量开始读数据,并且完成读操作后,文件偏移量将增加所读字节数。read的返回值是所读字节数、文件结束标志或者错误标志-1。读操作不受 O_APPEND标志的影响。
如果数据已经不在缓冲区中(由于以前的I/O操作),进程必须等待内核从磁盘得到数据。
与 write一样,从管道、特殊文件或套接字中读取数据时,read可以阻塞。此时读操作可能会被信号中断,结果返回值为-1,并把 errno 设置成 EINTR。
类比 writeall函数,如果需要读所有的数据,则通过循环调用 read,readall函数就是这样的一个非常方便实用的函数。
<span style="font-family:Courier New;font-size:18px;"><span style="font-family:Courier New;font-size:18px;">//readallssize_t readall(int fd, const void *buf, size_t nbyte){ ssize_t nread == 0; //总共读取的字符数 ssize_t n; //每次 read 操作读取的字符数 do{ if((n = read(fd, &((const char *)buf)[nread], nbyte - nread)) == -1) { if(errno == EINTR) //阻塞时持续循环读取 continue; else return -1; } nread += n; }while(nread < nbyte); return nread;}</span></span>
linux一键安装web环境全攻略 在linux系统中怎么一键安装web环境方法
Linux网络基本网络配置方法介绍 如何配置Linux系统的网络方法
Linux下DNS服务器搭建详解 Linux下搭建DNS服务器和配置文件
对Linux进行详细的性能监控的方法 Linux 系统性能监控命令详解
linux系统root密码忘了怎么办 linux忘记root密码后找回密码的方法
Linux基本命令有哪些 Linux系统常用操作命令有哪些
Linux必学的网络操作命令 linux网络操作相关命令汇总
linux系统从入侵到提权的详细过程 linux入侵提权服务器方法技巧
linux系统怎么用命令切换用户登录 Linux切换用户的命令是什么
在linux中添加普通新用户登录 如何在Linux中添加一个新的用户
2012-07-10
CentOS 6.3安装(详细图解教程)
Linux怎么查看网卡驱动?Linux下查看网卡的驱动程序
centos修改主机名命令
Ubuntu或UbuntuKyKin14.04Unity桌面风格与Gnome桌面风格的切换
FEDORA 17中设置TIGERVNC远程访问
StartOS 5.0相关介绍,新型的Linux系统!
解决vSphere Client登录linux版vCenter失败
LINUX最新提权 Exploits Linux Kernel <= 2.6.37
nginx在网站中的7层转发功能