第十章IO函数文档(一)
创建文件
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
int open(char *filename, int flags, mode_t mode)
open函数,filename 转换为一个文件描述符,返回的描述符重视在进程中当前没有打开的最小描述符。
flags
参数指明进程访问文件的方式:
- O_RDONLY 只读
- O_WRONLy 只写
- O_RDWR 可读可写
通过
|
或运算形成操作叠加 - O_CREAT 文件如果不存在,就创建一个空文件,需要提供mode_t 作为权限说明
读文件和写文件
include
ssize_t read(int fd, void *buf,size_t n)
ssize_t write(int fd, const void *buf, size_t n)
将标准输入复制标准输出,一个一个字节地复制
int main(void){
char c;
while(Read(STDIN_FILENO,&c,1)!= 0)
Write(STDOUT_FILENO,&c,1);
exit(0);
}
需要注意的细节:
- 最多复制n个字节到内存位置buf,
- 其中ssize_t 表示 signed size, 而size_t 是无符号的
. 这个函数的返回值是成功读写的字节数 - 传送的对应字节不一定等于n(返回一个不足值),可能的原因是因为读写网络套接字,较长的网络延迟和内部缓存冲突
CSAPP 提供的RIO包
- 无缓冲的输入输出函数 函数直接在内存和文件之间传送数据
- 带缓冲的输入函数 线程安全的缓冲
# include ”csapp.h“
sszie_t rio_readn(int fd, void *usrbuf,size_t n);
sszie_t rio_writen(int fd, void *usrbuf,size_t n);
返回值:成功则传送成功的字节数,如果rio_readn碰到EOF返回0, 出错返回-1
带缓冲的输入函数
# include "csapp.h"
void rio_readinitb(rio_t *rp,int fd);
函数的使用原型:
#define RIO_BUFSIZE 8192
typedef struct{
int rio_fd;
int rio_cnt; // 内部buffer未读的字节
char *rio_bufptr; //下一个未读字节指针
char rio_buf[RIO_BUFSIZE]; //内部的缓冲buffer
}rio_t;
void Rio_readinitb(rio_t *rp, int fd){
rp-> rio_fd = fd;
rp-> rio_cnt = 0;
rp->rio_bufptr = rp->rio_buf;
}
每一次打开一个描述符,都会调用一次rio_readinitb()函数,将描述符fd和地址rio_t的度缓冲区联系起来
ssize_t rio_readlineb(rio_t *rp,void *usrbuf, size_t maxlen)
ssize_t rio_readnb(rio_t *rp, void *usrbuf,size_t n)
用null 字符结束文本行,最多读取maxlen -1 个字节,最后的字符给null。
使用实例:
rio_read函数
一个带有缓冲区的read函数,这个函数被static 修饰,不会被外部函数随便调用
//static 关键字可以限定函数的作用域,达到封装的效果
static ssize_t rio_read(rio_t *rp, char *usrbuf,size_t n){
int cnt;
while(rp->rio_cnt <=0 ){
rp->rio_cnt = read(rp->rio_fd, rp-> rio_buf, sizeof(rp->rio_buf));
if(rp->rio_cnt < 0){
if(errno != EINTR)
return -1;
}
//还是依赖于linux的read函数 一个字节都没有读入
else if(rp->rio_cnt == 0)
return 0;
else
//下移指针
rp->rio_bufptr = rp -> rio_buf;
cnt = n;
if(rp->rio_cnt < n){
cnt = rp->rio_cnt;
}
memcpy(usrbuf,rp->rio_bufptr,cnt);
rp->rio_bufptr += cnt;
rp->rio_cnt -= cnt;
//可能没有完全读取完成n
return cnt;
}
}
其他的一些使用实例:
# include "csapp.h"
int main(int argc, char** argv){
int n;
rio_t rio;
char buf[MAXLINE];
//STDIN_FILENO 是标准输入的文件描述符
Rio_readinitb(&rio,STDIN_FILENO);
while((n = Rio_readlineb(&rio,buf,MAXLINE)!=0))
Rio_writen(STDOUT_FILENO,buf,n);
}
获取文件元数据
# include <unistd.h>
# include <sys/stat.h>
int stat(const char *filename, stuct stat *buf);
int fstat(int fd. struct stat *buf)
将文件名未输入,填写一个stat数据结构中的各个成员,
fstat相似 通过文件名未输入
struct stat{
mode_t st_mode;//protection and file type
off_t st_size; // total size in bytes
}
使用宏为此确定成员文件类型
S_ISREG(m) 普通文件
S_ISDIR(m) 目录文件
S_ISSOCK(m) 套接字
使用宏和stat函数解释一个文件的st_mode;
struct stat stat;
Stat(argv[1],&stat);
is(S_ISREG(stat.st_mode)
读取目录内容
# include <sys/types.h>
# include <dirent.h>
//参数 路径名
DIR *opendir(const char *name);
返回目录流的指针,目录项的列表
# include <dirent.h>
struct dirent{
ino_t d_ino; //文件位置
char d_name[256]; // 文件名
}
struct dirent *readdir(DIR *dirp);
每次对readdir 的调用返回的是指向流dirp的想一个目录项的指针
如果调用出错会设置errno
使用closedir关闭而且释放所有的资源
DIR *streamp;
struct dirent *dep;
streamp = Opendir(argv[1]);
errno = 0;
while((dep = readdir(streamp))!=null)
//通过 dep->d_name 获取文件名
if(errno)
//手动检查errno errno应该是一个全局变量
Closedir(streamp);
共享文件
- 描述符表 每个京城独立的描述符表,表项通过文件的描述符进行索引。每一个描述符指向文件表中的一个项
- 文件表
所有的进程共享,包括文件位置,引用计数
如果直接调用close函数是降低引用计数,指导引用计数为0,这个文件才真正的关闭
多个描述符 引用同一个文件
文件位置:
每个文件都有一个32位的数字来表示下一个读写的 字节位置,这个数字叫做文件位置。每次打开一个文件,除非明确要求,否则文件位置都被置为0,即文件的开始处,此后的读或写操作都将从文件的开始处执行, 但你可以通过执行系统调用LSEEK(随机存储)对这个文件位置进行修改
就像这个表中写得,每一个文件描述符都对应了一个文件表中的一个表项,所以同一个文件的文件位置可以不同.
fork()
fork之后父子进程会继承相同的文件描述表,所以一个子进程修改文件位置后,父进程中的文件位置也被改变