龙空技术网

Linux编程入门(2)-实现who指令

一起学嵌入式 173

前言:

现时兄弟们对“linux中perror”大致比较珍视,小伙伴们都需要了解一些“linux中perror”的相关资讯。那么小编同时在网络上网罗了一些对于“linux中perror””的相关文章,希望姐妹们能喜欢,我们快快来学习一下吧!

上一篇简单介绍了Linux系统编程的一些概念知识,从本篇文章开始,从解释系统命令的功能入手,由浅入深,逐步讲解Linux系统编程。

建议学习者最好具有一定的C语言基础,了解数组、结构体、指针和链表的概念。

代码实验环境

操作系统:Ubuntu 18.04 LTS

编译器gcc版本:gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0

学习目标

通过分析who指令,来学习Linux的读文件操作。

who指令介绍

Linux为多用户操作系统,有时候需要查看系统是否繁忙,某人是否正在使用系统等,可以使用who指令来查看Linux系统中活动用户的情况。

命令也是程序。Linux系统中,几乎所有的命令都是人为编写的程序。在Linux系统的中增加新的命令很简单,把可执行文件放到以下任意一个目录即可:/bin、/usr/bin、/usr/local/bin,这些目录存放着很多系统命令。

如果想知道谁在使用系统,输入who指令,输出如下:

$ who

user :0 2021-10-31 21:42 (:0)

test pts/1 2021-10-31 23:19 (192.168.0.104)

每一行代表一个已经登陆的用户,第一列是用户名,第二列是终端名,第三列是登陆时间,第四列是用户的登陆地址。

who指令详解

我们可以通过联机帮助指令man,来查看who的使用方法和详细解释。查看who的帮助可输入:

$ man who

Linux系统的联机帮助内容:

名字(NAME):命令的名字以及对这个命令的简短说明。

概要(SYNOPSIS):给出命令的用法说明,包括命令格式、参数和选项列表。方括号([OPTION])为可选项。选项为短线 - 加上abdHlmpqrstTu这些字母的任意组合,命令末尾还可以有一个文件参数或者给定两个参数。

描述(DESCRIPTION):关于指令的详细阐述。根据指令和平台的不同,描述的内容也不同。

选项(OPTIONS):给出命令行中每一个选项的说明。

作者(AUTHOR):命令的作者。

参阅(SEE ALSO):包含这个命令相关的其他主题。

who指令如何工作

向下翻阅 man who指令看到的帮助信息,有以下信息

圈出的内容说明,如果who命令没有指定文件,通常用 /var/run/utmp/var/log/wtmp作为选项文件。

/var/run/utmp 文件保存当前登陆系统的用户信息

/var/log/utmp 文件保存登陆过本系统的用户信息

who通过读取文件/var/run/utmp 获得当前系统登陆的用户信息。

utmp这个文件里保存的是结构体数组,数组元素是utmp类型的结构,可以utmp.h中找到utmp类型的定义。文件utmp.h存放在/usr/include目录下。

文件/usr/include/utmp.h部分内容如下(已删除无关代码):

#ifndef    _UTMP_H#define    _UTMP_H 1#include <features.h>#include <sys/types.h>__BEGIN_DECLS/* Get system dependent values and data structures.  */#include <bits/utmp.h>/* Compatibility names for the strings of the canonical file names.  */#define UTMP_FILE    _PATH_UTMP#define UTMP_FILENAME    _PATH_UTMP#define WTMP_FILE    _PATH_WTMP#define WTMP_FILENAME    _PATH_WTMP#endif    /* Use misc.  */__END_DECLS#endif /* utmp.h  */

utmp的具体结构定义在 bits/utmp.h文件中。如下:

#define EMPTY            0 #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     32#define UT_NAMESIZE     32#define UT_HOSTSIZE     256struct exit_status {  short int e_termination;   short int e_exit;};struct utmp {  short   ut_type;   pid_t   ut_pid;  char    ut_line[UT_LINESIZE];   char    ut_id[4];   char    ut_user[UT_NAMESIZE];   struct  exit_status ut_exit;#if __WORDSIZE == 64 && defined __WORDSIZE_COMPAT32  int32_t ut_session;  struct {    int32_t tv_sec;    int32_t tv_usec;  } ut_tv;#else  long   ut_session;   struct timeval ut_tv;#endif  int32_t ut_addr_v6[4];  char __unused[20];};/* 向后兼容定义 */#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]

由以上分析可知,who通过读文件来获取需要的信息,而每个登陆的用户在文件中都有对应的记录。who的工作流程可以用下图表示:

/var/run/utmp文件中的结构数组存放已登陆用户的信息,who指令的实现,是不是把记录一个一个地读出并显示出来呢?让我们继续分析。

实现who命令

编写who程序时,需要做两件事:

从文件(/var/run/utmp)中读取数据结构信息以合适的形式将结构中的信息显示出来第一步:读取信息

从某个文件中读取数据,Linux系统提供了三个系统函数:open()、read()、close()。

open() —— 打开一个文件

open在Linux下的定义以及调用函数所需的头文件如下:

#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int open(const char *pathname, int flags);int open(const char *pathname, int flags, mode_t mode);

函数第一个参数pathname,是要打开的文件的路径名或者文件名。

第二个参数flags,表示打开文件的操作模式(有3种):只读(O_RDONLY)、只写(O_WRONLY)、可读可写(O_RDWR),调用此函数时,必须指定其中一种。还有其他可选模式,暂不做介绍。

第三个参数mode,表示设置文件访问权限的初始值,和用户掩码umask有关。此文暂时不用这个参数。

打开文件时,如果操作成功,内核会返回一个正整数的值,这个数值叫做文件描述符。如果内核检测到任务错误,这个系统调用会返回-1。

