Unix I/O模型是在操作系统内核中实现的。应用程序可以通过 open()、read()、write()、lseek()、stat()、close() 函数来访问Unix I/O。
在Unix I/O函数上层,C应用程序对其进行了封装,产生了标准I/O函数和RIO函数。
1.open()函数
进程通过调用open函数来打开一个已存在的文件或者创建一个新文件
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int open(char *filename, int flags, mode_t mode);
/*返回:若成功则为新的文件描述符,若出错则为-1*/
参数说明:
open函数将filename转换为一个文件描述符,并返回描述符数字,数值为当前进程中未打开的最小描述符;
flags参数指明进程打算如何访问这个文件,可以是一个或者更多位掩码的或;
mode参数制定了新文件的访问权限位,只在创建新文件时有用。
创建新文件:
open(pathname, O_WRONLY|O_CREAT|O_TRUNC, mode)
2.read()函数
应用程序通过调用read函数执行输入功能
#include<unistd.h>
ssize_t read(int fd, void *buf, size_t n);
/*返回:若成功则为读的字节数,若EOF则为0,若出错为-1*/
read函数从文件描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。
ssize_t和size_t:
size_t被定义为unsigned long,而ssize_t被定义为long,是无符号数与有符号数的区别。read函数返回一个有符号的大小而不是一个无符号大小,这是因为出错时它必须返回-1。若返回值为无符号数,-1就会变成一个无符号的最大值,与我们要求的不相符
3.write()函数
应用程序通过调用write函数执行输出功能
#include<unistd.h>
ssize_t write(int fd, void *buf, size_t n);
/*返回:若成功则为写的字节数,若出错则为-1*/
write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。
在某些情况下,read和write传送的字节比应用程序要求的要少。但这些不足值不表示有错误,出现这样情况的原因有:
- 读时遇到EOF
- 从终端读文本行
- 读和写网络套接字
4.lseek()函数
应用程序通过调用lseek函数来显示地修改当前文件的位置
#include<sys/types.h>
#include<unistd.h>
off_t lseek(int fd, off_t offset, int whence);
/*返回:若成功则为新的偏移量,若出错则为-1*/
参数说明:
offset为偏移量,该值可正可负;
whence为定位的模式:
- SEEK_SET:基于文件开头定位,新光标位置=文件开头+offset(此时offset>0)
- SEEK_CUR:基于当前光标位置定位,新光标位置=当前光标位置+offset(可正可负)
- SEEK_END:基于文件末尾定位,新光标位置=文件末尾+offset(可正可负)
5.stat()函数
应用程序通过调用stat函数来检索到关于文件的信息(也称文件的元数据)
#include<unistd.h>
#include<sys/stat.h>
int stat(const *filename, struct stat *buf);
/*返回:若成功则为0,若出错则为-1*/
stat函数以一个文件名作为输入,并填写以下的一个stat数据结构中的各个成员:
struct stat{
dev_t st_dev; /* Device */
ino_t st_ino; /* inode */
mode_t st_mode; /* Protection and file type */
nlink_t st_nlink; /* Number of hard links */
uid_t st_uid; /* User ID of owner */
gid_t st_gid; /* Group ID of owner */
dev_t st_rdev; /* Device type(if inode device) */
off_t st_size; /* Total size,in bytes */
unsigned long st_blksize; /* Block size for filesystem I/O */
unsigned long st_blocks; /* Number of blocks allocated */
time_t st_atime; /* Time of last access */
time_t st_mtime; /* Time of last modification */
time_t st_ctime; /* Time of last change */
6.close()函数
进程通过调用close函数来关闭一个已经打开的文件
#include<unistd.h>
int close(int fd);
/*返回:若成功则为0,若出错则为-1*/
以上就是Unix中的6个基础函数。
在实际使用时,一定要考虑出错时的输出情况,因此可自己将基础函数进行封装,在封装后的函数内部处理出错的情况,在使用时就直接调用封装后的函数。但注意,由于新的函数不是系统自带的处理函数,在调用前一定要对其进行链接,否则会报错。
以open函数为例,在调用时,可直接选择Open函数来打开或创建文件:
int Open(const char *pathname, int flags, mode_t mode)
{
int rc;
if((rc = open(pathname, flags, mode)) < 0)
unix_error("Open error");
return rc;
}
还有一个比较重要的函数,即dup2函数,以实现I/O重定向功能。
#include<unistd.h>
int dup2(int oldfd, int new fd);
/*返回:若成功则为非负的描述符,若出错则为-1*/
dup2函数赋值描述符表表项oldfd到描述符表项newfd,覆盖描述符表表项newfd以前的内容。如果newfd已经打开了,dup2会在复制oldfd之前关闭newfd。
有关用法举例(foobar.txt文件由6个ASCII码字符“foobar”组成):
#include"csapp.h"
/*csapp.h为自定义的头文件,里面封装了Unix中的I/O函数*/
int main()
{
int fd1, fd2;
char c;
fd1 = Open("foobar.txt", O_RDONLY, 0);
fd2 = Open("foobar.txt", O_RDINLY, 0);
Read(fd2, &c, 1);
Dup2(fd2, fd1);
Read(fd1, &c, 1);
printf("c=%c\n", c);
exit(0);
}
在上述程序中,首先将foobar.txt文件同时打开两份。
由于系统自带的标准输入、标准输出、标准错误文件占用了0、1、2三个描述符,因此最新打开的文件描述符从3开始。则fd1=3,fd2=4。
在fd2中读取1个字符,即“f”,此时fd2的光标位置移动到了第二个字符处。
调用封装后的dup2函数,把fd2的内容覆盖到fd1上,此时对fd1操作也同时操作了fd2。因此在fd1中的光标位置也位于第二个字符处。
再从fd1中读取一个字符,即“o”。
因此该程序的输出结果为 c=o