最近阅读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

代码:

 #include<stdio.h>
#include<utmp.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#define SHOWHOST void show_info(struct utmp* utbufp) {
printf("%8.8s", utbufp -> ut_user);
printf(" ");
printf("%8.8s", utbufp -> ut_line);
printf(" ");
printf("%d", utbufp -> ut_tv.tv_sec);
printf(" ");
#ifdef SHOWHOST
printf("%s", utbufp -> ut_host);
#endif
printf("\n");
} int main() {
struct utmp current_record;
int utmpfd;
int reclen = sizeof(current_record);
if ((utmpfd = open(UTMP_FILE,O_RDONLY)) == -) {
perror(UTMP_FILE);
exit();
}
while (read(utmpfd,&current_record,reclen) == reclen) {
show_info(&current_record);
}
close(utmpfd);
return ;
}

查看一下输出:

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()) */
#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

 #include<stdio.h>
#include<utmp.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<time.h>
#define SHOWHOST
void show_time(long timeval) {
char* cp;
cp = ctime(&timeval);
printf("%12.12s",cp + );
} void show_info(struct utmp* utbufp) {
if (utbufp -> ut_type != USER_PROCESS) {
return;
}
printf("%-8.8s", utbufp -> ut_user);
printf(" ");
printf("%-8.8s", utbufp -> ut_line);
printf(" ");
show_time(utbufp -> ut_tv.tv_sec);
printf(" ");
#ifdef SHOWHOST
printf("%s", utbufp -> ut_host);
#endif
printf("\n");
} int main() {
struct utmp current_record;
int utmpfd;
int reclen = sizeof(current_record);
if ((utmpfd = open(UTMP_FILE,O_RDONLY)) == -) {
perror(UTMP_FILE);
exit();
}
while (read(utmpfd,&current_record,reclen) == reclen) {
show_info(&current_record);
}
close(utmpfd);
return ;
}

查看其输出结果:

ustc   tty7   Sep  1  20:34 :0

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

3.3. 继续优化,who3.c

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

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

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

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

 //utmplib.h
#ifndef UTMPLIB_H
#define UTMPLIB_H #define NRECS 16
#define NULLUT (struct utmp *)NULL
#define UTSIZE (sizeof (struct utmp)) static char utmpbuf[NRECS * UTSIZE]; //storage
static int num_recs; //nums of stored utmp data
static int cur_rec; //next pos to go
static int fd_utmp = -; //read from int utmp_open(char* filename); struct utmp* utmp_next(); int utmp_reload(); void utmp_close(); #endif
 //utmplib.c
#include<stdio.h>
#include<fcntl.h>
#include<sys/types.h>
#include<unistd.h>
#include<utmp.h>
#include"utmplib.h" int utmp_open(char* filename) {
fd_utmp = open(filename, O_RDONLY);
cur_rec = num_recs = ;
return fd_utmp;
} struct utmp* utmp_next() {
struct utmp* recp;
if (fd_utmp == -) {
return NULLUT;
}
if (cur_rec == num_recs && utmp_reload() == ) {
return NULLUT;
}
recp = (struct utmp*) &utmpbuf[cur_rec * UTSIZE];
cur_rec ++;
return recp;
} int utmp_reload() {
int aimt_read;
aimt_read = read(fd_utmp, utmpbuf, NRECS * UTSIZE);
num_recs = aimt_read / UTSIZE;
cur_rec = ;
return num_recs;
} void utmp_close() {
if (fd_utmp != -) {
close(fd_utmp);
}
return;
}
 //who3.c
#include<stdio.h>
#include<utmp.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<time.h>
#include"utmplib.h"
#define SHOWHOST
void show_time(long timeval) {
char* cp;
cp = ctime(&timeval);
printf("%12.12s",cp + );
} void show_info(struct utmp* utbufp) {
if (utbufp -> ut_type != USER_PROCESS) {
return;
}
printf("%-8.8s", utbufp -> ut_user);
printf(" ");
printf("%-8.8s", utbufp -> ut_line);
printf(" ");
show_time(utbufp -> ut_tv.tv_sec);
printf(" ");
#ifdef SHOWHOST
printf("%s", utbufp -> ut_host);
#endif
printf("\n");
} int main() {
struct utmp* utbufp;
if (utmp_open(UTMP_FILE) == -) {
perror(UTMP_FILE);
exit();
}
while ((utbufp = utmp_next()) != (struct utmp*)NULL) {
show_info(utbufp);
}
utmp_close();
return ;
}

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

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

参考文献:

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

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

