Linux系统编程22-简单的who命令实现
实现who命令:
- 从文件中读取数据结构
- 将结构中的信息以合适的形式显示出来
- who的时间表示格式:["2011-01-17 13:40"]
需要用到的函数(unistd.h):
- 打开一个文件: open(filename, how), 建立文件描述符,连接文件与进程
- 从文件读取数据: read(fd, buffer, amt),返回所读取的字节数目, 结尾时继续读会返回0
- 关闭文件: close()
需要访问的结构体:
struct utmp 包含以下成员:
ut_type:登录类型,包括如 INIT_PROCESS、LOGIN_PROCESS、USER_PROCESS 和 DEAD_PROCESS 等。
ut_pid:登录进程的进程 ID。
ut_line:登录用户所使用的设备名,例如 tty1、pts/1 等。
ut_id:登录进程的 ID,通常指在 /etc/inittab 文件中定义的 id。
ut_user:登录用户的用户名。
ut_host:远程登录的主机名。
ut_exit:记录进程退出状态的结构体,只有 ut_type 为 DEAD_PROCESS 时才有意义。
ut_session:会话 ID,用于窗口管理。
ut_tv:记录记录登录或注销时间的时间结构体,包括秒数和微秒数。
ut_addr_v6:远程主机的 IPv6 地址。
__glibc_reserved:保留字段,目前没有使用。
utmp 数据库通常可以在 /var/run/utmp 或者 /var/log/wtmp 文件中找到。
使用 utmp 数据库可以监控用户登录和注销信息,以及查看当前系统中登录的用户信息。
Unix存储时间的方式:time_t类型
- typedef long int time_t;
- ctime 将表示时间的整数值转换成人们日常所使用的时间形式:"Wed Jun 30 21:49:08 1993\n"
char* ctime(const time_t* timep);
- time_t* : 就是long int
- timep: 要转换的时间戳(秒)
返回: 格式为 www mmm dd hh:mm:ss yyyy\n 的时间字符串指针
- www是星期几的缩写,如“Mon”、“Tue”等。
- mmm是月份的缩写,如“Jan”、“Feb”等。
- dd表示一个月中的第几天。
- hh表示小时(24小时制)。
- mm表示分钟。
- ss表示秒。
- yyyy表示年份。
strftime 将时间格式化为指定格式的字符串 例如:"%Y-%m-%d %H:%M"
需要struct tm*结构体, 由localtime()根据当前时区转换
版本一:基本的who命令
//suppresses empty records
//formats time nicely
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <utmp.h>
#include <time.h>
#include <fcntl.h>
#define SHOWHOST
void show_time(long int);
void show_time2(long int);
void show_info(struct utmp*);
int main(int argc, char const *argv[])
{
struct utmp utbuf;
int utmpfd;
if((utmpfd = open(UTMP_FILE, O_RDONLY)) == -1){
perror(UTMP_FILE);
exit(1);
}
//UTMP_FILE 文件中有多条结果,循环读取,一次读取一个 struct utmp
while(read(utmpfd, &utbuf, sizeof(utbuf)) == sizeof(utbuf))
show_info(&utbuf);
close(utmpfd);
return 0;
}
//displays the contents of the utmp struct
//in human readable form
//displays nothing if recond has no user name
void show_info(struct utmp* utbufp)
{
if(utbufp->ut_type != USER_PROCESS)
return;
printf("%-8.8s", utbufp->ut_user);
printf(" ");
printf("%-12.8s", utbufp->ut_line);
printf(" ");
show_time2(utbufp->ut_tv.tv_sec);
#ifdef SHOWHOST
//只显示有地址的结果
if(utbufp->ut_host[0] != '\0')
printf("(%s)", utbufp->ut_host);
#endif
printf("\n");
//joe pts/0 2023-03-27 13:40 (192.168.111.1)
}
//old version
//format the time
void show_time(long int timeval){
char* cp = ctime(&timeval); //"Wed Jun 30 21:49:08 1993\n"
printf("%12.12s", cp+4);
//Jun 30 21:49
}
//new version
void show_time2(long int timeval){
struct tm* time_info;
char time_str[20];
time_info = localtime(&timeval);
// 将时间格式化为指定格式的字符串
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M", time_info);
//2023-03-27 13:40
printf("%s ",time_str);
}
版本二:使用缓冲,优化I/O效率
- 程序cp1读取磁盘上的数据只能通过系统调用read, 而read的代码在内核中,
- 当read调用发生,执行权会从用户代码转移到内核代码,
- 频繁在用户空间和系统空间切换很消耗时间,所以减少程序中系统调用的次数可以提高运行效率
优化代码:编写缓冲utmp文件, 让新的who命令使用缓冲
utmplib.c
// functions to buffer reads from utmp file
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <utmp.h>
#define NRECS 16 // 缓冲区最多存储16条记录
#define NULLUT ((struct utmp*)NULL) // utmp空指针
#define UTSIZE (sizeof(struct utmp)) // 每条utmp记录的字节数
static char utmpbuf[NRECS * UTSIZE]; //静态字符数组
static int num_recs; //缓冲区中记录数
static int cur_rec; //当前记录在缓冲区中的下标
static int fd_utmp = -1; //utmp文件的文件描述符
//打开utmp文件并初始化静态变量
int utmp_open(char* filename){
fd_utmp = open(filename, O_RDONLY);
cur_rec = num_recs = 0;
return fd_utmp;
}
//返回一个指向下一个 utmp 记录的指针
struct utmp* utmp_next(){
struct utmp* recp;
//读取出错
if(fd_utmp == -1)
return NULLUT;
//文件的结尾
if(cur_rec == num_recs && utmp_reload() == 0)
return NULLUT;
//取到了下一项utmp结构指针
recp = (struct utmp*)&utmpbuf[cur_rec*UTSIZE];
//当前下标+1
cur_rec++;
return recp;
}
//重新加载 utmp 文件
int utmp_reload()
{
int amt_read;
amt_read = read(fd_utmp, utmpbuf, NRECS*UTSIZE);
num_recs = amt_read/UTSIZE;
cur_rec = 0;
return num_recs;
}
void utmp_close(){
if(fd_utmp != -1)
close(fd_utmp);
}
who.c
//do with buffered reads
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <utmp.h>
#define SHOWHOST
void show_time(long int);
void show_time2(long int);
void show_info(struct utmp*);
int main(int argc, char const *argv[])
{
struct utmp* utbufp;
struct utmp* utmp_next();
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;
}
//displays the contents of the utmp struct
//in human readable form
//displays nothing if recond has no user name
void show_info(struct utmp* utbufp)
{
if(utbufp->ut_type != USER_PROCESS)
return;
printf("%-8.8s", utbufp->ut_user);
printf(" ");
printf("%-12.8s", utbufp->ut_line);
printf(" ");
show_time2(utbufp->ut_tv.tv_sec);
#ifdef SHOWHOST
//只显示有地址的结果
if(utbufp->ut_host[0] != '\0')
printf("(%s)", utbufp->ut_host);
#endif
printf("\n");
//joe pts/0 2023-03-27 13:40 (192.168.111.1)
}
//old version
//format the time
void show_time(long int timeval){
char* cp = ctime(&timeval); //"Wed Jun 30 21:49:08 1993\n"
printf("%12.12s", cp+4);
//Jun 30 21:49
}
//new version
void show_time2(long int timeval){
struct tm* time_info;
char time_str[20];
time_info = localtime(&timeval);
// 将时间格式化为指定格式的字符串
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M", time_info);
//2023-03-27 13:40
printf("%s ",time_str);
}