csapp:系统级IO

IO子系统层次结构

从用户I/O软件切换到内核I/O软件的唯一 办法是“异常”机制:系统调用(自陷)

大部分I/O软件都属于操作系统内核态程序,最初的I/O请求在用户程序中提出。

OS在I/O子系统中的重要性由I/O系统以下三个特性决定

  • 共享性。I/O系统被多个程序共享,须由OS对I/O资源统一调度管理,以保证用户程序只能访问自己有权访问的那部分I/O设备。
  • 复杂性。I/O设备控制细节复杂,需OS提供专门的驱动程序进行控制,这样可对用户程序屏蔽设备控制的细节。
  • 异步性。不同设备之间速度相差较大,因而,I/O设备与主机之间的信息交换使用异步的中断I/O方式,中断导致从用户态向内核态转移,因此必须由OS提供中断服务程序来处理。

文件

记住:LInux中一切皆文件。

根据文件的可读性,文件分成两类:
  1. ASCII文件。ASCII文件也称文本文件,可由多个正文行组成,每行以换行符‘\n’ 结束
  2. 二进制文件。
  • 标准输入和标准(错误)输出文件是ASCII文件。
  • 普通文件可能是文本文件或二进制文件。
  • 不同操作系统判断一行结束的符号不同。Linux & Mac OS: \n(0xa),Windows & 网络协议: \r\n (0xd 0xa)
  • 打开文件:int open(char *name, int flags, mode_t perms);

文件描述符 file descriptor

从当前未使用的最小的分配(3开始)

打开/关闭文件

int open(char *name, int flags, mode_t perms); /*打开文件*/
//e.g. fd=open(“test.txt”,O_RDONLY, 0);

/*
参数flags:O_RDONLY, O_WRONLY|O_APPEND, O_RDWR等
参数perms:用于指定文件的访问权限,通常在open函数中该参数总是0,除非以创建方式打开,此时,参数flags中应带有O_CREAT标志。
return:文件描述符
*/
  1. 标准输入(fd=0)、标准输出(fd=1)和标准错误(fd=2)三种文件在由shell创建进程时默认打开,其他文件须用creat或open函数显式创建或打开后才能读写

Example

int fd; // 文件描述符 file descriptor

if ((fd = open("/etc/hosts", O_RDONLY)) < 0)
{
    perror("open");
    exit(1);
}

int fd;     // 文件描述符
int retval; // 返回值

int ((retval = close(fd)) < 0)//如果在此关闭已经关闭了的文件,会出大问题。所以一定要检查返回值
{
    perror("close");
    exit(1);
}

读写文件

ssize_t read(int fd, void *buf, size_t n);/*从文件中读取n个元素到buf中,返回读取字节数*/
ssize_t write(int fd, const void * buf, size_t n);/*从buf中写n个文件到fd标识的文件*/

/*size_t 和 ssize_t 分别是 unsigned int 和 int,因为返回值可能是-1。*/

Example

char buf[512];
int fd;
int nbytes;

/* 打开文件描述符,并从中读取 512 字节的数据,实际上就是把文件中对应的字节复制到内存中
返回值是读取的字节数量,是一个 ssize_t 类型(其实就是一个有符号整型),如果 nbytes < 0 那么表示出错。nbytes < sizeof(buf) 
这种情况(short counts) 是可能发生的,而且并不是错误。*/
if ((nbytes = read(fd, buf, sizeof(buf))) < 0)
{
    perror("read");
    exit(1);
}

设置读写位置

long lseek(int fd, long offset, int origin);

/*offset指出相对字节数
origin指出基准:开头(0)、当前位置(1)和末尾(2)
返回的是位置值,若发生错误,则返回-1
例:lseek(fd,5L,0);表示定位到文件开始后的第5字节
lseek(fd, 0L, 2);表示定位到文件末尾*/

重定向

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

  • 描述符表。每个进程有独立的描述符表每个描述符表项指向文件表一个表项
  • 文件表。所有进程共享。
  • v-node表

  • 每个描述符都有他自己的文件位置,所以可以两个打开的文件v-node一样,但其实是打开了两个(操作独立)。
  • 父子进程指向同一个打开表表项(即是对同一个打开文件操作)

dup2(int old,int new)

复制描述符表项old到new,则new与old指向同一个文件,对文件操作同步

链接命令

gcc csapp.h csapp.c io.c -lpthread -o io

Example1:

打开同一个文件:

#include "csapp.h"

int main(int argc, char *argv[])
{
    int fd1, fd2, fd3;
    char c1, c2, c3;
    char *fname = argv[1];
    fd1 = Open(fname, O_RDONLY, 0);
    fd2 = Open(fname, O_RDONLY, 0);
    fd3 = Open(fname, O_RDONLY, 0);
    dup2(fd2, fd3);

    Read(fd1, &c1, 1);
    Read(fd2, &c2, 1);
    Read(fd3, &c3, 1);
    printf("c1 = %c, c2 = %c, c3 = %c\n", c1, c2, c3);

    Close(fd1);
    Close(fd2);
    Close(fd3);
    return 0;
}

/*
c1=a,c2=a,c3=b
*/

 虽然打开的是同一个文件,但fd1、fd2、fd3各不相同,指向的文件表各不相同。第一个Read,从fd1指向文件头处读取1个字节给c1,第二个Read,从fd2指向文件头处读取1个字节给c2,此时光标后移一位。dup重定位后fd3指向了fd2指向的文件表,所以这时的Read是接着刚刚对fd2文件的操作,从第一个字节之后再读一个字节赋给c3

Example2

父子进程

#include "csapp.h"

int main(int argc, char *argv[])
{
    int fd1;
    int s = getpid() & 0x1;
    char c1, c2;
    char *fname = argv[1];
    fd1 = Open(fname, O_RDONLY, 0);
    Read(fd1, &c1, 1);

    if (fork()) {
	/* Parent */
	sleep(s);
	Read(fd1, &c2, 1);
	printf("Parent: c1 = %c, c2 = %c\n", c1, c2);
    } 
    else {
	/* Child */
	sleep(1-s);
	Read(fd1, &c2, 1);
	printf("Child: c1 = %c, c2 = %c\n", c1, c2);
    }
    return 0;
}
/*
Parent: c1 = a, c2 = b
qian64@qian64-VirtualBox:~/csapp$ Child: c1 = a, c2 = c
*/

Example3

写重定向

#include "csapp.h"

int main(int argc, char *argv[])
{
    int fd1, fd2, fd3;
    char *fname = argv[1];
    fd1 = Open(fname, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR);
    Write(fd1, "pqrs", 4);	

    fd3 = Open(fname, O_APPEND|O_WRONLY, 0);
    Write(fd3, "jklmn", 5);
    fd2 = dup(fd1);  /* Allocates new descriptor */
    Write(fd2, "wxyz", 4);
    Write(fd3, "ef", 2);

    Close(fd1);
    Close(fd2);
    Close(fd3);
    return 0;
}

/*abcde.txt
pqrswxyznef
*/

开始对fd1 Write(fd1, "pqrs", 4)操作后文件为:pqrs

由于fd3设置O_APPEND,所以Write(fd3, "jklmn", 5)之后文件为pqrsjklmn

fd2重定向到fd1指向的文件表,而fd1当前光标在第5个位置,所以Write(fd2, "wxyz", 4);后变为:pqrswxyzn

之后Write(fd3, "ef", 2);追加两个字符后为:pqrswxyznef

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值