编写who命令:文件操作,缓冲区与联机帮助的更多相关文章

  1. Linux常用命令--文件操作

    常用Linux命令笔记(1) 1. 创建文件/文件夹 参考博客:https://www.cnblogs.com/lclq/p/5741852.html. 使用cat命令创建新文件: 输入命令 # ca ...

  2. Linux常用命令--文件操作、权限设置

    1.编辑文件 cat aaa.txt 查看aaa.txt文件的内容 head - aaa.txt 查看aaa.txt文件前5行的内容 tail - aaa.txt 展示aaa.txt文件最后10行的内 ...

  3. Linux之基础命令——文件操作

    ls(显示指定工作目录下的内容) -a 显示所有文件及目录 包括隐藏文件 -l 除文件名称外,还会将文件类型.权限.拥有者.文件大小等信息详细列出[可以ll简写] -r 将文件以相反次序显示(默认是a ...

  4. linux常用命令之------文件操作、文件查看、权限、打包压缩

    1.一般公司把linux作为自己的应用服务器,将应用和服务器部署在上面 2.测试一般用来打包.压缩.查日志,写一个简单的shell 获得linux服务器的方式 a:网上租一台云服务器 b:安装vmwa ...

  5. sed命令针对文件操作具体解释

    Linux的简单shell脚本中改动文件操作 1.Sed简单介绍 sed 是一种在线编辑器,它一次处理一行内容.处理时.把当前处理的行存储在暂时缓冲区中,称为"模式空间"(patt ...

  6. dos命令:文件操作

    文件操作 一.assoc命令 1.介绍 显示或修改文件扩展名关联 2.语法 ASSOC [.ext[=[fileType]]] .ext     指定跟文件类型关联的文件扩展名 fileType 指定 ...

  7. go语言之行--文件操作、命令行参数、序列化与反序列化详解

    一.简介 文件操作对于我们来说也是非常常用的,在python中使用open函数来对文件进行操作,而在go语言中我们使用os.File对文件进行操作. 二.终端读写 操作终端句柄常量 os.Stdin: ...

  8. Linux命令-文件文本操作grep

    文件文本操作 grep 在文件中查找符合正则表达式条件的文本行 cut 截取文件中的特定字段 paste 附加字段 tr 字符转换或压缩 sort 调整文本行的顺序,使其符合特定准则 uniq 找出重 ...

  9. Linux文件操作常用命令整理

    收集.整理日常系统管理或维护当中的,常用到的一些关于文件操作的命令或需求,后续会慢慢补充.完善! 查看.生成指定目录的目录树结构?   [root@DB-Server ~]#tree   #当前目录 ...

随机推荐

  1. openstack 实例迁移

  2. hdu 1199 Color the Ball

    http://acm.hdu.edu.cn/showproblem.php?pid=1199 Color the Ball Time Limit: 2000/1000 MS (Java/Others) ...

  3. 关于缺省路由传递问题的探讨(下)[ip default-network、ip default-gateway等]

    之前文章介绍的是没有路由协议的环境下,那么在有路由协议的环境下: ip default-network IGRP/EIGRP: IP Default-Network所指定的网络必须在EIGRP进程中通 ...

  4. 8.1搜索专练DFS和BFS

    这是第一次全部做出来的依次练习了,有一些都是做过两遍了的,但是还是错了几回,更多时候我还是应该多注意下细节,就好像章爷笑我 的一样,像什么vis[]标记没清0,什么格式错误,还有什么题目没看清,还是的 ...

  5. Add mappings to an Elasticsearch index in realtime

    Changing mapping on existing index is not an easy task. You may find the reason and possible solutio ...

  6. 编译ffmpeg(iOS)

    一,x264库的编译 首先到http://www.videolan.org/developers/x264.html下载x264的库,然后解压,修改文件夹名称为x264     二,下载ffmpeg2 ...

  7. Android Studio使用JNI

    0x01 前言 本文讲述使用Android Studio通过静态注册.动态注册使用JNI的方法,以及加载第三方so文件的方法 0x02 Android Studio静态注册的方式使用JNI 1. 添加 ...

  8. 《Spring3.0就这么简单》第1章快速入门

    问题 [如何将jsp中传递到HttpServletRequest的参数,自动装配到Java对象中] [成功] public void setUsernAme(String username) < ...

  9. 更改VisualStudio默认创建类和接口不加public问题

    由于VisualStudio创建类和接口时,默认的是不加public关键字的,而我们大多数时候创建的类都是public的,于是,为了更好工作觉得改之. 首先找到在VisualStudio安装目录下路径 ...

  10. HTML to DOM

    Although you can now natively parse HTML using DOMParser and XMLHttpRequest, this is a new feature t ...