Linux应用编程和网络编程(4)------- 系统信息的获取


一,关于时间的概念

1、GMT时间
(1)GMT是世界时也就是格林尼治时间,也就是格林尼治地区的当地时间。
(2)GMT时间的意义?用格林尼治的当地时间作为全球国际时间,用以描述全球性的事件的时间,方便大家记忆。
(3)一般为了方便,一个国家都统一使用一个当地时间。

2、UTC时间
(1)GMT时间是以前使用的,近些年来越来越多的使用UTC时间。
(2)整个地球分为二十四时区,每个时区都有自己的本地时间。在国际无线电通信场合,为了统一起见,使用一个统一的时间,称为通用协调时(UTC, Universal Time Coordinated)。北京时区是东八区领先UTC八个小时UTC + 时区差 = 本地时间
时区差东为正,西为负。在此,把东八区时区差记为 +0800, UTC + (+0800) = 本地(北京)时间 。那么,UTC = 本地时间(北京时间))- 0800 。例如0942 - 0800 = 0142 ,即UTC是当天凌晨一点四十二分二十二秒。如果结果是负数就意味着是UTC前一天,把这个负数加上2400就是UTC在前一天的时间。例如,本地 (北京)时间是 0432 (凌晨四点三十二分),那么,UTC就是 0432 - 0800 = -0368,负号意味着是前一天, -0368 + 2400 = 2032,既前一天的晚上八点三十二分。

可以参考:跟林尼治时间UTC

3、计算机中与时间有关的
(1)点时间和段时间。段时间=点时间-点时间
(2)定时器和实时时钟。定时器(timer)定的时间就是段时间,实时时钟(RTC)就是和点时间有关的一个器件。


二,linux系统中的时间

1、jiffies的引入

(1)jiffies是linux内核中的一个全局变量,jiffies记录了系统启动以来,经过了多少tick(系统时间片)。一个tick代表多长时间,在内核的CONFIG_HZ中定义。比如CONFIG_HZ=1000,则一个tick对应1ms时间。所以内核基于jiffies的定时器精度也是1ms,实际上linux内核的调度系统工作时就是以这个节拍时间为时间片的。
(2)jiffies变量开机时有一个基准值,然后内核每过一个节拍时间(tick)jiffies就会加1,然后到了系统的任意一个时间我们当前时间就被jiffies这个变量所标注。

2、linux系统如何记录时间
(1)内核在开机启动的时候会读取RTC硬件获取一个时间作为初始基准时间,这个基准时间对应一个jiffies值(这个基准时间换算成jiffies值的方法是:用这个时间减去1970-01-01 00:00:00 +0000(UTC),然后把这个时间段除以tick换算成jiffies数值),这个jiffies值作为我们开机时的基准jiffies值存在。然后系统运行时每个时钟节拍的末尾都会给jiffies这个全局变量加1,因此操作系统就使用jiffies这个全局变量记录了下来当前的时间。当我们需要当前时间点时,就用jiffies这个时间点去计算(计算方法就是先把这个jiffies值对应的时间段算出来,然后加上1970-01-01 00:00:00 +0000(UTC)即可得到这个时间点)。

(2)其实操作系统只在开机时读一次RTC,整个系统运行过程中RTC是无作用的。RTC的真正作用其实是在OS的2次开机之间进行时间的保存。
(3)jiffies这个变量记录的其实是段时间(其实就是当前时间和1970-01-01 00:00:00 +0000(UTC)这个时间的差值)。