要对一个文件进行操作(读或者写),必须先打开文件。文件打开成功后,可以通过文件描述符对文件进行操作。

read() —— 从文件读取数据

read在Linux下的定义以及调用函数所需的头文件如下:

#include <unistd.h>ssize_t read(int fd, void *buf, size_t count);

函数第一参数fd,为文件描述符,由open函数返回。

第二个参数buf,存放读取数据的内存空间。

第三个参数count,希望读取的数据的个数。

如果读取成功,返回所读取数据的字节个数;否则,返回-1。

注意:最终读取的数据可能没有要求的多。例如,文件中剩余的数据少于要求读取的个数,则程序只能读取文件中剩余的数据个数。当读到文件末尾时,函数会返回0。

close() —— 关闭文件

clsoe在Linux下的定义以及调用函数所需的头文件如下:

#include <unistd.h>int close(int fd);

close 这个系统函数会关闭已经打开的文件,fd为open()函数打开文件返回的描述符。如果关闭出错,函数返回-1。关闭成功,则返回0。

对文件的操作完成后,需要关闭文件,以减少内存资源占用。

第二步:显示信息

通过printf函数利用定宽度的格式显示utmp记录信息。

第三步:代码实现

初步代码实现如下:

#include<stdio.h>#include<utmp.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<stdlib.h>#define SHOWHOSTvoid show_info(struct utmp *utbufp);int main(){    struct utmp current_record;    int utmpfd;    int reclen = sizeof(current_record);    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){    printf("%-8.8s", utbufp->ut_name);    printf(" ");    printf("%-8.8s", utbufp->ut_line);    printf("%10d", utbufp->ut_time);    printf(" ");#ifdef SHOWHOST    printf("(%s)", utbufp->ut_host);#endif    printf("\n");}

编译

$gcc who1.c -o who1

运行结果如下

$./who1 reboot ~ 1635728912 (4.15.0-161-generic)

runlevel ~ 1635729058 (4.15.0-161-generic)

user :0 1635729148 (:0)

test pts/2 1635763291 (192.168.0.104)

将上述输出结果与系统who命令输出做对比:

$ who

user :0 2021-11-01 09:12 (:0)

test pts/2 2021-11-01 18:41 (192.168.0.104)

自己编写的who已经可以工作了,可以显示用户名、终端名、远程主机名。但是,根系统的who相比较还不完善。存在两处内容需要改进:

(1)消除空白记录

(2)正确显示登陆时间

程序代码优化消除空白记录

系统who命令只列出已登陆用户的信息。而我们编写的代码,除了列出已登录的用户,还会显示utmp文件中的其他信息。实际上utmp包含所有终端的信息,那些尚未用到的终端信息也会存放在utmp中。

utmp结构中有一个成员ut_type,当它的值为7(USER_PROCESS)时,表示这是一个已经登陆的用户。据此,对原来的程序显示信息函数 show_info() 函数开头添用户类型判断,即可消除空白记录:

if(utbufp->ut_type != USER_PROCESS){    return;}
使得显示登陆时间可读

Linux中的时间是用一个整数来表示的,类型为 time_t ,它的数值是从1970年1月1日0时开始经过的秒数。存储时间的结构 time_t 实际上就是 long int 。类型 time_t 定义为

typedef long int time_t;

需要将时间的整数值转换为易于理解的形式。实验环境系统中who指令显示的时间格式如下

2021-10-31 23:19

我们需要由存储的时间的秒数值得到:年、月、日、时、分等信息。即需要将Linux存储的时间秒数转换为分解时间。分解时间存储结构类型为tm,其结构定义如下

struct tm {    int tm_sec;    /* 秒 (0-59) */    int tm_min;    /* 分 (0-59) */    int tm_hour;   /* 小时 (0-23) */    int tm_mday;   /* 一个月中第几天 (1-31) */    int tm_mon;    /* 月份 (0-11) */    int tm_year;   /* 自 1900 年起的年数 */  int tm_wday;   /* 一周中第几天 (0-6, Sunday = 0) */    int tm_yday;   /* 一年中第几天 (0-365, 1 Jan = 0) */    int tm_isdst;  /* 夏令时 */  };

localtime()函数将时间秒数转换为分解时间,并用本地时区表示,其定义如下

#include <time.h>struct tm *localtime(const time_t *timep);

函数的参数为一个指向 time_t 的指针,返回一个指向 tm 结构的指针。

代码优化

综合以上两点对代码进行优化。

优化信息显示函数show_info如下:

void show_info(struct utmp *utbufp){    	if(utbufp->ut_type != USER_PROCESS) 	{       		return; 	}   	printf("%-8.8s", utbufp->ut_name);	printf(" ");	printf("%-8.8s", utbufp->ut_line);	show_time(utbufp->ut_time); printf(" ");#ifdef SHOWHOST 	printf("(%s)", utbufp->ut_host);#endif	printf("\n");}

添加时间显示函数show_time如下

void show_time(time_t timeval){	struct tm *info = NULL;	info = localtime(&timeval);	printf("%4d-%2d-%02d %02d:%02d", (info->tm_year + 1900), (info->tm_mon + 1), \				info->tm_mday, info->tm_hour, info->tm_min);}

编译后,运行结果如下

$./who2

user :0 2021-11-01 09:12 (:0)

user pts/2 2021-11-01 18:41 (192.168.0.104)

显示的结果与系统的who命令对比,显示结果基本一致。

小结

本篇文章介绍了Linux系统中who命令的工作原理,并通过自己实现who指令,来学习Linux编程对文件的读操作。并学习了登陆信息utmp文件结构,学习了Linux的时间处理。

涉及到的系统函数:open、read、close、localtime

相关指令:man、who

后续

接下来学习Linux文件操作之写文件操作

标签: #linux中perror