进程缓冲区
我们慢慢来说明为什么要使用缓冲:
- 只有工作在管理员模式下,即只有内核代码才可以访问磁盘、终端、打印机等设备,还可以访问全部的内存。而用户模式下只访问特定区域的内存空间。
- 系统调用就是要从用户空间陷入内核空间,进入管理员模式。系统调用是很花时间的,原因:(1)执行权从用户代码转移到内核代码本身要进行数据传输;(2)管理员模式对应特殊的堆栈和内存环境,系统调用前必须建立好,系统调用后又要把堆栈和内存环境恢复成用户程序运行时的状态,这种切换需要消耗很多时间。
- read()和write()要访问磁盘,它们是系统调用,为节约时间应尽量减少read()和write()的调用次数,为达到此目的就要增大文件缓冲。
下面的代码是自己实现使用缓冲区的who命令实现。
#include<stdio.h>
#include<utmp.h>
#include<string.h>
#include<stdlib.h>
#include<time.h>
#include<sys/types.h>
#include<fcntl.h>
#define BUFSIZE 16 //缓冲区中存放16条记录
#define NULLUT ((struct utmp*)NULL) //定义一个空指针,不指向任何实际的对象或函数
#define UTSIZE (sizeof(struct utmp))
#define SHOWHOST
int my_utmp_open(char *filename);
struct utmp* my_utmp_next();
int my_utmp_reload();
void my_utmp_close();
void show_info(struct utmp*);
static char utmpbuf[BUFSIZE*UTSIZE]; //缓冲区大小
static int num_recs; //缓冲区中的数据个数
static int cur_rec; //缓冲区中已经使用的数据个数
static int fd_utmp=-1; //utmp文件的描述符
int my_utmp_open(char *filename){
fd_utmp=open(filename,O_RDONLY);
cur_rec=num_recs=0;
return fd_utmp;
}
struct utmp* my_utmp_next(){
struct utmp *result;
if(fd_utmp==-1) //文件打开失败
return NULLUT;
if(cur_rec==num_recs && my_utmp_reload()==0) //缓冲区已读完,就从文件向缓冲区reload新内容。如果从文件中已读不出更多的内容,则返回空指针
return NULLUT;
result=(struct utmp*)&utmpbuf[cur_rec*UTSIZE];
cur_rec++;
return result;
}
int my_utmp_reload(){
int num=read(fd_utmp,utmpbuf,BUFSIZE*UTSIZE); //最多读取16条记录
num_recs=num/UTSIZE; //实际读取的记录数
cur_rec=0;
return num_recs;
}
void my_utmp_close(){
if(fd_utmp!=-1)
close(fd_utmp);
}
int main(){
struct utmp *record;
if(my_utmp_open("/var/run/utmp")==-1){ //打开文件
perror("fopen");
exit(0);
}
while((record=my_utmp_next())!=NULLUT){ //循环读取记录(结构体)
show_info(record);
}
my_utmp_close(); //关闭文件
return 0;
}
void show_info(struct utmp *buf){
if(buf->ut_type!=USER_PROCESS)
return;
printf("%-8s ",buf->ut_name); //输出用户名
printf("%-12s ",buf->ut_line); //终端设备名
time_t timelong=buf->ut_time;
struct tm *localnow=localtime(&timelong);
printf("%d-%d-%d %d:%d ",localnow->tm_year+1990,localnow->tm_mon+1,localnow->tm_mday,localnow->tm_hour,localnow->tm_min); //登录时间
#ifdef SHOWHOST
printf("(%s)",buf->ut_host);
#endif
printf("\n");
}
内核缓冲区
管理员模式和用户模式之间切换需要时间,相比之下,磁盘I/O消耗的时间更多,因此内核也利用缓冲技术来提高对磁盘的访问速度。
因此read函数并不是把数据直接从磁盘读到进程缓冲区,而是从内核缓冲区读到进程缓冲区;同样write也不会都导致内核的写磁盘操作,只有当内核缓冲区积累到一定量时才一次写入磁盘,有时突然断电,则内核缓冲区的数据会丢失。
当进程要求的数据块不在内核缓冲区时,内核把相应的数据加到请求数据列表中,然后挂起该进程,为其他进程服务。一段时间后(很短),内核把相应数据从磁盘读到内核缓冲区,然后再把数据复制到进程缓冲区,最后唤起被挂起的进程。