系统级I/O

输入/输出(I/O)是在主存和外部设备间拷贝数据的过程。

UNIX I/O

所有的I/O设备,如网络、磁盘和终端,都被模型化为文件;

所有的输入/输出,都被当做对所有文件的读和写来执行。

【1】打开文件:

应用程序向内核发出打开文件的指令——>内核返回一个小的非负整数,即描述符 [内核记录有关这个打开文件的所有信息,应用程序只需要记住这个描述符]

Unix外壳创建的每个进程开始时都会打开三个文件,定义的头文件<unistd.h>:

标准输入		描述符0		STDIN_FILENO
标准输出		描述符1		STDOUT_FILENO
标准错误		描述符2		STDERR_FILENO

【2】改变当前文件位置

应用程序通过执行seek操作,显式设置文件当前位置k

【3】读写文件

读:从文件拷贝字节到存储器

"EOF":EOF在文件结尾处并没有明确标识。读操作时,若K(文件当前位置k)>m(字节文件的大小),会触发end-of-file(EOF),应用程序能检测到这个文件。

文件的操作

  • 打开文件open()

  • 关闭文件close()

  • 读文件read()

  • 写文件write()

  • 打开文件open()

【1】打开文件open()

(1)函数定义

#include <sys/types.h>
#include <sys/stat.h>
#include <fcnt1.h>
int open(char *filename,int flags,mode_t mode);

(2)函数解析

1)filename:文件名

2)flags:指明进程打算如何访问这个文件

  • O_RDONLY:只读

  • O_WRONLY:只写

  • O_RDWRR:可读可写

  • O_CREAT:如果文件不存在,就创建其截断的文件

  • O_TRUNC:如果文件已经存在,就截断它

  • O_APPEND:在每次写操作前,设置文件位置到文件结尾处

3)mode:指定新文件的访问权限位

掩码							描述
S_IRUSR					拥有者能读这个文件
S_IWUSR					拥有者能写这个文件
S_IXUSR					拥有者能执行这个文件

S_IRGRP					拥有者所在组成员能读这个文件
S_IWGRP					拥有者所在组成员能写这个文件
S_IXGRP					拥有者所在组成员能执行这个文件

S_IROTH					其他人(所有人)能读这个文件
S_IWOTH					其他人(所有人)能写这个文件
S_IXOTH					其他人(所有人)能执行这个文件

4)返回值:若成功则返回值为新文件的描述符,若失败则返回值为-1

【2】关闭文件close()

(1)函数定义

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

(2)函数解析

fd:文件描述符
返回值:若成功则返回值为0,若失败则返回值为-1

【3】读文件read()

(1)函数定义

#include <unistd.h>
ssize_t read(int fd,void *buf,size_t n);

read函数从描述符为fd的当前位置拷贝最多n个字节到存储器位置buf。

(2)函数解析

返回值:若成功则返回值为读的字节数,若EOF返回值为0,若出错返回值为-1.

【4】写文件write()

#include <unistd.h>
ssize_t write(int fd,const void *buf,size_t n);

write函数从存储器位置buf拷贝至多n个字节到描述符fd的当前文件位置

不足值

出现原因:

  • 读时遇到EOF
  • 从终端读文本行
  • 读和写网络套接字

用RIO包健壮地读写

RIO(Robust I/O)会自动处理上述不足值。

RIO提供了两类不同的函数:

【1】RIO的无缓冲的输入输出函数

这些函数直接在存储器和文件之间传送数据,没有应用级缓冲。

函数定义:

#include "csapp.h"
ssize_t rio_readn(int fd,void *usrbuf,size_t n);
ssize_t rio_writen(int fd,void *usrbuf,size_t n);

参数:

fd:文件描述符
usrbuf:存储器位置
n:传送的字节数
返回值:
	rio_readn函数若成功则返回传送的字节数,若EOF返回值为0(一个不足值),若出错则为-1.
	rio_writen函数若成功则返回传送的字节数,若出错则为-1,没有不足值

【2】RIO的带缓冲的输入函数

高效的从文件中读取文本行和二进制数据

文本行:由换行符结尾的ASCII码字符序列

范例:计算文本文件中文本行的数量所用到的函数

