本篇笔记的所有操作是在VMware12.5.2,Ubuntu14.04.1下进行的。如果由大神对我写的文章有兴趣的话,文章后面有我的两个问题,希望大神不吝赐教!
-
who命令工作流程
1.打开utmp文件
2.读取utmp文件里的内容,并保存在struct utmp结构体中
3.将结构体中的内容展示出来 -
strcut utmp结构定义(这个是系统定义好的,不是我自己定义的)
struct utmp {
short ut_type; /* Type of record */
pid_t ut_pid; /* PID of login process */
char ut_line[UT_LINESIZE]; /* Device name of tty - "/dev/" */
char ut_id[4]; /* Terminal name suffix,
or inittab(5) ID */
char ut_user[UT_NAMESIZE]; /* Username */
char ut_host[UT_HOSTSIZE]; /* Hostname for remote login, or
kernel version for run-level
messages */
struct exit_status ut_exit; /* Exit status of a process
marked as DEAD_PROCESS; not
used by Linux init(8) */
/* The ut_session and ut_tv fields must be the same size when
compiled 32- and 64-bit. This allows data files and shared
memory to be shared between 32- and 64-bit applications. */
#if __WORDSIZE == 64 && defined __WORDSIZE_COMPAT32
int32_t ut_session; /* Session ID (getsid(2)),
used for windowing */
struct {
int32_t tv_sec; /* Seconds */
int32_t tv_usec; /* Microseconds */
} ut_tv; /* Time entry was made */
#else
long ut_session; /* Session ID */
struct timeval ut_tv; /* Time entry was made */
#endif
int32_t ut_addr_v6[4]; /* Internet address of remote
host; IPv4 address uses
just ut_addr_v6[0] */
char __unused[20]; /* Reserved for future use */
};
当然,我后面的实现who命令时没有将所有的struct utmp的成员都用到的。
-
要使用的函数原型介绍
int fd=open(char *filename,int how);
ssize_t numread=read(int fd,void *buf,size_t qty);
int result=close(int fd); -
内核在who命令中的作用(这个是需要重点理解的)
1.在打开utmp文件的时候,我们使用的是open函数。注意,这个不是c标准库里的函数,是Linux系统函数,移植性不高。open函数是系统的调用,之前我们大多数写的程序都是进程的调用。例如:在你的test.c文件里,有一个functionA(),functionB(),都在主函数里被执行,此时,functionA(),functionB()就是都是进程在调用;了解过C++,java的人都知道,在他们中函数的调用是需要对象来完成的,但是c语言是面向过程的语言,是没有对象的。所以,一个函数是怎么就启动了捏?当然,就是test.c所形成的test.exe文件的这个进程所调用的了。回到主题,open()函数不是进程所调用,而是内核的调用,所以内核的作用就体现出来了。
2.在open一个文件后,自然就得去read它,不然打开就显得意义不大了。这里要强调的是open函数的返回值是一个文件描述符fd,不是c标准函数fopen的一个文件指针FILE *,其中的区别我就在这里细说了。而read()函数也恰恰需要这样一个文件指针。在unix/Linux编程实践教程一书上面对于文件描述符是这样说的:它就像一条由进程通向内核的管道 -
who01.c代码
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<utmp.h>
#include<time.h>
void show_info(struct utmp* utbufp);
void showtime(long timeval);
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)
{
if(utbufp->ut_type!=USER_PROCESS)
return;
printf("%-8.8s",utbufp->ut_name);
printf(" ");
printf("%-8.8s",utbufp->ut_line);
printf(" ");
showtime(utbufp->ut_time);
printf("\n");
}
void showtime(long timeval)
{
char *cp;
cp=ctime(&timeval);
printf("%12.12s",cp+4);
}
-
改进who01.c的方法—使用缓冲
1.为什么执行read方法耗时长?用户进程位于用户空间,内核位于系统空间,而磁盘只能被内核直接访问,并且文件的数据是存储在磁盘上面的。程序要读取磁盘上文件的数据就只能通过系统调用read()方法,而read方法的代码是在内核中,所以当read被调用的时候,执行权会从用户代码到内核代码,执行切换是需要时间的。
2.为了减少切换所需要的时间,所以我们使用一个缓冲数组,一次性读取20sizeof(struct utmp)字节的数据(当然,也不一定就是20个,只是比喻一下),然后,以后取数据就从缓冲数组里取出,直到缓冲数组的数据读取完毕了,就再调用read方法,从文件里将数据读取出来到缓冲数组里。下面给出who02.c的代码 -
who02.c代码
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<utmp.h>
#include<fcntl.h>
#include<time.h>
#include"utmplib.c"
void show_info(struct utmp*);
void showtime(time_t);
int main()
{
struct utmp* utbufp;
if(utmp_open(UTMP_FILE)==-1)
{
perror(UTMP_FILE);
exit(-1);
}
while((utbufp=utmp_next())!=((struct utmp*)NULL))
{
show_info(utbufp);
}
if(utbufp==NULL)
printf("指针为空\n");
utmp_close();
return 0;
}
void show_info(struct utmp* utbufp)
{
//printf("执行show_info\n");
if(utbufp->ut_type!=USER_PROCESS)
return;
printf("%-8.8s",utbufp->ut_name);
printf(" ");
printf("%-8.8s",utbufp->ut_line);
printf(" ");
showtime(utbufp->ut_time);
printf("\n");
}
void showtime(time_t timeval)
{
char *cp;
cp=ctime(&timeval);
printf("%12.12s",cp+4);
}
//这里给出的是utmplib.c的代码
#include<stdio.h>
#include<fcntl.h>
#include<sys/types.h>
#include<utmp.h>
#define NRECS 16
#define NULLUT ((struct utmp* )NULL)
#define UTSIZE (sizeof(struct utmp))
typedef struct utmp utmp;
//static utmp utmpbuf[NRECS*UTSIZE];
static char utmpbuf[NRECS*UTSIZE];
static int num_recs;
static int cur_rec;
static int fd_utmp=-1;
int utmp_open(char* filename)
{
fd_utmp=open(filename,O_RDONLY);
cur_rec=num_recs=0;
return fd_utmp;
}
struct utmp* utmp_next()
{
struct utmp* recp;
if(fd_utmp==-1)
return NULLUT;
if(cur_rec==num_recs && utmp_reload()==0)
return NULLUT;
recp=(struct utmp*)&utmpbuf[cur_rec*UTSIZE];
//recp=&utmpbuf[cur_rec*UTSIZE];
cur_rec++;
return recp;
}
int utmp_reload()
{
int amt_read;
amt_read=read(fd_utmp,utmpbuf,NRECS*UTSIZE);
printf("执行reload函数\n");
num_recs=amt_read/UTSIZE;
cur_rec=0;
return num_recs;
}
void utmp_close()
{
if(fd_utmp!=-1)
close(fd_utmp);
}
- 总结
1.这篇笔记主要就是想要说一个关于内核在who命令中的作用。
2.还有两个问题没有解决,如果有那个网友知道原因的话,欢迎私信我,或者下面留言,我们互相讨论。
问题一:在书本上使用的是系统自带的函数open,read之类的,我想问一下为什么不使用fopen,fread之类的函数。经过我的测试,我自己写了一个使用fopen函数来实现who命令的函数,也是可以使用的,所以我不清楚为什么书本上一定要使用open函数,还是说书本上就是为了引出缓冲的概念,从而使用的open之类的函数。
问题二:在使用缓冲区的时候,为什么使用的缓冲数组是char型,而不是struct utmp型。按照我的想法,我们是使用缓冲数组来存贮在utmp文件中的数据(并且一次读取n个sizeof(strcut utmp)),那使用struct utmp型数组来“装”不是情理之中的事情吗?为此,我在windows平台测试了一下,发现在windows下的devc++编译器是会报错了,因为
recp=(struct utmp*)&utmpbuf[cur_recUTSIZE];会报错,但是在Ubuntu下不会报错,并且如果你写成我在utmplib.c文件注释的那个样子,就得不到正确的结果了。(不同编译环境下会报错这个可以理解,但是为什么我把缓冲区写成static utmp utmpbuf[NRECSUTSIZE];就得不到正确的结果捏)。
3.这篇笔记是我作为一个Linux新手写的,可能会有很多纰漏之处,希望大神们不要嫌弃,还希望大神多多指教!