前言:
现时兄弟们对“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