编写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查看。

原文地址:https://www.cnblogs.com/wangxiaobao/p/5836944.html