20155224 《信息安全系统设计基础》第十三周学习总结
第十章
课上内容
-
who
命令:显示目前登陆的用户信息(who=who am I)
1. 打开utmp文件 open(2)
2. 用循环读纪录,打印…… read(2)
3. 关闭 close(2) -
man
命令:查看帮助man 3
——函数man 2
——系统调用man 1
——命令
man -k xxx
,查找功能man -k xxx | grep xxx
,精确查找
-
vi xxx
,若显示乱码则说明为二进制文件 -
umtp文件,保存登录信息
-
grep -nr
,搜索目录中所有文件(包括行号)n
显示行号r
递归调用
10.1 Unix I/O
- UnixI/O
-
输入/输出是在主存和外部设备(如磁盘驱动器、终端和网络)之间拷贝数据的过程。输入操作时从I/O设备拷贝数据到主存,而输出操作时从主存拷贝数据到I/O设备
-
所有的I/O设备,如网络、磁盘盒终端,都被模型化为文件,而所有的输入和输出都被当做对相应的文件的读和写来执行。这是一种应用接口,称为Unix I/O
-
每个unix文件都是一个m字节的序列;所有I/O设备如网络、磁盘和终端都被模型化为文件,而输入和输出就是对这些文件的读写操作。
-
unix系统中输入输出的操作:
- 打开文件:一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备,内核返回一个小的非负整数,叫做描述符。unix系统创建每个进程的时候都有三个打开的文件:标准输入;标准输出,标准错误。
- 改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k(从文件开头起始的字节偏移量)。
- 读写文件。读操作就是从文件拷贝n>0个字节到存储器,从当前文件位置k开始,然后将k增加到k+n。
- 关闭文件。应用通知内核关闭这个文件;作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池当中。
把I/O抽象成文件,其实是把系统内的一切操作都变成对文件(字节序列)的操作;这样极大地简洁了各类动作。上述对文件的描述,其实是输入输出类型的“文件操作”,分别对应的是进行I/O、读写操作。
10.3 打开和关闭文件
- 打开文件:
fd = Open("文件名",flag参数,mode参数);
-
mode参数指定新文件的访问权限位。作为上下文的一部分,每个进程都有一个umask;当进程通过带某个带mode参数的open函数用来创建一个新文件的时候,文件的访问权限位被设置为mode & ~umask。
-
fd是返回的文件描述符(数字),总是返回在进程中当前没有打开的最小描述符。
-
open函数将filename转换成一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。
-
flag参数:
O_RDONLY:只读。
O_WRONLY:只写。
O_RDWR:可读可写。
//一位或者多位掩码的或
O_CREAT,表示如果文件不存在,就创建它的一个截断的文件。
O_TRUNC:如果文件已经存在,就截断它。
O_APPEND:在每次写操作前,设置文件位置到文件的结尾处。
- 关闭文件:
int close(int fd);//若成功则返回0,不成功则为-1
10.4 读和写文件
-
应用程序是通过分别调用read和write函数来执行输入和输出的。
-
read函数:
ssize_t read(int fd,void *buf,size_t n);//成功则返回n;EOF返回0;出错返回-1
从描述符为fd的当前文件位置拷贝最多n个字节到存储器位置buf。返回值:-1:一个错误;0:EOF;否则,返回值:实际传送的字节数量。
- write函数:
ssize_t write(int fd,const void *buf,size_t n);
从存储器位置buf拷贝至多n个字节到描述符fd的当前文件位置。
- 不足值:在某些情况下,read和write传送的字节比应用程序要求的要少
- 原因:
- 读时遇到EOF
- 从终端读文本行
- 读和写网络套接字
- 原因:
10.5 用RIO包健壮地读写
-
实质:I/O包
-
两种函数:无缓冲的输入输出函数、带缓冲的输入函数(线程安全)
-
RIO的无缓冲的输入输出函数
-
应用程序通过调用rioreadn和riowritten函数可以在存储器和文件之间直接传送数据。
ssize_t rio_readn(int fd,void *usrbuf,size_t n);
ssize_t rio_writen(int fd,void *usrbuf,size_t n);
//rio_ readn函数在遇到EOF时,只能返回一个不足值;
//rio_ writen函数绝不会返回不足值。
- RIO读程序的核心——rio_read函数
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));//调用read函数填满缓冲区
if(rp->rio_cnt<0)//排除文件读不出数据的情况
{
if(error != EINTR)
{
return -1;
}
}
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;//以上三步,将n与rp->rio_cnt中较小的值赋给cnt
memcpy(usrbuf,rp->rio_bufptr,cnt);把读缓冲区的内容拷贝到用户缓冲区
rp->rio_bufptr+=cnt;
rp->rio_cnt-=cnt;
return cnt;
}
10.6 读取文件元数据
-
应用程序能够通过调用stat和fstat函数,检索到关于文件的信息(元数据)。
-
函数格式:
#include <unistd.h>
#include <sys/stat.h>
int stat(cost char *filename,struc sta *buf);
int fstat(int fd,struct stat *buf);
-
st_size成员包含了文件的字节数大小
-
st_mode成员编码了文件访问许可位和文件类型
-
Unix提供的宏指令根据st_mode成员来确定文件的类型
- 宏指令:S_ISREG() 普通文件?二进制或文本数据
- 宏指令:S_ISDIR() 目录文件?包含其他文件的信息
- 宏指令:S_ISSOCK() 网络套接字?通过网络和其他进程通信的文件
10.8 共享文件
-
内核用三个相关的数据结构来表示打开的文件:
- 描述符表:每个打开的描述符表项指向文件表中的一个表项
- 文件表:所有进程共享这张表,每个表项包括文件位置,引用计数,以及一个指向v-node表对应表项的指针
- v-node表:所有进程共享这张表,包含stat结构中的大多数信息
-
三种打开文件的类型:
- 典型:描述符各自引用不同的文件,没有共享
- 共享:多个描述符通过不同的文件表表项引用同一个文件。(关键思想:每个描述符都有自己的文件位置,对不同描述符的读操作可以从文件的不同位置获取数据)
- 继承:子进程继承父进程打开文件。调用fork后,子进程有一个父进程描述符表的副本,父子进程共享相同的打开文件表集合,因此共享相同的文件位置
10.10 标准I/O
-
应用程序可以通过open、close、lseek、read、write和stat这样的函数来访问Unix I/O。
-
RIO函数:read和write的健壮的包装函数,自动处理不足值,为读文本行提供一种高效的带缓冲的方法。
-
标准I/O函数:提供了Unix I/O函数的一个更加完整的带缓冲的替代品,包括格式化的I/O例程。是磁盘和终端设备I/O之选。
-
套接字描述符:Unix对网络的抽象是一种称为套接字的文件类型,被称为套接字描述符。应用进程通过读写套接字描述符来与运行在其他计算机上的进程通信。
-
对流I/O限制:
- 跟在输出函数之后的输入函数,必须在其中间插入fflush、fseek、fsetpos或者rewind函数,后三个函数使用Unix I/O中的lseek函数来重置当前的文件位置。
- 跟在输入函数之后的输出函数,必须在中间插入fseek、fsetpos或者rewind的调用,一个输出函数不能跟随在一个输入函数之后,除非该输入函数遇到了一个EOF。
-
解决对流I/O限制的方法是:
- 采用在每个输入操作前刷新缓存区这样的规则来满足。
- 对同一个打开的套接字描述符打开两个流,一个用来读,一个用来写。
十二章相关扩展内容
-
每个网络应用都是基于客户端——服务器模型的。一个应用是由一个服务器进程和多个客户端进程组成。服务端管理某种资源,并通过操作这种资源来为它的客户端提供某种服务
-
模型中的基本操作是事务,由四步构成的:
- 客户端发送请求
- 服务器处理请求
- 服务器发送响应
- 客户端处理响应
-
网络
- 对于主机而言,网络是一种I/O设备,一个插到I/O总线扩展槽的适配器提供了到网络的物理接口。从网络上接收到的数据从适配器经过I/O和存储器总线拷贝到存储器
- 物理上而言,网络是一个按照地理远近组成别的层次系统。最低层是LAN(局域网),最流行的局域网技术是以太网;集线器不加分辨地将从一个端口上收到的每个位复制到其他所有的端口上;多个不兼容的局域网可以通过路由器连接起来
-
套接字结构地址:sockaddr、sockaddr_in
-
并发:逻辑控制流在时间上重叠
-
并发程序:使用应用级并发的应用程序称为并发程序
-
三种基本的构造并发程序的方法
- 进程:用内核来调用和维护,有独立的虚拟地址空间,显式的进程间通信机制
- I/O多路复用,应用程序在一个进程的上下文中显式的调度控制流。逻辑流被模型化为状态机。
- 线程,运行在一个单一进程上下文中的逻辑流。由内核进行调度,共享同一个虚拟地址空间。
-
常用构造并发进程的函数
- fork
- exec
- waitpid
-
基于进程的并发编程
- 服务器派生子进程来服务客户端:在接受连接请求之后,服务器派生出一个子进程,这个子进程获得服务器描述表完整的拷贝。子进程关闭它的拷贝中监听描述符listenfd,父进程关闭它的已连接描述符connfd的拷贝,因为不需要这些描述符了。进程能够共享文件表,但不共享用户地址空间。
-
基于I/O多路复用的并发编程
- 服务器必须响应两个互相独立的I/O事件:1)网络客户端发起的连接请求 2)用户在键盘上键入的命令 ,解决的办法是I/O多路复用技术:使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。
-
基于I/O多路复用的并发事件
- 驱动服务器I/O多路复用可以用做并发事件驱动程序的基础,在事件驱动程序中,流是因为某种事件而前进的,一般概念是把逻辑流模型化为状态机。一个状态机就是一组状态、输入事件和转移。
-
基于线程的并发编程
- 线程运行在进程上下文中的逻辑流。线程由内核自动调度,每个线程都有它自己的线程上下文。
- 线程执行模型。多线程的执行模型在某些方面和多进程的执行模型相似。每个进程开始生命周期时都是单一线程,这个线程称为主线程。在某一时刻,主线程创建一个对等线程,从在此刻开始,两个线程就并发地运行。
- Posix线程
-
信号量:
- 是用信号量解决同步问题,信号量s是具有非负整数值的全局变量,有两种特殊的操作来处理(P和V):
- P(s):如果s非零,那么P将s减1,并且立即返回。如果s为0,那么就挂起这个线程,直到s变为非零
- V(s):V操作将s加1
-
共享资源 利用信号量调度共享资源:在这种场景中,一个线程用信号量操作来通知另一个线程,程序状态中的某个条件已经为真了:
- 生产者——消费者问题
- 读者——写者问题
课后习题答案
-
10.6
fd = 4
-
10.7
#include <stdio.h>
#include "csapp.h"
int main(int argc, char* argv[]) {
int n;
char buf[MAXBUF];
while ((n = Rio_readn(STDIN_FILENO, buf, MAXBUF)) != 0)
Rio_writen(STDOUT_FILENO, buf, n);
return 0;
}
- 10.8
#include <stdio.h>
#include "csapp.h"
int main(int argc, char* argv[]) {
struct stat stat;
char *type, *readok;
int fd;
if (argc <= 1)
fd = 0; // stdin
else
fd = atoi(argv[1]);
Fstat(fd, &stat);
if (S_ISREG(stat.st_mode))
type = "regular";
else if (S_ISDIR(stat.st_mode))
type = "dir";
else
type = "other";
if ((stat.st_mode & S_IRUSR))
readok = "yes";
else
readok = "no";
printf("type: %s, read: %s
", type, readok);
return 0;
}
- 10.9
if (Fork() == 0) {
dup2(0, 3);
Execve("fstatcheck", argv, envp);
}
- 10.10
#include <stdio.h>
#include "csapp.h"
int main(int argc, char* argv[]) {
int n;
rio_t rio;
char buf[MAXLINE];
if (argc == 2) {
int fd = Open(argv[1], O_RDONLY, 0);
while ((n = Rio_readn(fd, buf, MAXBUF)) != 0)
Rio_writen(STDOUT_FILENO, buf, n);
exit(0);
}
Rio_readinitb(&rio, STDIN_FILENO);
while ((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0)
Rio_writen(STDOUT_FILENO, buf, n);
return 0;
}