unix i/o

UNIX I/O

参考csapp,现在做一个总结

文件

/*******************************************************************************/
所有的I/O设备(磁盘,网络,终端)都被模拟成是文件
分类:普通文件(文本文件和二进制文件)
目录文件(包含一组链接的文件,每个链接都将一个文件名映射到一个文件),
套接字(用来与另一个进程进行跨网络通信的文件)

/*******************************************************************************/

打开文件

打开文件:应用程序要求内核打开文件来访问I/O设备,内核从可用的描述符池中返回一个非负整数,即描述符,直到被关闭之前,对文件的所有操作都用该描述符标识,应用程序只需记住这个描述符。
linux创建的任何进程开始时就已经打开了三个文件:标准输入,标准输出和标准错误,描述符分别为0,1,2或STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int open(char*filename, int flags, mode_t mode);
返回:成功返回新文件描述符,错误返回-1.

int fd1=open(“foo.txt”,O_RDONLY,DEF_MODE)
filename即要打开的文件名
flags:指明进程将如何访问打开的这个文件,注意与文件本身的权限的区别
O_RDONLY:只读,进程将只读该文件,前提:该文件是可读的
O_WRNOLY:只写,进程将只写该文件,前提:该文件是可写的
O_RDER: 读写,…
O_CREAT: 如果文件不存在就创建一个截断的(空)文件
O_TRUNC:如果文件已经存在就截断它
O_APPEND:在每次写操作前设置文件位置到文件结尾处
mode:当创建新文件时用来设置文件的访问权限,每个进程都有一个umask,文件的权限位被设置为mode &~ umask,设置方法

#define DEF_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH
#define DEF_UMASK S_IWGRP|S_IWOTH

umask(DEF_UMASK);
fd = open("foo.txt",O_CREAT|O_TRUNC|O_WRONLY,DEF_MODE);

进程通过调用open函数打开已存在的文件或创建新的文件
使用注意点:
1文件是否已经存在,如不存在需不需要创建
2创建新文件时要注意文件的读写权限

/*******************************************************************************/

关闭已达开的文件

内核关闭文件,释放描述符到可用描述符池,当一个进程终止,内核会关闭所有其打开的文件

#include<unistd.h>
int close(int fd);

返回:成功则为0,出错则为-1

/*******************************************************************************/

读和写文件

文件位置:既然要读写就要指出读哪个文件从文件的哪里开始读写,对于每个打开的文件,内核保持着一个文件位置k,初始为0,是从文件头起始的偏移量
读: 从文件当前位置k开始复制n>0个字节到内存,,然后将k增加到k+n。当k超过文件大小的字节数m会触发EOF
写: 从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k

#include<unistd.h>
ssize_t read(fd, const void*buf, size_t n);//从fd的当前文件位置复制最多n个字节到buf

返回:成功返回读得字节数,若EOF则为0,若出错则为-1,被信号中断返回-1并设置errno为EINTR
ssize_t write(int fd,const void *buf, size_t n);从内存位置buf复制最多n个字符到当前文件位
返回:若成功则为写的字节数,出错则为-1

关键:文件:fd,内存位置:buf(需由自己声明),字节数:n
例:

#include"csapp.h"
int main(){
	char c; //这里的内存位置既然设置为char c那么,每次读写的字节数就写1,如果大了可能会出错
	while(read(STDIN_FILENO,&c,1)!=0)//STDIN_FILENO表明可以任意输入,当按下enter后才开始读一个字节到内存
	  write(STDOUT_FILENO,&c,1);//然后再向标准输出中写一个字节,然后回去再读下一个。
	exit(0);
}

输出

$ ./wr1
jhkgjgkjhg
jhkgjgkjhg

/***********************************************************************************************/

深入理解unix I/O:不足值及其处理

