上一篇简单介绍了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_COMPAT32int32_t ut_session;struct {int32_t tv_sec;int32_t tv_usec;} ut_tv;
#elselong   ut_session; struct timeval ut_tv;
#endifint32_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, &current_record, reclen) == reclen){show_info(&current_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 SHOWHOSTprintf("(%s)", utbufp->ut_host);
#endifprintf("\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);
#endifprintf("\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编程入门(2)-实现who指令相关推荐

  1. Linux编程入门(8)-文件系统初步

    文件系统是对常规文件和目录组织的集合.用于创建文件系统的命令是 mkfs.Linux 的强项之一便是支持种类繁多的文件系统 .从 Linux 专有文件 /proc/filesystems 中可以查看当 ...

  2. Linux编程入门四进程

    进程的创建有两种方式:一种是由操纵系统创建,一种是由父进程创建.在系统启动时,操作系统会创建一些进程,它们承担着管理和分配系统资源的任务,这些进程通常被称为系统进程.系统允许一个进程创建新进程(即为子 ...

  3. 计算机语言中call,PLC编程入门:子程序调用指令CALL、FROM指令、TO指令

    CALL指令描述: 梯形图示例: 由上面的示例程序可知,若X000为ON,则执行CALL跳转指令,将会到子程序P0中去执行,执行完子程序P0后,继续回到主程序中执行下个语句:同样,若果X001为ON, ...

  4. Linux编程入门三网络编程三 epoll的LT和ET模式以及EPOLLONESHOT事件

    epoll对文件描述符的操作有两种模式:LT(Level Trigger 电平触发)模式和ET(Edge Trigger 边沿触发)模式. LT是默认的工作模式,这种模式下epoll相当于一个效率较高 ...

  5. 敲代码时如何快速移动光标_数控加工中心编程入门知识,半小时快速入门!

    数控加工中心编程入门知识汇总,教你半小时快速入门!不管做哪一行,想要成为个中高手,必然要经得住时间的历练,自身要不断提高工作能力,要想成为一个数控高手,从大学毕业进工厂起,最起码需要6年以上的时间.既 ...

  6. 敲代码时如何快速移动光标_数控加工中心编程入门知识,半小时快速入门!超简洁明了!...

    数控加工中心编程入门知识汇总,教你半小时快速入门!不管做哪一行,想要成为个中高手,必然要经得住时间的历练,自身要不断提高工作能力,要想成为一个数控高手,从大学毕业进工厂起,最起码需要6年以上的时间.既 ...

  7. 【学习笔记】Linux 系统编程入门

    Linux 系统编程入门 静态库与动态库 静态库命名规则 静态库的制作 静态库使用 动态库制作 动态库使用 加载动态库 静态库的优缺点 动态库的优缺点 Makefile 文件命名 工作原理 变量 模式 ...

  8. 一文带你Linux系统编程入门

    文件和文件系统 文件是linux系统中最重要的抽象,大多数情况下你可以把linux系统中的任何东西都理解为文件,很多的交互操作其实都是通过文件的读写来实现的. 文件描述符 在linux内核中,文件是用 ...

  9. Linux【实操篇】—— Shell 编程入门、变量、运算符、条件判断、流程控制

    目录 一.Shell 编程入门 1. 认识 Shell 2. Shell 脚本的创建与执行 二.Shell 变量 1. 系统变量和自定义变量 2. 变量的基本规则 3. 设置环境变量 4. 位置参数变 ...

最新文章

  1. Tomcat 服务器的端口号的修改
  2. Knuth(佩服的一塌糊涂)
  3. Python标准库:内置函数dict(mapping, **kwarg)
  4. unordered_map自定义key
  5. 30分钟正则表达式指导
  6. Spring3之Security
  7. Matlab库中过采样函数rcosflt参数及源代码详解(翻译)
  8. edit with idle 没反应_搬个家,猫咪不吃不喝甚至猝死?可能是你没做好“前戏”...
  9. 6-vue-component
  10. K-Means优缺点
  11. 数据库设计规范化的五个要求
  12. 计算机组成与结构1800题,最新版数据结构1800题含完整答案详解.doc
  13. Java性能优化(详解)
  14. 批量标注数量大的地图注记
  15. 真格量化的回测交易撮合机制简介
  16. 木门企业最典型的十八个问题
  17. npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
  18. 项目需求变更原因及处理
  19. 你今天真好看中文扫描版
  20. CFileDialog的使用(MFC-C++)

热门文章

  1. 剖析公司技术栈,看看是否对大家适用!
  2. 第五届中国网络安全大会(NSC2017)
  3. 黑苹果无法连接wifi
  4. 你们一个个都人工智能了,让PC怎么办?
  5. 5月 CSDN 创作者之夜:获奖名单公布
  6. GIS基本功 | 14 地图投影及其相关概念
  7. html text-decoration,text-decoration 属性
  8. Java webp图片处理
  9. 大学公众号题库API
  10. 在网页中发起QQ临时对话的方法