Unix/Linux编程实践教程阅读笔记-who指令实现的优化-来自第二章P48-P54的笔记

在读它之前,需要先掌握who指令的基本实现原理,参考我之前的文章:

https://www.cnblogs.com/czw52460183/p/10999434.html

怎么优化它的实现?答案是利用缓冲区。

在讲述具体方法之前,需要补充一点缓冲区的前置知识:

  我们平时讲的缓冲区,基本指的是进程缓冲区,它位于用户空间,但其实,为了提高效率,内核也会使用缓冲区,这就是内核缓冲区。

  当进程要从磁盘读取数据时,按照之前的讲法,就会直接进行系统调用,切换到内核态并从磁盘读取数据。但其实内核一般不直接读磁盘,而是将内核缓冲区的数据复制到进程的缓冲区中

  当进程请求的数据不在内核缓冲区时,内核把对应的数据块加入到请求列表中,随后挂起该进程,并为其它进程服务。很短时间过去后,内核把对应的数据块从磁盘读到内核缓冲区,随后把数据从内核缓冲区复制到进程缓冲区,并唤醒被挂起的进程。

  之前在CSAPP笔记的第九章中(链接:https://www.cnblogs.com/czw52460183/p/10865447.html),我们展示过虚拟地址空间的结构图。

  那么这个内核缓冲区位于虚拟地址空间的哪个部分呢?堆区?栈区?

  我并没有查到相关资料描述其具体位置,但我的推测是:内核缓冲区属于内核,那它应该是存在于进程虚拟地址空间中属于内核的那一部分。还记得吗?之前说过每个进程虚拟地址空间的最上方,就是栈的上方,存放的是内核常驻与内存的部分,里面有内核栈的,我认为这个内核缓冲区对应的是这块地方。当然,只是猜测,如果以后发现有不对的地方,我再来纠正。

  当内核缓冲区的数据积累够一定数量后,才会一次写入磁盘,在这期间万一断电了,数据会丢失。

  附上一份内核缓冲区的参考文章:https://www.zhihu.com/question/30868347?sort=created

  

  现在介绍下进程缓冲区,即用户缓冲区,在https://www.cnblogs.com/czw52460183/p/10999434.html中,我们提到过,fgetc函数的缓冲是基于用户空间的,其实它就是用了用户缓冲区作了优化比如用fgetc读一个字节,fgetc有可能从内核中预读1024个字节到I/O缓冲区中,再返回第一个字节,这时该文件在内核中记录的读写位置是1024,而在FILE结构体中记录的读写位置是1。

  而之前我们提到read函数是底层的系统调用,它的缓冲是基于内核空间的,相当于它有内核缓冲区,但它没有用户空间的缓冲机制。

  进一步了解可参考文章:https://www.cnblogs.com/NeilHappy/archive/2013/03/12/2955552.html

  

  在之前的文章中(https://www.cnblogs.com/czw52460183/p/10999434.html),我们在实现who指令时发现Mac下对读取utmpx作了封装,直接读取该文件并解析会出现乱码。虽然不知道为什么它会做这样的封装,但我们可以推测一下:我认为它可能是为了通过封装实现一个基于用户缓冲的优化。

  由于read没有用户缓冲机制,因此直接用read读取utmpx需要对结构体数组中的每一个结构体单独读取,我们可以在用户空间构造一片缓冲区,每次read从内核缓冲区读取多个utmpx结构体,存放在用户缓冲区中,随后封装一个方法,为外部提供下一个结构体,当数据被取完,则再次调用read。

  

  由于Mac下极有可能已经对读取utmpx作了封装,所以我们无法验证这个优化缓冲机制了,这里给一下书上的代码吧,我也懒得装Linux去尝试了,有条件的自己试吧。

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <utmp.h>

//缓冲区参数
#define NRECS 16
#define UTSIZE (sizeof(struct utmp))
//结构化的返回参数
#define NULLUT ((struct utmp*)NULL)

//用户缓冲区
static char utmpbuf[NRECS * UTSIZE];
//缓冲区实际存储的utmp结构体数量
static int num_recs;
//缓冲区中下一个要读取的utmp结构体序号
static int cur_rec;
//文件描述符
static int fd_utmp;

//打开文件并初始化读取序号
int utmp_open(char *filename)
{
    fd_utmp = open(filename,O_RDONLY);
    cur_rec = num_recs = 0;
    return fd_utmp;
}

//加载下一批结构体到缓冲区,并返回实际加载的数量
int utmp_reload()
{
    //实际读取字节数
    int amt_read;
    amt_read = read(fd_utmp,utmpbuf,NRECS*UTSIZE);
    //更新实际读取到的结构体数量
    num_recs = amt_read/UTSIZE;
    //重置当前要读取的结构体序号
    cur_rec = 0;
    return num_recs;
}


//获取下一个utmp结构体
struct utmp *utmp_next()
{
    struct utmp *recp;
    //打开文件出错
    if(fd_utmp == -1)
    {
        return NULLUT;
    }
    //当缓冲区读完且加载不到新结构体时,返回空
    if(cur_rec == num_recs && utmp_reload() == 0)
    {
        return NULLUT;
    }
    //从缓冲区获取下一个结构体
    recp = (struct utmp *) &utmpbuf[cur_rec * UTSIZE];
    //更新下一个要读取结构体序号
    cur_rec++;
    return recp;
}

//关闭文件
void utmp_close()
{
    //只有文件被打开成功才需要关闭
    if(fd_utmp != -1)
    {
        close(fd_utmp);
    }
}

  这个文件实现了对获取下一个utmp结构体的缓冲区优化封装,配合上一章写的main函数使用,稍微修改下即可,main函数就不贴出来了。

 

posted on 2019-06-13 10:16  暴躁法师  阅读(167)  评论(0编辑  收藏  举报