• 编写who命令:文件操作,缓冲区与联机帮助


    最近阅读UULP(Understanding Unix/Linux Programming),按照书中介绍对Unix/Linux系统编程进行学习梳理,总结如下。

    1. who命令能做什么

    who命令用于查看有谁在使用系统。

    执行who命令。

    who

    其输出格式如下:

    ustc   tty7    Sept  1   08:34  (xxx.yy.com用户登陆地址,有些版本没有)

    john   lft/0   Sept  1   08:34 

    其显示包含用户名,终端名,和登录时间。

    2. who命令是如何工作的

    想知道如何自己编写这样一个命令,首先当然是要了解who是如何工作的?最好的方法自然是先查看一下联机帮助(man)。

    man who

    查看输出结果,在描述部分有这样一句话:

      If FILE is not specified, use /var/run/utmp. /var/log/wtmp as FILE is common.

    所以who的实现,跟utmp/wtmp文件应该有很大的联系。

    继续对utmp查看联机帮助。

    man utmp

    果然在描述部分里有:

       The utmp file allows one to discover information about who is currently using the system.

    表明的确可以通过utmp查看谁在使用系统。

    进一步查看其数据结构,发现与我们who命令需要相关的用户名,终端名,和登录时间。

     char    ut_user[UT_NAMESIZE]; /* Username */
     char    ut_line[UT_LINESIZE]; /* Device name of tty - "/dev/" */
     char    ut_host[UT_HOSTSIZE]; /* Hostname for remote login, or
                                                    kernel version for run-level
     struct {
              int32_t tv_sec;           /* Seconds */
              int32_t tv_usec;          /* Microseconds */
     } ut_tv;                           /* Time entry was made */

    他们都在struct utmp中。

    3. 编写自己的who命令

    3.1 who1.c

    有了第二部分的了解之后,编写自己的who命令看起来就有眉目了,我们只需要利用unix文件操作,读取utmp文件中的相应项,并以固定格式输出在屏幕即可。

    基础的文件操作函数接口复习一下:

    #include <fcntl.h>
    int  open(  char  *filename,  int  access,  int  permission  );
    int  read(  int  handle,  void  *buffer,  int  nbyte );
    int  write(  int  handle,  void  *buffer,  int  nbyte  );
    int  close(  int  handle  );

    所以对相关项以固定格式输出,利用#SHOWHOST控制是否显示第四项(用户登录地址),得到第一个版本的who命令.

    who1.c

    代码:

     1 #include<stdio.h>
     2 #include<utmp.h>
     3 #include<fcntl.h>
     4 #include<unistd.h>
     5 #include<stdlib.h>
     6 #define SHOWHOST
     7 
     8 void show_info(struct utmp* utbufp) {
     9     printf("%8.8s", utbufp -> ut_user);
    10     printf(" ");
    11     printf("%8.8s", utbufp -> ut_line);
    12     printf(" ");
    13     printf("%d", utbufp -> ut_tv.tv_sec);
    14     printf(" ");
    15 #ifdef SHOWHOST
    16     printf("%s", utbufp -> ut_host);
    17 #endif
    18     printf("
    ");
    19 }
    20 
    21 int main() {
    22     struct utmp current_record;
    23     int utmpfd;
    24     int reclen = sizeof(current_record);
    25     if ((utmpfd = open(UTMP_FILE,O_RDONLY)) == -1) {
    26         perror(UTMP_FILE);
    27         exit(1);
    28     }
    29     while (read(utmpfd,&current_record,reclen) == reclen) {
    30         show_info(&current_record);
    31     }
    32     close(utmpfd);
    33     return 0;
    34 }

    查看一下输出:

    reboot     ~     1472787188 4.4.0-34-generic
    runlevel   ~      1472787197 4.4.0-34-generic
    LOGIN     tty1  1472787200
    ustc        tty7  1472787242 :0

    对比系统who命令输出,多了一些项,而且时间显示好像也不是可读的时间,所以需要进一步修改。

    3.2 who2.c

    who1.c的输出中包含了所有终端的信息,甚至是尚未使用的,同时还有LOGIN这样的控制台信息(不是真正的用户)。

    所以考虑如何去掉他们,继续查看utmp的联机帮助,在关于ut_type中有这样的定义。

    #define EMPTY         0 /* Record does not contain valid info
                                          (formerly known as UT_UNKNOWN on Linux) */
    #define RUN_LVL       1 /* Change in system run-level (see
                                          init(8)) */
    #define BOOT_TIME     2 /* Time of system boot (in ut_tv) */
    #define NEW_TIME      3 /* Time after system clock change
                                          (in ut_tv) */
    #define OLD_TIME      4 /* Time before system clock change
                                          (in ut_tv) */
    #define INIT_PROCESS  5 /* Process spawned by init(8) */
    #define LOGIN_PROCESS 6 /* Session leader process for user login */
    #define USER_PROCESS  7 /* Normal process */

    可以看出,ut_type的值为7时,表明其是一个user_process,所以加一句判断,即可消除其他行。

    代码:

    if (utbufp -> ut_type != USER_PROCESS) {
            return;
    }

    再看时间显示问题,unix有库函数ctime可以将time_t类型转换为人常用的时间形式。

    查看ctime的man文档,学习其使用方式:

    char *ctime(const time_t *timep);

    发现给定一个time_t类型(long int),即可转换为一个可读字符串,还是很简单的。

    注: 如果不知道ctime,也可以通过

    man time | grep transform之类的命令,查找内容中带有转换字样的与时间相关的命令,在显示结果中找到可能有用的,再细看其man文档,发现ctime。

    这样,把上述两部分总结起来,就可以得到第二个版本的who。

    who2.c

     1 #include<stdio.h>
     2 #include<utmp.h>
     3 #include<fcntl.h>
     4 #include<unistd.h>
     5 #include<stdlib.h>
     6 #include<time.h>
     7 #define SHOWHOST
     8 void show_time(long timeval) {
     9     char* cp;
    10     cp = ctime(&timeval);
    11     printf("%12.12s",cp + 4);
    12 }
    13 
    14 void show_info(struct utmp* utbufp) {
    15     if (utbufp -> ut_type != USER_PROCESS) {
    16         return;
    17     }
    18     printf("%-8.8s", utbufp -> ut_user);
    19     printf(" ");
    20     printf("%-8.8s", utbufp -> ut_line);
    21     printf(" ");
    22     show_time(utbufp -> ut_tv.tv_sec);
    23     printf(" ");
    24 #ifdef SHOWHOST
    25     printf("%s", utbufp -> ut_host);
    26 #endif
    27     printf("
    ");
    28 }
    29 
    30 int main() {
    31     struct utmp current_record;
    32     int utmpfd;
    33     int reclen = sizeof(current_record);
    34     if ((utmpfd = open(UTMP_FILE,O_RDONLY)) == -1) {
    35         perror(UTMP_FILE);
    36         exit(1);
    37     }
    38     while (read(utmpfd,&current_record,reclen) == reclen) {
    39         show_info(&current_record);
    40     }
    41     close(utmpfd);
    42     return 0;
    43 }

    查看其输出结果:

    ustc   tty7   Sep  1  20:34 :0

    与系统自带的who没什么区别了。至此,一个能用的who命令应该是完成了。

    3.3. 继续优化,who3.c

    一个能用的who命令是编写好了,但是其有没有不足之处呢?显然是有的,一个最大的问题,就是文件的I/O效率太低了。

    因为每次读写操作的系统调用CPU都要完成用户态与内核态之间的切换,假设结果中有大量用户信息要去读,则大量的资源消耗在了这里,所以这是优化的重点。

    而优化的思路也很简单,就是使用缓冲区。每次从内核多读一些进来进缓冲区,然后从缓冲区读取utmp数据进行显示。

    把缓冲区大小设置为16,开闭读取文件都用写好的接口从缓冲区读取和写入数据,有如下代码:

     1 //utmplib.h
     2 #ifndef UTMPLIB_H
     3 #define UTMPLIB_H
     4  
     5 #define NRECS 16
     6 #define NULLUT (struct utmp *)NULL
     7 #define UTSIZE (sizeof (struct utmp))
     8 
     9 static char utmpbuf[NRECS * UTSIZE];    //storage
    10 static int num_recs;            //nums of stored utmp data
    11 static int cur_rec;            //next pos to go
    12 static int fd_utmp = -1;        //read from
    13 
    14 int utmp_open(char* filename);
    15 
    16 struct utmp* utmp_next();
    17 
    18 int utmp_reload();
    19 
    20 void utmp_close();
    21 
    22 #endif
     1 //utmplib.c
     2 #include<stdio.h>
     3 #include<fcntl.h>
     4 #include<sys/types.h>
     5 #include<unistd.h>
     6 #include<utmp.h>
     7 #include"utmplib.h"
     8 
     9 int utmp_open(char* filename) {
    10     fd_utmp = open(filename, O_RDONLY);
    11     cur_rec = num_recs = 0;
    12     return fd_utmp;
    13 }
    14 
    15 struct utmp* utmp_next() {
    16     struct utmp* recp;
    17     if (fd_utmp == -1) {
    18         return NULLUT;
    19     }
    20     if (cur_rec == num_recs && utmp_reload() == 0) {
    21         return NULLUT;
    22     }
    23     recp = (struct utmp*) &utmpbuf[cur_rec * UTSIZE];
    24     cur_rec ++;
    25     return recp;
    26 }
    27 
    28 int utmp_reload() {
    29     int aimt_read;
    30     aimt_read = read(fd_utmp, utmpbuf, NRECS * UTSIZE);
    31     num_recs = aimt_read / UTSIZE;
    32     cur_rec = 0;
    33     return num_recs;
    34 }
    35 
    36 void utmp_close() {
    37     if (fd_utmp != -1) {
    38         close(fd_utmp);
    39     }
    40     return;
    41 }
     1 //who3.c
     2 #include<stdio.h>
     3 #include<utmp.h>
     4 #include<fcntl.h>
     5 #include<unistd.h>
     6 #include<stdlib.h>
     7 #include<time.h>
     8 #include"utmplib.h"
     9 #define SHOWHOST
    10 void show_time(long timeval) {
    11     char* cp;
    12     cp = ctime(&timeval);
    13     printf("%12.12s",cp + 4);
    14 }
    15 
    16 void show_info(struct utmp* utbufp) {
    17     if (utbufp -> ut_type != USER_PROCESS) {
    18         return;
    19     }
    20     printf("%-8.8s", utbufp -> ut_user);
    21     printf(" ");
    22     printf("%-8.8s", utbufp -> ut_line);
    23     printf(" ");
    24     show_time(utbufp -> ut_tv.tv_sec);
    25     printf(" ");
    26 #ifdef SHOWHOST
    27     printf("%s", utbufp -> ut_host);
    28 #endif
    29     printf("
    ");
    30 }
    31 
    32 int main() {
    33     struct utmp* utbufp;
    34     if (utmp_open(UTMP_FILE) == -1) {
    35         perror(UTMP_FILE);
    36         exit(1);
    37     }
    38     while ((utbufp = utmp_next()) != (struct utmp*)NULL) {
    39         show_info(utbufp);
    40     }
    41     utmp_close();
    42     return 0;
    43 }

    至此一个who命令基本编写完毕。

    我们在这个过程中大量使用了man文档学习和辅助编写,复习了基本的文件操作函数,学习了使用缓冲技术提高I/O效率。

    参考文献:

    Bruce Molay, Understanding Unix/Linux programming - A Guide to Theory and Practice.

    代码也可以通过https://github.com/wangxiaobao1114/Unix-Linux_Programming查看。

  • 相关阅读:
    折半枚举(双向搜索)
    弹性碰撞
    集合的整数表示
    反转(开关问题)
    尺取法
    floor函数用法
    二分搜索
    4. 差分约束系统
    二叉树的表达式求值
    关于移动app开发的一些不错的站点
  • 原文地址:https://www.cnblogs.com/wangxiaobao/p/5836944.html
Copyright © 2020-2023  润新知