read和write时需要指定读或写的字节数,但返回值可能会小于这个字节数,原因有
1:磁盘文件,EOF读到头了,否则将不会遇到不足值
2:从终端读取文本,如果打开的文件是与终端相关联的,则每次传送的是一个文本行,返回的则是实际的大小(肯定小于文本行
3:读和写网络套接字:需要读的字节数是很大的,因为缓冲区限制或者网络延迟,返回的字节数肯定也是小于全部的信息字节数的
前两者的不足值可能无所谓,毕竟该读的都读完了,但是对于第三种,一个健壮的web服务器必须反复调用read和write来处理不足值,直到所有的字节斗传送完毕。

如何处理不足值?即文件可能因为某种原因没读完就返回了,如何再接着读?
:关键是如何判断这次的read读完了:1.遇到EOF即返回值为0肯定读完了,2.读取的字节数等于设置的字节数
1.无缓冲的输入输出函数:用于将二进制数据读写到网络和从网络中读写二进制数据。
#include"csapp.h"
ssize_t rio_readn(int fd,void *usrbuf, size_t n);
ssize_t rio_written(int fd,void *usrbuf,size_t n);
当作read和write使用,优点:只有读完了文件,或者读取例要求的字节数才会停止,被信号中断了会自动重新读

ssize_t rio_read(int fd, void *usrbuf, size_t n){
char *bufp = usrbuf;
size_t nleft = n;
ssize_t nread;
while(nleft>0){
nread = read(fd,bufp,nleft)
if(nread <0){
if(errno = EINTR)//被信号中断,并从信号处理程序返回,此时不会读取,所以得接着读
nread=0;
else
return -1;//其他错误
}
else if(nread ==0)
break;
//when nread>0或信号中断那里的nread=0
nleft = n - nread;//所以对于网络的数据read之后总是会自动继续read
bufp = bufp + nread;
}
return (n-nleft);
}

ssize_t rio_writen(int fd, void *usrbuf, size_t n)
{
size_t nleft = n;
ssize_t nwritten;
char *bufp = usrbuf;

while (nleft > 0) {
if ((nwritten = write(fd, bufp, nleft)) <= 0) {
    if (errno == EINTR)  /* Interrupted by sig handler return */
	nwritten = 0;    /* and call write() again */
    else
	return -1;       /* errno set by write() */
}
nleft -= nwritten;
bufp += nwritten;
}
return n;

}
2.带缓冲的输入输出函数:高效,便于处理
#include"csapp.h"
void rio_readinitb(rio_t *rp, int fd);//首先将文件和缓冲区关联起来
ssize_t rio_readlineb(rio_t *rp, void *usrbuf,size_t maxlen);//从内部缓冲区复制一个文本行\n结尾
ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n);//rio_readn的带缓冲区的版本,功能一样
返回:成功返回读的字节数,EOF则为0,若出错则为-1

//首先设置一个内存缓冲区,实际上就是在内存中模拟一个文件一个模拟文件:描述符,文件位置的指针,一块内存空间,此外还有一个参数记录未读的字节数

#define RIO_BUFSIZE 8192
typedef struct{
	int rio_fd;//描述符
	int rio_cnt;//未读取的字节数
	char *rio_bufptr;//文件位置的指针
	char rio_buf[RIO_BUFSIZE];//内存空间
}rio_t;

//rio_readinitb函数将文件和上面的内存空间关联起来

void rio_readinitb(rio_t *rp, int fd)
{
    rp->rio_fd = fd;
    rp->rio_cnt = 0;
    rp->rio_bufptr = rp->rio_buf;
}

//重点是这个rio_read函数

static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n)
{
    int cnt;
    while (rp->rio_cnt <= 0) {  /* Refill if buf is empty */
	rp->rio_cnt = read(rp->rio_fd, rp->rio_buf,
		   sizeof(rp->rio_buf));
	if (rp->rio_cnt < 0) {
	    if (errno != EINTR) /* Interrupted by sig handler return */
		return -1;
	}
	else if (rp->rio_cnt == 0)  /* EOF */
	    return 0;
	else
	    rp->rio_bufptr = rp->rio_buf; /* Reset buffer ptr */
    }

/* Copy min(n, rp->rio_cnt) bytes from internal buf to user 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;
return cnt;
}

/*

  • rio_readnb - Robustly read n bytes (buffered)
    /
    /
    $begin rio_readnb */
    ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n)
    {
    size_t nleft = n;
    ssize_t nread;
    char *bufp = usrbuf;

    while (nleft > 0) {
    if ((nread = rio_read(rp, bufp, nleft)) < 0)
    return -1; /* errno set by read() /
    else if (nread == 0)
    break; /
    EOF */
    nleft -= nread;
    bufp += nread;
    }
    static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n)
    {
    int cnt;

    while (rp->rio_cnt <= 0) { /* Refill if buf is empty /
    rp->rio_cnt = read(rp->rio_fd, rp->rio_buf,
    sizeof(rp->rio_buf));
    if (rp->rio_cnt < 0) {
    if (errno != EINTR) /
    Interrupted by sig handler return /
    return -1;
    }
    else if (rp->rio_cnt == 0) /
    EOF /
    return 0;
    else
    rp->rio_bufptr = rp->rio_buf; /
    Reset buffer ptr */
    }

    /* Copy min(n, rp->rio_cnt) bytes from internal buf to user 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;
    return cnt;
    }

// rio_readnb - Robustly read n bytes (buffered)
ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n)
{
size_t nleft = n;
ssize_t nread;
char *bufp = usrbuf;

while (nleft > 0) {
if ((nread = rio_read(rp, bufp, nleft)) < 0) 
        return -1;          /* errno set by read() */ 
else if (nread == 0)
    break;              /* EOF */
nleft -= nread;
bufp += nread;
}
return (n - nleft);         /* return >= 0 */

}

