在使用Unix的时候,经常需要知道有哪些用户正在使用系统,系统是否繁忙,某人是否正在使用这个系统。为了回答这些问题,可以使用who命名,很多多用户操作系统都会有这个命名。这个命令会显示系统中活动用户的情况。接下来的问题是,who命令是如何工作的
who
- 上面最大的长方体表示计算机内存,它被分为用户空间和系统空间,用户通过终端连接到系统。
- 一大一小两个柱状体表示两个硬盘,系统中还有一个打印机
- 上方三个较小的长方体是表示3个应用程序,他们运行在用户空间,通过内核与外界进行通信。应用程序和内核之间的连线表示通信管道
是命令也是程序
在Unix系统中,几乎所有的命令都是人为编写的程序,比如who,ls。如果你对某个命令的功能不满意,完全可以编写自己的命令
在Unix系统中增加新的命令是一件很容易的事。把程序的可执行文件放到以下任意一个目录就可以了: /bin,/usr/bin,/usr/local/bin,这些目录里面存放着很多系统命令。
who命令能够做什么?
如果想知道谁正在使用系统,可以输入who命令。
每一行代表一个已经登录的用户
- 第一列是用户名
- 第二列是终端名
- 第三列是登录时间
- 第四列是用户的登录地址
who命令是如何工作的
- 阅读联机帮助
$ man who
NAME
who - 显示已经登录的用户
总览 (SYNOPSIS)
who [OPTION]... [ FILE | ARG1 ARG2 ]
描述 (DESCRIPTION)
-H, --heading
显示 栏目行
-i, -u, --idle
增加 显示 用户的 空闲时间, 格式是 HOURS:MINUTES, . 或 old
-l, --lookup
试图 通过 DNS 规范 主机名
-m 仅显示 和 stdin 关联 的 主机名 和 用户
-q, --count
显示 全部的 登录名 和 登录数
-s (忽略)
-T, -w, --mesg
用 +, - 或 ? 表示 用户的 消息(message) 状态.
--message
同 -T
--writable
同 -T
--help 显示 帮助信息, 然后 结束
--version
显示 版本信息, 然后 结束
如果 没有 指定 FILE, 缺省 使用 /var/run/utmp. /var/log/wtmp 是 比较
常用的 FILE. 如果 给出 ARG1 ARG2, who 设定 -m 有效: 他们 通常 是 `am i'
或 `mom likes'.
另见 (SEE ALSO)
who 的 完整文档 以 Texinfo 手册 的 格式 维护. 如果 正确 安装了 info 和
who 程序, 使用 命令
info who
能够 访问到 完整 的 手册.
从上面可以看出,已经登录用户的信息放在文件/var/run/utmp中,who通过读该文件好的信息。可以通过搜索联机帮助来了解这个文件的结构信息。
- /var/run/utmp存储的是当前登录的用户信息
- /var/log/wtmp存储的是历史登录用户信息
- 搜索联机帮助
使用man -k xxx可以根据关键字搜索联机帮助
$ man -k utmp
utmp (5) - 登 录 记 录(login records)
从上面可以看出utmp位于man的第5节:
$ man 5 utmp
NAME[名称]
utmp, wtmp - 登 录 记 录(login records)
SYNOPSIS[总览]
#include
DESCRIPTION[描述]
utmp 文 件 用 于 记 录 当 前 系 统 用 户 是 哪 些 人。 但 是 实 际 的
人 数 可 能 比 这 个 数 目 要 多 , 因 为 并 非 所 有 用 户 都 用 utmp
登 录。
警告: utmp 必 须 置 为 不 可 写 , 因 为 很 多 系 统 程 序 ( 有 点 傻
的 那 种 ) 依 赖 于 它。 如 果 你 将 它 置 为 可 写 , 其 他 用 户 可
能 会 修 改 它 (//* 导 致 程 序 运 行 出 错 ) 。 (//* (//* )中 为
译 者 注)
utmp是一个结构体,"/var/run/utmp"中存储的就是这个结构体,utmp结构体定义如下:
#define UT_UNKNOWN 0
#define RUN_LVL 1
#define BOOT_TIME 2
#define NEW_TIME 3
#define OLD_TIME 4
#define INIT_PROCESS 5
#define LOGIN_PROCESS 6
#define USER_PROCESS 7
#define DEAD_PROCESS 8
#define ACCOUNTING 9
#define UT_LINESIZE 12
#define UT_NAMESIZE 32
#define UT_HOSTSIZE 256
struct exit_status {
short int e_termination; /* process termination status. */
short int e_exit; /* process exit status. */
};
struct utmp {
short ut_type; /* type of login */
pid_t ut_pid; /* pid of login process */
char ut_line[UT_LINESIZE]; /* 登录终端 - "/dev/" */
char ut_id[4]; /* init id or abbrev. ttyname */
char ut_user[UT_NAMESIZE]; /* 登录的用户名 */
char ut_host[UT_HOSTSIZE]; /* hostname for remote login */
struct exit_status ut_exit; /* The exit status of a process
marked as DEAD_PROCESS. */
long ut_session; /* session ID, used for windowing*/
struct timeval ut_tv; /* time entry was made. */
int32_t ut_addr_v6[4]; /* IP address of remote host. */
char pad[20]; /* Reserved for future use. */
};
/* Backwards compatibility hacks. */
#define ut_name ut_user
#ifndef _NO_UT_TIME
#define ut_time ut_tv.tv_sec
#endif
#define ut_xtime ut_tv.tv_sec
#define ut_addr ut_addr_v6[0]
FILES[相关文件]
/var/run/utmp
/var/log/wtmp
- 阅读头文件
$ cat /usr/include/utmp.h
- 总结:
- who通过读取文件来获得需要的信息,每个登录的用户在文件中都有对应的记录,其工作流程如下图:
编写一个who程序
- 第一版
#include<stdio.h>
#include<utmp.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<time.h>
//辅助函数声明
void show_info(struct utmp*);
void showtime(long);
int main()
{
struct utmp current_record; //定义utmp结构体
int utmpfd; //定义文件描述符
int reclen = sizeof(current_record);//获得utmp结构体大小
//打开文件的错误处理
if((utmpfd = open(UTMP_FILE,O_RDONLY)) == -1){
perror(UTMP_FILE);
exit(1);
}
//读取文件内容并打印
while(read(utmpfd,¤t_record,reclen) == reclen){
show_info(¤t_record);
}
close(utmpfd);
return 0;
}
//打印读取到的结构体数据
void show_info(struct utmp* utbufp)
{
//当目前记录不是用户信息时,舍弃
if(utbufp->ut_type != USER_PROCESS){
return;
}
//打印用户名
printf("%-10.10s",utbufp->ut_name);
printf(" ");
//打印用户登录终端
printf("%-10.10s",utbufp->ut_line);
printf(" ");
//打印用户登录时间
showtime((long)utbufp->ut_time);
//打印用户登录地址
if(utbufp->ut_host[0] != '\0'){
printf("(%s)",utbufp->ut_host);
}
printf("\n");
}
//完成时间转换并打印
void showtime(long timeval)
{
char *cp;
cp = ctime(&timeval);
printf("%24.24s",cp);
}
- 上面程序的问题是每次进行read调用,只能读取一条记录,这可能非常消耗时间。
- 系统调用是在用户态下调用“系统调用函数”,转为“内核态”去执行这个函数,然后将结果返回给用户态。
- 这就需要完成“用户态”到“内核态”再到“用户态”之间切换。
- 这种切换过程需要完成状态保存、参数压栈、寄存器切换等等操作,比较费事(但为了系统的安全稳定性,不得不这样进行状态划分)
改进方法是使用缓冲,一次读取多条记录,缓存起来,然后从缓冲中拿就行,缓冲中拿完了再调用read,这样就能减少系统调用次数,提高程序效率。
//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 = -1; //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 = 0;
return fd_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;
}
int utmp_reload() {
int aimt_read;
aimt_read = read(fd_utmp, utmpbuf, NRECS * UTSIZE);
num_recs = aimt_read / UTSIZE;
cur_rec = 0;
return num_recs;
}
void utmp_close() {
if (fd_utmp != -1) {
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 + 4);
}
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) == -1) {
perror(UTMP_FILE);
exit(1);
}
while ((utbufp = utmp_next()) != (struct utmp*)NULL) {
show_info(utbufp);
}
utmp_close();
return 0;
}