创建文件

# 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,这个文件才真正的关闭
    多个描述符 引用同一个文件

image.png 文件位置: 每个文件都有一个32位的数字来表示下一个读写的 字节位置,这个数字叫做文件位置。每次打开一个文件,除非明确要求,否则文件位置都被置为0,即文件的开始处,此后的读或写操作都将从文件的开始处执行, 但你可以通过执行系统调用LSEEK(随机存储)对这个文件位置进行修改
就像这个表中写得,每一个文件描述符都对应了一个文件表中的一个表项,所以同一个文件的文件位置可以不同.

image.png fork() fork之后父子进程会继承相同的文件描述表,所以一个子进程修改文件位置后,父进程中的文件位置也被改变