#include "csapp.h"
void rio_readinitb(rio_t *rp,int fd);将描述符fd和地址rp处的一个类型为rio_t读缓冲区联系起来。
ssize_t rio_readlineb(rio_t *rp,void *usrbuf,size_t maxlen);从读缓冲区rp读出一个文本行
(包括结尾的换行符"\n"),将它拷贝到存储器位置usrbuf,并用空(零)字符来结束这个文本行,当缓
冲区变空时,这个函数会自动调用read函数从文件描述符fd读取文件,重新填满缓冲区。rio_readlineb函数最多读maxlen-1个字节,余下的一个字节留给结尾的空字符。
ssize_t rio_readnb(rio_t *rp,void *usrbuf,size_t n);从rp最多读n个字节到存储器位置usrbuf

返回值:成功则为读的字节数,若EOF则为0,若出错则为-1.

RIO函数使用示例:

以下代码展示了如何使用RIO函数来一次一行地从标准输入拷贝一个文本文件到标准输出:

#include "csapp.h"
int main(int argc,char **argv)
{
	int n;
	rio_t rio;
	char buf[MAXLINE];

	Rio_readinitb(&rio,STDIN_FILEON); 将标准输入连接到缓冲区地址rio
	while((n = Rio_readlineb(&rio,buf,MAXLINE)) =! 0) 当成功返回时,将rio中的内容拷贝到buf中,最多拷贝MAXLINE-1个字节
		Rio_written(STDOUT_FILEON,buf,n);将buf中的内容拷贝到标准输出
}

首先将标准输入连接到缓冲区地址rio,然后将rio中的内容拷贝到buf中,最后将buf中的内容拷贝到标准输出

一个类型为rio_t的读缓冲区和初始化它的rio _ readinitb函数:

#define RIO_BUFSIZE 8192
typedef struct{
	int rio_fd;
	int rio_cnt;
	char *rio_bufptr;
	char rio_buf[RIO_BUFSIZE];
}rio_t;

从rio_t的结构体定义可以看出,它的结构体中定义了`内部缓冲区的描述符` 、`没有读的字节`、`下一个要读的字节`、`缓冲区`

void rio_readinitb(rio_t *rp,int fd)
{
	rp->rio_fd = fd;将fd值赋给rp所指向的结构体成员rio_fd
	rp->rio_cnt = 0;将0赋给rp所指向的结构体成员rio_cnt
	rp->rio_bufptr = rp->rio_buf;使结构体成员rio_bufptr指针指向rio_buf缓冲区
}

读取文件元数据

元数据即关于文件的信息,需要用到的函数是stat和fstat.函数定义如下:

#include <unistd.h>
#include <sys/stat.h>

int stat(const char *filename,struct stat *buf);
int fstat(int fd,struct stat *buf);
若成功则返回0,若出错则返回-1.

stat(取得文件状态),函数stat()用来将参数file_name所指的文件状态,复制到参数buf所指的结构中,stat函数以文件名作为输入;fstat(由文件描述词取得文件状态)

struct stat结构体简介

使用这个结构体时,需要使用<sys/types.h>和<sys/stat.h>这两个头文件,struct stat内容参数说明:

struct stat
{
dev_t st_dev;设备编号
ino_t st_ino;文件的i-node
mode_t st_mode;文件的类型和存取的权限
nlink_t st_nlink;连接到该文件的硬连接数目,刚建立的文件值为1
uid_t st_uid;文件所有者的用户识别码
gid_t st_gid;文件所有者的组识别码
dev_t st_rdev;Device type(if inode device)
off_t st_size;文件大小,以字节计算
unsigned long st_blksize;系统I/O的缓冲区大小
unsigned long st_blocks;占用文件区块的个数
time_t st_atime;文件最近一次被存取或被执行的时间
time_t st_mtime;文件最后一次被修改的时间
time_t st_ctime;i-node最近一次被更改的时间
}

st _ size和st _ mode要重点掌握。

st_size:包含了文件的字节数大小

st_mode:编码了文件访问许可位和文件类型。许可位在最开始提到过,unix文件类型如下,并有对应的宏指令:

普通文件  二进制或文本数据(对内核没区别)  	S_ISREG()   这是一个普通文件吗

目录文件   关于其他文件的信息 			     S_ISDIR()	这是一个目录文件吗