// rio_readlineb - Robustly read a text line (buffered)

ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen)
{
int n, rc;
char c, *bufp = usrbuf;

for (n = 1; n < maxlen; n++) { 
    if ((rc = rio_read(rp, &c, 1)) == 1) {
    *bufp++ = c;
    if (c == '\n') {
            n++;
 		break;
        }
} else if (rc == 0) {
    if (n == 1)
	return 0; /* EOF, no data read */
    else
	break;    /* EOF, some data was read */
} else
    return -1;	  /* Error */
}
*bufp = 0;
return n-1;

}

unix目录

/*********************************************************************************/

打开目录

目录也是一种文件,不过与前面的区别是
输入文件名——打开文件返回描述符————读写文件————关流闭文件
输入路径名——打开目录流返回指针————读取目录项————关闭目录流
//输入路径名,打开目录流,获取指针

#include<sys/types.h>
#include<dirent.h>
DIR *opendir(const char *name);//参数就是路径的字符串,返回指向该目录的指针

返回:若成功则为处理的指针,若出错则为NULL

读取目录

//以指针为参数,获取下一个目录项的指针

#include<dirent.h>
struct dirent *readdir(DIR *dirp);

返回:成功返回指向下一个目录的指针,若没有更多目录项或出错则为NULL
目录项是一个结构

struct dirent{
	ino_t d_ino; //文件位置
	char d_name[256]; //文件名
}

标题关闭目录流

#include<dirent.h>
int closedir(DIR *dirp);

例:

#include "csapp.h"
int main(int argc, char **argv)
{
    DIR *streamp;
    struct dirent *dep;

/* $end readdir */
if (argc != 2) {
    printf("usage: %s <pathname>\n", argv[0]);
    exit(1);
}
/* $begin readdir */
streamp = Opendir(argv[1]);

errno = 0;
while((dep=readdir(streamp))!=NULL){//每次读取目录流的下一个目录项,从结果来看,肯定也有个类似于文件位置的东西,每读一次加1,因为没有重复读取一个目录项,而是读到头触发返回NULL
    printf("Found file: %s\n", dep->d_name);
}
if (errno != 0)
    unix_error("readdir error");

Closedir(streamp);
exit(0);

}
输出:

$ ./readdir /home/lm/Desktop/repository/
Found file: .git
Found file: chapter9
Found file: chapter7
Found file: .
Found file: ..
Found file: chapter10
Found file: chapter8

/***********************************************************************************/

文件共享(理解三张表)

共享文件,注意每次打开文件都有者三张表,fork会共享父进程的文件表,所以对读写文件要从这里分析
内核使用三张表来表示打开的文件
1.描述符表:每个进程有它自己的一张描述符表,一个描述符一个文件表
2.文件表:所有打开文件的集合由文件表表示,由所有进程共享,每个表项标识了该文件与进程相关的信息,包括文件位置,引用计数(即当前指向该表项的描述符表项数),以及一个指向v-node表项的指针
3.v-node表:所有进程共享:文件大小,访问权限,文件类型,对应与一个实际文件,多个文件表可能指向惟一的一个v-node表
例:用同一个filename调用open两次会有两个描述符,对应两个文件表,一个v-node表
父子进程如何共享文件的?P636,假如父进程打开了文件,有自己的三个表,fork后,子进程拷贝一个父进程的描述表的副本,指向父进程的文件表,即文件表是进程共享的
/**************************************************************************************************/

I/O重定向

I/O重定向,即在一个进程之内,改变描述符的指向,使其指向一个文件表,从而最终作用于另一个文件

#include<unistd.h>
int dup2(int oldfd,int newfd);复制oldfd的描述表项到newfd
返回:成功返回非负的描述符,出错为-1


dup2(4,1);//则将标准输出1重定向到了fd=4对应的文件,原来对应的显示器则释放掉,从而后来使用fd=1进行的写都将作用于fd=4对应的文件

shell也提供了重定向的操作符
linux> ls > foo.txt加载和执行程序ls并将标准输出重定向到foo.txt

/************************************************************************************/

conclusion

unix I/O,通过系统调用来访问:open,read,write,close.stat,lseek
标准I/O函数(理解流:一个指向file类型的结构的指针,该结构类似于RIO中的包含缓冲区的那个结构):fopen,fdopen,fread,fwrite,fscanf,fprintf,sscanf,sprintf,fgets,fputs.fflush,fseek,fclose
书中使用的RIO函数:rio_readn,rio_written,rio_readinitb,rio_readlineb,rio_readnb

上一篇:axure rp 9 Mac


下一篇:题解】[CF718C Sasha and Array]