3、linux中时间相关的系统调用
常用的时间相关的API和C库函数有9个:time/ctime/localtime/gmtime/mktime/asctime/strftime/gettimeofday/settimeofday

  • time系统调用返回当前时间以秒为单位的距离1970-01-01 00:00:00+0000(UTC)过去的秒数。这个time内部就是用jiffies换算得到的秒数。其他函数基本都是围绕着time来工作的。
  • gmtime和localtime把time得到的秒数变成一个struct tm结构体表示的时间。区别是gmtime得到的是国际时间,而localtime得到的是本地(指的是你运行localtime函数的程序所在的计算机所设置的时区对应的本地时间)时间。mktime用来完成相反方向的转(struct tm到time_t)。
  • 如果从struct tm出发想得到字符串格式的时间,可以用asctime或者strftime都可以。(如果从time_t出发想得到字符串格式的时间用ctime即可
  • gettimeofday返回的时间是由struct timeval和struct timezone这两个结构体来共同表示的,其中timeval表示时间,而timezone表示时区。settimeofday是用来设置当前的时间和时区的

在这里插入图片描述

总结:不管用哪个系统调用,最终得到的时间本质上都是一个时间(这个时间最终都是从kernel中记录的jiffies中计算得来的),只不过不同的函数返回的时间的格式不同,精度不同。


三,时间相关API实战

在这里插入图片描述

1、time
(1)time能得到一个当前时间距离标准起点时间1970-01-01 00:00:00 +0000(UTC)过去了多少秒

2、ctime
(1)ctime可以从time_t出发得到一个容易观察的字符串格式的当前时间。
(2)ctime好处是很简单好用,可以直接得到当前时间的字符串格式,直接打印来看。坏处是ctime的打印时间格式是固定的,没法按照我们的想法去变。
(3)实验结果可以看出ctime函数得到的时间是考虑了计算机中的本地时间的(计算机中的时区设置)。

3、gmtime和localtime
(1)gmtime获取的时间中:年份是以1970为基准的差值,月份是0表示1月,小时数是以UTC时间的0时区为标准的小时数(北京是东8区,因此北京时间比这个时间大8)
(2)猜测localtime和gmtime的唯一区别就是localtime以当前计算机中设置的时区为小时的时间基准,其余一样。实践证明我们的猜测是正确的。

1-3实验代码:

#include <stdio.h>
#include <time.h>
#include <string.h>



int main(void)
{
	time_t tNow = -1;
	struct tm tmNow;
	
	// time
	//tNow = time(NULL);		// 返回值
	time(&tNow);				// 指针做输出型参数
	if (tNow < 0)
	{
		perror("time");
		return -1;
	}
	printf("time: %ld.\n", tNow);
	
	// ctime
	printf("ctime: %s\n", ctime(&tNow));
	
	// gmtime 和localtime
	memset(&tmNow, 0, sizeof(tmNow));
	gmtime_r(&tNow, &tmNow);
	printf("gmtime:%d年%d月%d日%d时.\n", tmNow.tm_year, tmNow.tm_mon, tmNow.tm_mday, tmNow.tm_hour);
	
	memset(&tmNow, 0, sizeof(tmNow));
	localtime_r(&tNow, &tmNow);
	printf("localtime:%d年%d月%d日%d时.\n", tmNow.tm_year, tmNow.tm_mon, tmNow.tm_mday, tmNow.tm_hour);
	
	
	return 0;
}

实验结果:

在这里插入图片描述

4、mktime
(1)从OS中读取时间时用不到mktime的,这个mktime是用来向操作系统设置时间时用的。

5、asctime
(1)asctime得到一个固定格式的字符串格式的当前时间,效果上和ctime一样的。区别是ctime从time_t出发,而asctime从struct tm出发。

6、strftime
(1)asctime和ctime得到的时间字符串都是固定格式的,没法用户自定义格式
(2)如果需要用户自定义时间的格式,则需要用strftime。

7、gettimeofday和settimeofday
(1)前面讲到的基于time函数的那个系列都是以秒为单位来获取时间的,没有比秒更精确的时间。
(2)有时候我们程序希望得到非常精确的时间(譬如以us为单位),这时候就只能通过gettimeofday来实现了。

示例代码:

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <sys/time.h>


int main(void)
{
	time_t tNow = -1;
	struct tm tmNow;
	char buf[100];
	struct timeval tv = {0};
	struct timezone tz = {0};
	int ret = -1;
	
	// time
	//tNow = time(NULL);		// 返回值
	time(&tNow);				// 指针做输出型参数
	if (tNow < 0)
	{
		perror("time");
		return -1;
	}
	printf("time: %ld.\n", tNow);
	
	// ctime
	printf("ctime: %s.\n", ctime(&tNow));
	// strftime
	memset(&tmNow, 0, sizeof(tmNow));
	localtime_r(&tNow, &tmNow);
	printf("年%d月%d日%d时%d.\n", tmNow.tm_year, tmNow.tm_mon, tmNow.tm_mday, tmNow.tm_hour);
	
	memset(buf, 0, sizeof(buf));
	strftime(buf, sizeof(buf), "%Y * %m * %d, %H-%M-%S.", &tmNow);
	printf("时间为:[%s].\n", buf);


	// gettimeofday
	ret = gettimeofday(&tv, &tz);
	if (ret < 0)
	{
		perror("gettimeofday");
		return -1;
	}
	printf("seconde: %ld.\n", tv.tv_sec);
	printf("timezone:%d.\n", tz.tz_minuteswest);
	
	
	return 0;
}

四,linux中使用随机数

1、随机数和伪随机数
(1)随机数是随机出现,没有任何规律的一组数列。
(2)真正的完全随机的数列是不存在的,只是一种理想情况。我们平时要用到随机数时一般只能通过一些算法得到一个伪随机数序列。
(3)我们平时说到随机数,基本都指的是伪随机数。

2、linux中随机数相关API
(1)连续多次调用rand函数可以返回一个伪随机数序列
(2)srand函数用来设置rand获取的伪随机序列的种子

在这里插入图片描述

3、实战演示
(1)单纯使用rand重复调用n次,就会得到一个0-RAND_MAX之间的伪随机数,如果需要调整范围,可以得到随机数序列后再进行计算。
(2)单纯使用rand来得到伪随机数序列有缺陷,每次执行程序得到的伪随机序列是同一个序列,没法得到其他序列
(3)原因是因为rand内部的算法其实是通过一个种子(seed,其实就是一个原始参数,int类型),rand内部默认是使用1作为seed的,种子一定的算法也是一定的,那么每次得到的伪随机序列肯定是同一个。
(4)所以要想每次执行这个程序获取的伪随机序列不同,则每次都要给不同的种子。用srand函数来设置种子
示例代码:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
	int i = 0, val = 0;
		
	printf("RAND_MAX = %d.\n", RAND_MAX);		// 2147483647
	
	srand(time(NULL));                       //用时间去作为随机数的种子
	for (i=0; i<6; i++)
	{
		val = rand();
		printf("%d ", (val % 6));
	}
	printf("\n");
	
	return 0;
}