套接字	   通过网络与其他进程通信的文件		  S_ISSOCK()	这是一个网络套接字吗

查询和处理一个文件的st_mode位:

#include "csapp.h"

int main (int argc, char **argv) 
{
	struct stat stat;
	char *type, *readok;

    Stat(argv[1], &stat);//取得stat1的文件状态,存入stat结构体中
    if (S_ISREG(stat.st_mode))     /* 如果是一个文本文件 */
	type = "regular";
    else if (S_ISDIR(stat.st_mode))//如果是一个目录文件
	type = "directory";
    else 
	type = "other";
    if ((stat.st_mode & S_IRUSR)) /* 检查拥有者是否具有阅读权限 */
	readok = "yes";
    else
	readok = "no";

    printf("type: %s, read: %s\n", type, readok);
    exit(0);
}

共享文件

内核用三个相关的数据结构来表示打开的文件:

  • 描述符表
  • 文件表
  • v-node表

内核中,对应于每个进程都有一个独立的文件描述符表,表示这个进程打开的所有文件。文件描述符表中每一项都是一个指针,指向一个用于描述打卡的文件的数据块——文件表,所有进程共享这张表文件表的表项包括当前的文件位置、引用计数(即当前指向该表项的描述符表项数)、以及一个指向v—node表中对应表项的指针,所有进程共享v-node表,每个表项包含stat结构中的大多数信息。

附上几张图,有助于理解:



代码实践:

footbar.txt由6个ASCII码描述符“footbar组成”,判断最后的输出。

#include "csapp.h"
int main()
{
	int fd1,fd2;
	char c;
	fd1 = open("footbar.txt",O_RDONLY,0)
	fd2 = open("footbar.txt",O_rdonly,0)
	read("fd1",&c,1);
	read("fd2",&c,1);
	printf("c = %c\n",c);
	exit(0);
}

描述符fd1和fd2都有各自的文件描述符表,文件表及i-node表,所以读取文件时各自独立的,最后输出f.

#include "csapp.h"
int main()
{
	int fd;
	char c;
	fd = open("footbar.txt",O_RDONLY,0);
	if(fork() == 0)
	{
		read(fd,&c,1);
		printf("c = %c\n",c);
		exit(0);
	}
	wait(NULL);
	read(fd,&c,1);
	printf("c = %c\n",c);
	exit(0);

}

fork是子程序,和父程序共享同一个描述符表、文件表、v-node表,指向同一个打开文件表表项,子进程读取文件的第一个字节时,文件位置加1,父进程会读取第二个字节,最后输出o.

I/O重定向

 unix > ls >foot.txt

使外壳加载并执行ls程序,将标准输出重定向到磁盘文件foot.txt

重定向函数:

#include <unistd.h>

int dup2(int oldfd,int newfd);

返回:成功返回非负的描述符,出错返回-1

函数解析:dup2函数拷贝描述符表表项oldfd,覆盖描述符表表项newfd,如果后者被打开,则在拷贝前关闭它。

标准I/O

ANSI定义了一组高级输入输出函数,称为标准I/O库,包括:

  • fopen、fclose,打开和关闭文件
  • fread、frite,读和写字节的函数
  • fgets、fputs,读和写字符串
  • scanf、printf,复杂格式化的I/O函数

标准I/O库将一个打开的文件模型化为一个流,对程序员而言,一个流就是一个指向FILE类型的结构的指针。

每个ASCII程序开始时都有三个打开的流stdin、stdout、stderr,分别对应于标准输入、标准输出、标准错误,定义如下:

#include <stdio.h>

extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;

套接字

大多数C程序员在他们的职业生涯中之使用标准I/O,而从不涉及低级Unix I/O函数

Unix对网络的抽象是一种称为套接字的文件类型。套接字也是用文件描述符来引用的,称为“套接字描述符”,应用程序通过读写套接字描述符来与运行在其他计算机上的进程通信。

参考博客

体会

这篇博客,是主要参考了闫佳歆同学的博客写的,最大的体会是觉得自己以前的读书学习太流于表面,没有自己的总结和体会,以后读书不仅要系统更要细化,才能真正有收获!

posted @ 2015-11-16 13:04  adacn  阅读(255)  评论(1编辑  收藏  举报