浅析linux应用编程系统信息的获取
前言
本小节分享的是linux应用编程中一些系统信息获取的使用,时间、随机数、proc文件系统等。
一、关于时间的概念
1.1 GMT时间
GMT,即格林尼治标准时间,也就是世界时。GMT的正午是指当太阳横穿格林尼治子午线(本初子午线)时的时间。但由于地球自转不均匀不规则,导致GMT不精确,现在已经不再作为世界标准时间使用。
1.2 UTC时间
UTC是以原子时秒长为基础,在时刻上尽量接近于GMT的一种时间计量系统。为确保UTC与GMT相差不会超过0.9秒,在有需要的情况下会在UTC内加上正或负闰秒。UTC现在作为世界标准时间使用。
1.3 本地时间
本地时间 = UTC + 时区差 时区差:东为正,西为负。在此,把东八区时区差记为 +0800
本地(北京)时间 = UTC + (+0800)
UTC = 本地时间(北京时间))- 0800
1.3 点时间和段时间
段时间 = 点时间 - 点时间
1.4 定时器和实时时钟
定时器定的时间就是段时间,实时时钟(RTC)就是和点时间相关的一个器件.
二、Linux系统中的时间
2.1 jiffies 的引入
(1)jiffies 是Linux内核中的一个全局变量,这个变量就是用来记录Jiffies的节拍时间为单位时间长度的一个数值。 (2)内核配置的时候定义了一个节拍时间,实际上linux内核的调度系统工作的时间就是以这个节拍时间为时间片的。 (3)jiffies变量开机时有一个基准值,然后内核每过一个节拍时间jiffies就会加1 ,然后到了系统的任意一个时间我们当前时间就被jiffies这个变量所标注。 (4) 内核在开机启动的时候会读取RTC硬件获取一个时间作为初始基准时间,这个基准时间对应的一个Jiffies 值(这个基准时间换算成Jiffies值的方法是:用这个时间减去1970-01-01 00:00:00 + 0000 (UTC),然后把这个时间段换算成JIffies数值),这个Jiffies值作为我们开机的基准Jiffies值存在,然后系统运行时每个时钟节拍的末尾都会给Jiffies这个全局变量加1 ,因此存在系统就使用Jiffies这个时间点去计算(计算方法就是先把这个Jiffies值对应的时间段算出来,然后加上1970-01-01 00:00:00 +0000 (UTC)则得到这个时间点)。 (5)操作系统只在开机时读取一次RTC,整个系统运行过程中RTC是无作用的,RTC的真正作用其实是在OS的2次开机之间进行时间的保存。Jiffies这个变量记录的其实是短时间(其实就是当前时间和基准时间的差值) (6)一个时间节拍的时间取决于操作系统的配置,现代linux系统一般都是10Ms或者1ms这个时间就是调度时间,在内核中用Hz来记录和表示,如果HZ定义成1000则时钟节拍就是1/hz 也就是1ms 。
2.2 linux中时间相关的系统调用
(1)常用的时间相关API和库函数 time/ctime/localtime/gmtime/mktime/asctime/strftime/gettimeofday/settimeofday (2)time系统调用返回当前时间以秒为单位的距离1970-01-01 00:00:00 +0000(UTC)过去的秒数。这个time内部就是Jiffies换算得到的秒数,其它函数基本都是围绕Time来工作的 。 (3) gmtime和localtime会把time的到的秒数变成一个struct tm结构体表示的时间,区别是gmtime得到的是国际时间,而localtime得到的是本地时间,mktime用来完成相反方向的转换(struct tm到time_t) (4) 如果从strutc tm出发想得到字符串格式的时间用asctime或者strftime都可以 (5)gettimeofday返回的时间是由struct timeval 和struct.settimeofday用来设置当前的时间和时区的 总结:不管用那个系统调用,最终得到的时间本质上都是一个时间(这个时间最终都是Kernel中记录的Jiffies中计算得来的)只不过不同的函数返回的时间的格式不同。
三、时间相关API实战
time_t time(time_t *tloc);
char *ctime(const time_t *timep);
char *asctime(const struct tm *tm)
char *asctime_r(const struct tm *tm, char *buf);
char *ctime(const time_t *timep);
char *ctime_r(const time_t *timep, char *buf);
struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *timep, struct tm *result);
struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);
time_t mktime(struct tm *tm);
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
int gettimeofday(struct timeval *tv, struct timezone *tz);
int settimeofday(const struct timeval *tv, const struct timezone *tz);
1、time
time能得到一个当前时间距离标准起点时间过去了多少秒 ctime好处是简单好用,可以直接得到当前时间的字符串格式直接打印出来,坏处是时间的格式是固定的。ctime函数得到的时间是考虑了计算机
2、 gmtime and localtime
gmtime获取的时间中:年份是以1970Wie基准的差值,月份是0表示1月小时数是以UTC时间的0时区为标志的小时数(北京是东8区,因此北京时间比这个大8) localtime和gmtime的唯一区别就是localtime以当前计算机中设置的时区为小时基准,其余一样。
3、mktime
从os中读取时间时用不到mktime,这个Mktime是用来向操作系统设置时间时用的。
4、asctime
asctime 得到一个固定格式的字符串当前时间,效果上和ctime是一样的,区别是ctime从time_t出发,asctime 从struct tm出发。
5、strftime
asctime 和ctime得到的时间字符串格式都是固定的,没办法用户自定义 strftime得到的时间可以用户自定义
6、gettimeofday
前面所获取的时间最小以秒为单位而gettimeofday可以用更小单位的时间值
demo
#include<stdio.h>
#include <time.h>
#include <error.h>
#include <string.h>
#include <sys/time.h>
int main(void)
{
int ret = -1;
time_t tnow = 0;
tnow = time(NULL);
char buf[128];
struct tm tmnow;
struct timeval tv ;
struct timezone tz;
if(tnow < 0)
{
perror("time");
return -1;
}
printf("time:%ld\n",tnow);
printf("ctime: %s\n",ctime(&tnow));
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);
memset(&tmnow,0,sizeof(tmnow));
gmtime_r(&tnow,&tmnow);
printf("asctime:%s \n",asctime(&tmnow));
memset(&tmnow,0,sizeof(tmnow));
localtime_r(&tnow,&tmnow);
memset(buf,0,sizeof(buf));
strftime(buf,sizeof(buf),"[%Y-%m-%d,%H-%M-%S]",&tmnow);
printf("strftime:%s \n",buf);
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);
}
实验结果
四、随机数
int rand(void);
int rand_r(unsigned int *seedp);
void srand(unsigned int seed);
1、随机数是随机出现的,没有任何规律的一组数列 2、真正的随机数是不存在,只是一种理想情况,我们平时用到的随机数一般只能通过一些算法得到一个伪随机数序列 3、在linux中连续多次调用rand函数可以返回一个伪随机数序列 4、srand函数用来设置
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i = 0,ret = 0;
printf("RAND_MAX = %d\n",RAND_MAX);
for(i = 0;i< 6;i++)
{
ret = rand();
printf("[%d] ",ret);
}
printf("\n");
return 0;
}
运行结果
RAND_MAX = 2147483647
[1804289383] [846930886] [1681692777] [1714636915] [1957747793] [424238335]
从上面的结果我们可以看出系统随机产生的数字都很大如果我们想要产生的随机数比较小,我们可以使用下面这种方式产生我们想要范围的随机数。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i = 0,ret = 0;
printf("RAND_MAX = %d\n",RAND_MAX);
for(i = 0;i< 6;i++)
{
ret = rand()%32;
printf("[%d] ",ret);
}
printf("\n");
return 0;
}
运行结果
RAND_MAX = 2147483647
[7] [6] [9] [19] [17] [31]
多次运行同一个程序
通过上面两个demo实验 可以看出rand的缺陷,每次执行同一个程序的时候产生的伪随机数序列是同一个序列,没法得到其他序列。 原因是rand内部的算法其实是通过一个种子(seed)其实就是一个原始参数int类型,rand内部默认是使用1,种子和算法都是一定的,所以每次得到的都是同一个随机序列。 解决方式: 通过srand来设置种子产生不同的种子这样在调用的时候就可以产生不同的序列。 一般做法是用time函数的返回值来做srand的参数。
通过传参 传入不同的种子
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc,char *argv[])
{
int i = 0,ret = 0;
printf("RAND_MAX = %d\n",RAND_MAX);
if(argc != 2)
{
printf("usage:%s num\n",argv[0]);
return -1;
}
srand(atoi(argv[1]));
for(i = 0;i< 6;i++)
{
ret = rand()%32;
printf("[%d] ",ret);
}
printf("\n");
return 0;
}
实验结果
通过time传入不同的种子
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc,char *argv[])
{
int i = 0,ret = 0;
printf("RAND_MAX = %d\n",RAND_MAX);
srand(time(NULL));
for(i = 0;i< 6;i++)
{
ret = rand()%32;
printf("[%d] ",ret);
}
printf("\n");
return 0;
}
实验结果
五、proc文件系统
5.1 操作系统级别的调试
1)简单程序的单步调试 2)复杂程序printf打印信息调试 3)框架体系日志记录信息调试
5.2 proc虚拟文件系统的工作原理
1) linux内核是一个非常庞大、非常复杂的一个单独的程序,对于这样的一个程序来说调试是非常复杂的。 2)像kernel这样庞大的项目,给里面添加或者更改一个功能是非常麻烦的,因为添加的功能可能会影响其他已有的功能。 3)为了降低内核调试和学习的难度,内核开发者在内核中添加了一些属性专门用于内核调试,proc文件系统就是一个尝试。 4)proc文件系统的思路是:在内核中构建一个虚拟文件系统/proc ,内核运行时将内核中一些关键的数据结构以文件的方式呈现在/proc目录中的一些特定文件中,这样相当于将不可见的内核中数据结构以可视化的方式呈现给内核开发者。 5)proc文件系统给开发者一种调试内核的方式,我们通过实时的观察/proc/xxx文件,来观看内核中特定数据结构的数值。我们可以通过前后对比来检测我们是否出错。
5.3 proc文件系统使用
1)cat 手动查看 2)程序中可以文件IO访问
cat /proc/devices
cat /proc/version
.
.
.
/*====================IO方式访问================*/
int main(int argc,char *argv[])
{
int fd = -1;
char buf[1024];
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");
return -1;
}
read(fd,buf,sizeof(buf));
printf("result:%s\n",buf);
}
else if(!strcmp(argv[1] ,"-d"))
{
fd = open("/proc/devices",O_RDONLY);
if(fd < 0)
{
perror("open");
return -1;
}
read(fd,buf,sizeof(buf));
printf("result:%s\n",buf);
}
}
sys文件系统
1)sys文件系统本质上和Proc文件系统是一样的,都会虚拟文件系统,都是在根目录下有个目录(一个是/proc目录一个是/sys目录)因此都不是硬盘的文件。都是内核中的数据结构的可视化接口 2)不同的是/proc文件系统只能读,但是/sys中的文件可以读写。读/sys中的文件就是获取内核中数据结构的值,而写入/sys中的文件就是设置内核中的数据结构中元素的值。
以上就是关于linux应用编程中系统信息的获取,加油勒
|