实验结果:
在这里插入图片描述

4、总结和说明
(1)在每次执行程序时,先用srand设置一个不同的种子,然后再多次调用rand获取一个伪随机序列,这样就可以每次都得到一个不同的伪随机序列。
(2)一般常规做法是用time函数的返回值来做srand的参数。

5、在linux系统中获取真正的随机数
(1)linux系统收集系统中的一些随机发生的事件的时间(譬如有人动鼠标,譬如触摸屏的操作和坐标等)作为随机种子去生成随机数序列。


五,proc文件系统介绍与使用

1.proc文件系统介绍

1、从简单到复杂的调试
(1)简单程序单步调试
(2)复杂程序printf打印信息调试
(3)框架体系日志记录信息调试
(4)操作系统内核用虚拟文件系统调试

2、proc虚拟文件系统的工作原理
(1)linux内核是一个非常庞大、非常复杂的一个单独的程序,对于这样的一个程序来说调试是非常复杂的。
(2)像kernel这样庞大的项目,给里面添加/更改一个功能是非常麻烦的,因为你这添加的一个功能可能会影响其他已经有的。
(3)早期内核版本中尽管调试很麻烦,但是高手们还可以凭借个人超凡脱俗的能力去驾驭。但是到了2.4左右的版本的时候,这个难度已经非常大了。
(4)为了降低内核调试和学习的难度,内核开发者们在内核中添加了一些属性专门用于调试内核,proc文件系统就是一个尝试。

(5)proc文件系统的思路是:在内核中构建一个虚拟文件系统/proc,内核运行时将内核中一些关键的数据结构以文件的方式呈现在/proc目录中的一些特定文件中,这样相当于将不可见的内核中的数据结构以可视化的方式呈现给内核的开发者。

(6)proc文件系统给了开发者一种调试内核的方法:我们通过实时的观察/proc/xxx文件,来观看内核中特定数据结构的值。在我们添加一个新功能的前后来对比,就可以知道这个新功能产生的影响对还是不对。

(7)proc目录下的文件大小都是0,因为这些文件本身并不存在于硬盘中,也不是一个真实文件,他只是一个接口,当我们去读取这个文件时,其实内核并不是去硬盘上找这个文件,而是映射为内核内部一个数据结构被读取并且格式化成字符串返回给我们。所以尽管我们看到的还是一个文件内容字符串,和普通文件一样的;但是实际上我们知道这个内容是实时的从内核中数据结构来的,而不是硬盘中来的。
在这里插入图片描述

2.proc文件系统的使用

1、cat以手工查看常用proc中的文件
(1)/proc/cmdline
(2)/proc/cpuinfo
(3)/proc/devices
(4)/proc/interrupts等…
在这里插入图片描述

2、程序中可以文件IO访问
示例代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
	int fd = -1;
	char buf[512] = {0};
	
	if (argc != 2)
	{
		printf("usage: %s -v|-d\n", argv[0]);
		return -1;
	}
	
	if (!strcmp(argv[1], "-v"))
	{
		fd = open("/proc/version", O_RDONLY);
		if (fd < 0)
		{
			perror("open /proc/version");
			return -1;
		}
		read(fd, buf, sizeof(buf));
		printf("结果是:%s.\n", buf);
	}
	else if (!strcmp(argv[1], "-d"))
	{
		fd = open("/proc/devices", O_RDONLY);
		if (fd < 0)
		{
			perror("open /proc/devices");
			return -1;
		}
		read(fd, buf, sizeof(buf));
		printf("结果是:%s.\n", buf);
	}
	
	return 0;
}

实验结果:

在这里插入图片描述

3、在shell程序中用cat命令结合正则表达式来获取并处理内核信息

4、扩展:sys文件系统
(1)sys文件系统本质上和proc文件系统是一样的,都是虚拟文件系统,都在根目录下有个目录(一个是/proc目录,另一个是/sys目录),因此都不是硬盘中的文件,都是内核中的数据结构的可视化接口。
(2)不同的是**/proc中的文件只能读,但是/sys中的文件可以读写**。读/sys中的文件就是获取内核中数据结构的值,而写入/sys中的文件就是设置内核中的数据结构的元素的值。
(3)历史上刚开始先有/proc文件系统,人们希望通过这种技术来调试内核。实际做出来后确实很有用,所以很多内核开发者都去内核调价代码向/proc目录中写文件,而且刚开始的时候内核管理者对proc目录的使用也没有什么经验也没什么统一规划,后来的结果就是proc里面的东西又多又杂乱。
(4)后来觉得proc中的内容太多太乱缺乏统一规划,于是乎又添加了sys目录。sys文件系统一开始就做了很好的规划和约定,所以后来使用sys目录时有了规矩。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值