一、linux 系统中的时间
1、关于时间的概念
1、GMT时间(经度为0 的地区,时区是按照 东西方向划分的)
(1)GMT是世界时也就是格林尼治时间,也就是格林尼治地区的当地时间。
(2)GMT时间的意义?用格林尼治的当地时间作为全球国际时间,用以描述全球性的事件的时间,方便大家记忆。
(3)一般为了方便,一个国家都统一使用一个当地时间(中国是跨越了3个时区)。
2、UTC时间(表示 0 时区的时区,东为正,西为负 )
(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)就是和点时间有关的一个器件。
2、linux 系统中的时间
- jiffies的引入
jiffies 是linux 当中的一个全局变量,用来记录linux 内核节拍时间的变量,linux 系统启动的时候 jiffies 变量有一个基准值,然后内核每过一个节拍, jiffies 变量就会加 1。
- linux系统如何记录时间
(1)RTC 这个硬件,有备用电池,在断电的情况下也可以自己工作。所以这个时间是一直走的。
(2)在 linux 内核启动的时候,会自动读取 RTC 硬件的一个时间作为基准时间,这个基准时间对应一个 jiffies 的数值。
(3)jiffies 基准值的计算: RTC 读取的时间 —— 1970-01-01 00:00:00 +0000 (UTC) = 时间段, 然后将这个 时间段/tick = jiffies数值
(4)当前时间的计算: jiffies数值 ——> 一个时间段 + 1970-01-01 00:00:00 。
注:RTC 在操作系统运行的时候,RTC 是不起作用的, 时间的计算靠 jiffies 变量来完成, RTC 只有在开机(那一瞬间)和关机的时候起作用。
扫描二维码关注公众号,回复: 12410312 查看本文章![]()
(5)现代linux 当中的时间节拍(调度时间)一般是 10ms 或者是 1ms。
3、linux 当中有关时间的系统调用
常用的时间相关的API和C库函数有9个:time/ctime/localtime/gmtime/mktime/asctime/strftime/gettimeofday/settimeofday、
- time(API)
time_t time(time_t *t);
time() returns the time as the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC).
返回值是距离 1970-01-01 00:00:00 +0000 这个时间过去的 秒数。
参数分析:
time_t *t : 输出型变量,也相当于一个返回值。
总结:所以我们可以利用 输出型指针 或者 返回值来进行返回。
- ctime(C库函数)
char *ctime(const time_t *timep);
返回值:是一个字符串(char * 类型),表示一个固定的时间。
- gmtime(国际时间) 和 localtime(本地时间)会把time得到的秒数变成一个struct tm结构体表示的时间
国际时间:指 UTC 时间
本地时间:指当前系统设置的所在的时区的时间。(在设置里面可以进行设置)
struct tm *gmtime(const time_t *timep);
struct tm *localtime(const time_t *timep);
struct tm {
int tm_sec; /* seconds */
int tm_min; /* minutes */
int tm_hour; /* hours */
int tm_mday; /* day of the month */
int tm_mon; /* month */
int tm_year; /* year */
int tm_wday; /* day of the week */
int tm_yday; /* day in the year */
int tm_isdst; /* daylight saving time */
};
- mktime: 根据图表可以看出 mktime 是从结构体变为总秒数
time_t mktime(struct tm *tm);
-
如果从struct tm出发想得到字符串格式的时间,可以用asctime或者strftime都可以。
-
如果从time_t出发想得到字符串格式的时间用ctime即可
-
gettimeofday 函数 (API)
int gettimeofday(struct timeval *tv, struct timezone *tz);
输出型参数:返回两个结构体
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
and gives the number of seconds and microseconds since the Epoch (see time(2)). The tz argument is a struct timezone:
struct timezone {
int tz_minuteswest; /* minutes west of Greenwich */
int tz_dsttime; /* type of DST correction */
};
timeval:时间
timezone: 时区
- settimeofday (API)
int settimeofday(const struct timeval *tv, const struct timezone *tz);
输入型参数:
说明是在设置我们当前系统的时间。
二、时间相关的 API 实战
这张图:表现了各个时间格式之间的转换需要的函数。
1、time(API)—— 将 jifiees 转化为秒
一定要先理解这个 输出型参数。
time_t time(time_t *t);
参数分析:
time_t *t : 输出型变量,也相当于一个返回值。
总结:所以我们可以利用 输出型指针 或者 返回值来进行返回。
示例1、利用返回值来进行返回。
#include <time.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
time_t ret = 0;
ret = time(NULL);
printf("the second is %ld \n",ret);
return 0;
}
#include <time.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
time_t t_NOW = 0;
time(&t_NOW);
printf("the second is %ld \n",t_NOW);
return 0;
}
示例二:利用输入型参数(指针)
测试一下:
2、ctime 从time_t 出发得到一个容易观察的字符串格式的
char *ctime(const time_t *timep);
const time_t *timep :输入型参数(传入 变量的地址即可)
返回值:
char * :代表字符串
- 示例代码
#include <time.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
time_t t_NOW = 0;
time(&t_NOW);
printf("the second is %ld \n",t_NOW);
printf("%s \n", ctime(&t_NOW));
return 0;
}
3、gmtime 和 localtime 库函数的操作
注:gmtime是国际时间,也就是时区为0 的时间,localtime 是当地时间,中国算是东8区,所以要比 gmtime 快8小时。
#include <time.h>
#include <stdio.h>
struct tm *gmtime(const time_t *timep);
int main(int argc,char *argv[])
{
// time 函数变量定义
time_t *t_p = NULL;
time_t t = 0;
t_p = &t;
// ctime 函数变量定义
char *p_ctime = NULL;
// gmtime 函数变量的定义
struct tm *p_gmtime = NULL;
//time 函数应用
time(t_p);
printf("the second is %ld \n",*t_p);
//ctime 函数应用
p_ctime = ctime(t_p);
printf("%s\n",p_ctime);
//gmtime函数的应用
p_gmtime = gmtime(t_p);
printf("%d年. %d月. %d日\n",p_gmtime->tm_year,p_gmtime->tm_mon,p_gmtime->tm_mday);
return 0;
}
4、mktime(从结构体得到秒数)
注:
1、一般我们获得秒数不会通过 mktime 来获得。直接利用 time 就可以得到。
2、这个mktime是用来向操作系统设置时间时用的
5、strftime(用户自定义格式)
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
参数:
char *s :传入一个 buf ,用来存放我们的字符串。(输出型参数,这个buf会被填充)
size_t max :这个 buf 的大小
const char *format: 格式化字符串,用户可以自定义
const struct tm *tm :传入图中的结构体指针(获取一些时间元素)
//strftime 函数的应用
memset(buf,0,sizeof(buf));
strftime(buf,sizeof(buf),"%Y - %m - %d, %H-%M-%S.",p_gmtime);
printf("strftime:%s\n",buf);
#include <time.h>
#include <stdio.h>
#include <string.h>
int main(int argc,char *argv[])
{
// time 函数变量定义
time_t *t_p = NULL;
time_t t = 0;
t_p = &t;
// ctime 函数变量定义
char *p_ctime = NULL;
// gmtime 函数变量的定义
struct tm *p_gmtime = NULL;
// strftime 函数变量的定义
char buf[30];
//time 函数应用
time(t_p);
printf("the second is %ld \n",*t_p);
//ctime 函数应用
p_ctime = ctime(t_p);
printf("%s",p_ctime);
//gmtime函数的应用
p_gmtime = gmtime(t_p);
printf("%d年. %d月. %d日\n",p_gmtime->tm_year,p_gmtime->tm_mon,p_gmtime->tm_mday);
//mk_time 函数的应用
t = mktime(p_gmtime);
printf("%ld\n",t);
//strftime 函数的应用
memset(buf,0,sizeof(buf));
strftime(buf,sizeof(buf),"%Y - %m - %d, %H-%M-%S.",p_gmtime);
printf("strftime:%s\n",buf);
return 0;
}
6、gettimeofday
(1)前面讲到的基于time函数的那个系列都是以秒为单位来获取时间的,没有比秒更精确的时间。
(2)有时候我们程序希望得到非常精确的时间(譬如以us为单位),这时候就只能通过gettimeofday来实现了。
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <sys/time.h>
int main(int argc,char *argv[])
{
struct timeval tv = {
0};
struct timezone tz = {
0};
int ret = -1;
// 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;
}
注:-480:是以分为单位的时区。 480/60 =8
三、linux系统当中的随机数
1、随机数和伪随机数
(1)随机数是随机出现,没有任何规律的一组数列。真正的完全随机的数列是不存在的,只是一种理想情况。这个世界上任何东西都是有规律,只是规律很复杂,可能几十亿年才循环一次。
(2)我们平时要用到随机数时一般只能通过一些算法得到一个伪随机数序列。所以计算机中产生的都是伪随机数。(只是这个随机数可以满足我们的日常使用了)
注:我们平时说到随机数,基本都指的是伪随机数。
2、rand函数和srand函数
int rand(void);
直接利用返回值来得到这个随机数
int rand_r(unsigned int *seedp);
可重入函数(后面带一个 _r),既可以利用返回值,又可以利用 输出型指针来进行得到随机数。
void srand(unsigned int seed);
作为随机数的种子,一般利用当前时间来做种子。
注: The rand() function returns a pseudo-random integer in the range 0 to RAND_MAX
#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;
}
1、利用取余操作,可以来限制随机数的范围
- 种子的意义:为了执行程序可以产生不同的随机数
(1)当种子一直为1 的时候。即
srand(1);
(2) 当种子一直变化的时候。以时间为种子(time函数产生的秒)
- 在linux系统中获取真正的随机数
linux系统收集系统中的一些随机发生的事件的时间(譬如有人动鼠标,譬如触摸屏的操作和坐标等)作为随机种子去生成随机数序列。
五、proc 文件系统介绍(仅仅是介绍)
1、操作系通级别的调试
(1)简单程序单步调试(类似于单片机,利用 JLink 来进行单步调试)
(2)复杂程序 printf 打印信息调试
(3)框架体系 日志记录 信息调试(将printf 打印到一个日志文件当中)
举例:有的程序一开始不会有问题,出错间隔在几天(比如有的程序会因为吃内存出错)(短时间我们不容易发现)。
注:以上三种方法,是我们利用内核来进行调试(printf就是在内核当中实现的)如果我们内核本身出现错误。我们该怎么办呢?
(4)操作系统内核用虚拟文件系统调试
2、proc虚拟文件系统的工作原理
(1)像kernel这样庞大的项目,给里面添加/更改一个功能是非常麻烦的,因为你这添加的一个功能可能会影响其他已经有的。
早期内核版本中尽管调试很麻烦,但是高手们还可以凭借个人超凡脱俗的能力去驾驭。但是到了2.4左右的版本的时候,这个难度已经非常大了。
(2)为了降低内核调试和学习的难度,内核开发者们在内核中添加了一些属性专门用于调试内核,proc文件系统就是一个尝试。
- proc文件系统的思路是
(1)在内核中构建一个虚拟文件系统/proc , 让我们启动内核的时候,在根文件系统当中就会产生 /proc 这个文件夹。
(2)内核运行时将内核中一些关键的数据结构以文件的方式呈现在/proc目录中的一些特定文件中,这样相当于将不可见的内核中的数据结构以可视化的方式呈现给内核的开发者。
- proc文件系统给了开发者一种调试内核的方法:
我们通过实时的观察/proc/xxx文件,来观看内核中特定数据结构的值。在我们添加一个新功能的前后来对比,就可以知道这个新功能产生的影响对还是不对。
注:
1、proc 目录下的问价,本身并不存在于硬盘当中,他也不是一个真实文件。它只是一个接口
2、当我们去读取这个文件的时候,内核不会去硬盘当中找这个文件。
3、尽管我们 ls 看到了一些文件,但是这个是内核从它本身的数据结构转化为字符串来呈现出来。
3、proc文件系统的使用
1、cat以手工查看常用proc中的文件
(1)/proc/cmdline
(2)/proc/cpuinfo (该硬件 cpu 的信息)
(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}; // buf 用来记录字符串
//使用说明:./a.out 后面跟-v|-d 参数,代表 version 和 device
if (argc != 2)
{
printf("usage: %s -v|-d \n", argv[0]);
return -1;
}
// 第一种情况:如果参数是 -v ,则打开 /proc/version 文件
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文件系统
- sys文件系统和proc文件系统的异同:
(1)sys文件系统本质上和proc文件系统是一样的,都是虚拟文件系统,都在根目录下有个目录(一个是/proc目录,另一个是/sys目录),因此都不是硬盘中的文件,都是内核中的数据结构的可视化接口。
(2)不同的是/proc中的文件只能读,但是/sys中的文件可以读写。
读/sys中的文件就是获取内核中数据结构的值,而写入/sys中的文件就是设置内核中的数据结构的元素的值。
- 注
(1)历史上刚开始先有/proc文件系统,人们希望通过这种技术来调试内核。实际做出来后确实很有用,
所以很多内核开发者都去内核调价代码向/proc目录中写文件,而且刚开始的时候内核管理者对proc目录的使用也没有什么经验也没什么统一规划,后来的结果就是proc里面的东西又多又杂乱。
(2)后来觉得proc中的内容太多太乱缺乏统一规划,于是乎又添加了sys目录。sys文件系统一开始就做了很好的规划和约定,所以后来使用sys目录时有了规矩。
(3)因为 proc 文件系统已经在内核当中合理使用,所以我们后来并没有砍掉。只是添加了sys文件系统,sys 文件系统提前做了很好的规划。