实现who命令

76 阅读2分钟

who命令

列出已经登录的用户
[用户名][终端名][登陆时间][登陆地址]

who命令.png

实现思路

  1. 查看Linux系统who命令的信息
    man who
    Linux用户信息存储在/var/log/wtmp文件中
  2. 查看utmp头文件 man utmp

png.png /var/log/wtmp中的数据使用utmp结构体的形式存储 3. who的工作流程

graph TD
A[打开文件] --> B[循环读取];
B -- Yes --> C[打印用户信息];
C --> B;
B -- No --> D[End];

代码

// who.h文件
#ifndef MYWHO_H__
#define MYWHO_H__

#define FILE_NAME "/var/log/wtmp"

void show_time(long);

void show_utmp_info(struct utmp *);

int who();
#endif
// who.c文件
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

void show_time(long timeval) {
    char *cp;
    cp = ctime(&timeval);
    printf("%12.12s ", cp + 4);
}

void show_utmp_info(struct utmp *p_utmp_info) {
    // #define USER_PROCESS 7
    // ut_type = 7的用户为已登录的用户
    if(p_utmp_info -> ut_type != USER_PROCESS)
        return;
    printf("%-8.8s ", p_utmp_info -> ut_name);
    printf("%-8.8s ", p_utmp_info -> ut_line);
    show_time(p_utmp_info -> ut_time);
    printf("(%s) ", p_utmp_info -> ut_host);
    printf("\n");
}

int who() {
    int utmp_fd;
    struct utmp utmp_info;
    int utmp_len = sizeof(struct utmp);
    
    if((utmp_fd = open(FILE_NAME, O_RDONLY)) == -1) {
        perror(FILE_NAME);
        return -1;
    }
    
    while(read(utmp_fd, &utmp_info, utmp_len) == utmp_len) {
        show_utmp_info(&utmp_info);
    }
    
    close(utmp_fd);
    return 0;
}
// main.c
#include <stdio.h>
#include <stdlib.h>
#include "who.h"

int main(void) {
    if(who() == -1) {
        exit(1);
    } 
    exit(0);
}
makefile文件

OBJS=main.o who.o
CC=gcc
CFLAGS+=-c -Wall -g -o

who:$(OBJS)
    $(CC) $(OBJS) -o who
%.o:%.c
    $(CC) $^ $(CFLAGS) $@
clean:
    rm who *.o -rf

使用localtime函数显示时间

void show_time(long timeval) {
    struct tm *current_tm = localtime(&timeval);
    printf("%d-%d-%d %d:%d:%d ", current_tm -> tm_year + 1900, current_tm -> tm_mon + 1, current_tm -> tm_mday, current_tm -> tm_hour, current_tm -> tm_min, current_tm -> tm_sec);
}

使用缓冲区优化代码

使用缓冲区的目的:减少系统调用次数
磁盘只能被操作系统操作,程序中的open、read、write都是告诉操作系统要发起一个系统调用,而不是直接在程序中操作磁盘,这个过程中就需要将控制权从用户程序切换到操作系统,这个切换过程涉及到CPU切到用户模式、堆栈、内存环境切换
所以系统调用次数越多,切换越频繁,那么消耗在切换过程中的时间越多

// who.h
#ifndef MYWHO_H__
#define MYWHO_H__

#include <stdio.h>
#include <stdlib.h>
#include <utmp.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define FILE_NAME "/var/run/utmp"
#define LEN sizeof(struct utmp)
#define SIZE 16

int utmp_open(); // 打开/var/run/utmp文件
int utmp_load(); // 将数据加载到缓冲区
struct utmp *utmp_next(); // 获取缓冲区下一条数据
void utmp_close(); // 关闭/var/run/utmp文件
void show_time(long timeval); // 时间打印
void show_utmp_info(struct utmp *p_utmp); // 打印utmp结构体
int who();

#endif
// who.c
#include "who.h"

static int utmp_fd = -1;
static struct utmp buffer[SIZE];
static int total_num = 0;
static int cur_num = 0;

int utmp_open() {
    utmp_fd = open(FILE_NAME, O_RDONLY);
    total_num = cur_num = 0;
    return utmp_fd;
}

int utmp_load() {
    int read_chars = read(utmp_fd, buffer, SIZE * LEN);
    if(read_chars < 0)
        return -1;
    total_num = read_chars / LEN;
    cur_num = 0;
    return total_num;
}

struct utmp *utmp_next() {
    struct utmp *res = NULL;
    if(utmp_fd == -1)
        return NULL;
    if(total_num == cur_num && utmp_load() == 0)
        return NULL;
    res = &buffer[cur_num];
    cur_num ++;
    return res;
}

void utmp_close() {
    if(utmp_fd != -1)
        close(utmp_fd);
}

void show_time(long timeval) {
    struct tm *current_tm = localtime(&timeval);
    printf("%d-%d-%d %d:%d:%d ", current_tm -> tm_year + 1900, current_tm -> tm_mon + 1, current_tm -> tm_mday, current_tm -> tm_hour, current_tm -> tm_min, current_tm -> tm_sec);
}

void show_utmp_info(struct utmp *p_utmp) {
    if(p_utmp -> ut_type != USER_PROCESS)
        return;
    printf("%-8.8s ", p_utmp -> ut_name);
    printf("%-8.8s ", p_utmp -> ut_line);
    show_time(p_utmp -> ut_time);
    printf("(%s)", p_utmp -> ut_host);
    printf("\n");
}

int who() {
    struct utmp *p_utmp = NULL;
    if(utmp_open() == -1) {
        perror(FILE_NAME);
        return -1;
    }
    while((p_utmp = utmp_next()) != NULL)
        show_info(p_utmp);
    utmp_close();
    return 0;
}