Unix/Linux编程:who

在使用Unix的时候,经常需要知道有哪些用户正在使用系统,系统是否繁忙,某人是否正在使用这个系统。为了回答这些问题,可以使用who命名,很多多用户操作系统都会有这个命名。这个命令会显示系统中活动用户的情况。接下来的问题是,who命令是如何工作的

who

在这里插入图片描述

  • 上面最大的长方体表示计算机内存,它被分为用户空间和系统空间,用户通过终端连接到系统。
  • 一大一小两个柱状体表示两个硬盘,系统中还有一个打印机
  • 上方三个较小的长方体是表示3个应用程序,他们运行在用户空间,通过内核与外界进行通信。应用程序和内核之间的连线表示通信管道

是命令也是程序

在Unix系统中,几乎所有的命令都是人为编写的程序,比如who,ls。如果你对某个命令的功能不满意,完全可以编写自己的命令

在Unix系统中增加新的命令是一件很容易的事。把程序的可执行文件放到以下任意一个目录就可以了: /bin,/usr/bin,/usr/local/bin,这些目录里面存放着很多系统命令。

who命令能够做什么?

如果想知道谁正在使用系统,可以输入who命令。

在这里插入图片描述
每一行代表一个已经登录的用户

  • 第一列是用户名
  • 第二列是终端名
  • 第三列是登录时间
  • 第四列是用户的登录地址

who命令是如何工作的

  1. 阅读联机帮助
$ 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存储的是历史登录用户信息
  1. 搜索联机帮助

使用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


  1. 阅读头文件
$ cat /usr/include/utmp.h
  1. 总结:
  • who通过读取文件来获得需要的信息,每个登录的用户在文件中都有对应的记录,其工作流程如下图:

在这里插入图片描述

编写一个who程序

  1. 第一版
#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,&current_record,reclen) == reclen){
        show_info(&current_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);
}
  1. 上面程序的问题是每次进行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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值