Linux编程实战读书笔记 第2章
前言:该章展示了联机帮助的作用和使用方法,linux中文件操作相关的接口函数、参数知识,内核模式和用户模式,系统调用的代价等;最后自己编写实现一个简单的who命令。
2.2 命令who
上图最大的长方形为计算机内存,分为用户空间和系统空间;三个较小的长方体为应用程序,运行在用户空间,而底部的长方形为内核程序,应用程序通过内核与外界进行通信;最底部两个圆柱体为两个硬盘,一些小的管道则为通信管道;其他为一些外部设备,如打印机、屏幕、键盘等。
学习who命令则要了解三个问题:
1. who命令能干啥
2. who命令怎么工作的
3. 如何编写who
命令也是程序
2.3 问题1:who能干啥
在命令行打who,便会得到现在系统中登陆的用户。
还可以通过man who阅读who的手册,在手册中会有详细的描述,其的作者、使用、位置等。
2.4 问题2:who是如何工作的?
书中使用的系统是SunOS平台,本人使用的ubuntu平台,who手册中描述的没有那么详细,如下图:
再图中提到两个文件,/var/run/utmp,/var/log/wtmp,跟书中的位置不同,文件名称相同,已登陆的用户信息会存放在/var/run/utmp,通过联机帮助来了解该文件的结构信息。
命令行输入man -k utmp则可以查找“utmp”相关的信息,如图:
书中会查看utmp(4),但是ubuntu没有这个,则选择utmp(5),查看其帮助内容,在命令行输入man 5 utmp,如图:
在内容里面是utmp.h文件内容,utmp结构体也在其中,可以很好了解该数据结构包含的信息,因为已经过了很多迭代,与书中的改变已经很大,但结构体中的成员还是差不多的。
结构体中每个成员都会有相应的注释,可以解读到用户u名、用户登陆设备等信息。
根据上述总结,who的工作流程如图:
接下来则可以通过读取utmp文件获取登陆信息数据,最后编写一个简单的who命令。
2.5 编写who
知道要对utmp文件进行读取,这时候先去了解文件操作的read、open、close函数的使用。想了解file和read相关的帮助,可以在命令行输入man -k file | grep read,就会输出文件read相关的接口。
如图中的read(2)就是相关函数,在输入man 2 read,则可阅读相关帮助。
在帮助中就有函数描述,怎么调用,返回值有什么意义。open,close,read三个函数就是实现的基本,阅读他们的帮助。
open函数注意:内核允许多进程访问文件,多次打开是允许的,多次打开对应的文件描述符也不相同。read函数读取数据就会返回读取的数据大小
编写时需多查看文档,其中liunx中的utmp结构体中时间结构体为timeval结构体,使用man -k time | grep timeval可查看相关帮助,其结构为
linux中对于时间戳有相应的转换函数ctime函数,可以将timeval结构体中的tv_sec成员变为字符串。
最后代码为:
/*who1.c - who命令初版
* open, read UTMP file,and show results
* 版本2:只显示用户类型、同时显示正确时间格式
*/
#include <stdio.h>
#include <stdlib.h>
#include <utmp.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#define SHOWHOST //include remote machine on output
//登陆信息展示函数
void show_info(struct utmp *utbufp);
//登陆时间显示函数
void show_time(time_t tv_sec);
int main(){
struct utmp current_record; //读出的信息保存在该结构体
int utmpfd; //文件操作符
int reclen = sizeof(current_record);//每次读出utmp结构体大小数据
if((utmpfd = open(UTMP_FILE,O_RDONLY))==-1){//打开utmp文件
perror(UTMP_FILE); //UTMP_FILE is in utmp.h
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_user);//用户名
printf(" ");
printf("%-8.8s",utbufp->ut_line);//用户登陆设备名
printf(" ");
show_time(utbufp->ut_tv.tv_sec);//展示登陆时间
#ifdef SHOWHOST
printf("(%s)",utbufp->ut_host);//登陆主机地址
#endif
printf("\n");
}
void show_time(time_t tv_sec){
char *cp;
cp = ctime(&tv_sec);//将时间戳变为字符串
//大概为Mon Feb 4 00:46:40 EST 1991这样的格式
printf("%15.24s",cp);//简单打印时间
printf(" ");
}
2.6 编写cp
cp命令是再熟悉不过了,cp即复制,该命令会使用文件操作的create函数进行创建新文件。
create函数创建文件时会传入两个参数,一个是文件名如果文件名不存在则创建新的,如果存在则将存在的文件清空,文件长度为0;第二个为文件的操作许可模式,设置可读可写的等级,创建成功会返回一个文件操作符。当然还需要write函数进行写入,编写流程如下:
这时候编写比较简单,就是打开读然后写到新的文件中去,再ubuntu中cp如果第二个参数是路径则可以将文件复制过去。
2.7 提高文件I/O效率的方法:使用缓冲
系统调用需要时间,使用缓冲就是利用空间换取时间,如图所示为系统调用控制流图。
当读取磁盘数据时需要调用read代码。read代码在内核中,所以当read调用时,执行权会从用户代码到内核代码中。
用户态到内核态需要切换一些特殊的堆栈和内存环境,所以系统调用的开销大。
对于之前所编写who2.c中是十分低效的,每次只拿取一个用户的信息,然后打印完成后在去取下一个的,这里可以与cp类似使用缓存事先读取多个用户信息。
根据书中的代码编写,本人添加了头文件这样编写时候方便,代码简单就不贴出来。
2.8 内核缓存技术
磁盘的I/O操作消耗的时间更多,为了提高效率,内核也是用缓冲技术来提高对磁盘的访问速度,如图所示:
内核将磁盘上的数据块复制到内核缓冲区中,当一个用户空间中的进程要从磁盘读取数据是,内核一般不直接读磁盘,而是将内核缓冲区的数据复制到进程的缓冲区去。
可能进程访问的数据块不在内核缓冲区,内核会将相应的数据块加入到请求数据队列表中,先将进程挂起;将数据块读到缓冲区后,再把数据复制到进程的缓冲区,最后唤醒被挂起的进程。读和写都会由缓存区这么一个步骤,写需要到一定数量才会一次写入;断电可能会导致缓存器清空,内核来不及将数据写入。
之后章节中会展示文件读写中的lseek用于定位文件指针,还有处理系统错误,在之前的编写中对于错误的处理已经有了。