• 浅谈Linux系统信息与资源


    大家将来应用开发Linux程序,无论是ARM架构的板子,还是在Linux上开发应用程序,相信大家都会用到到一些系统相关的信息,譬如时间、日期、以及其它一些系统相关信息,今天带大家了解一下如何通过 Linux 系统调用或 C 库函数获取系统信息,譬如获取系统时间、日期以及设置系统时间、日期等。

    除此之外,还会向大家介绍 Linux 系统下的/proc 虚拟文件系统,包括/proc 文件系统是什么以及如何从/proc 文件系统中读取系统、进程有关信息。

    1. 系统信息

    1.1 系统标识 uname

    大家一看这几个字母,相信很快就能意识到这个是关于名称的意思。解释一下:这是系统调用 uname()用于获取有关当前操作系统内核的名称和信息的函数。
    同样,可以根据:

    man 2 uname
    
    • 1

    在这里插入图片描述

    #include 
    
    int uname(struct utsname *buf);
    
    • 1
    • 2
    • 3

    函数参数和返回值含义如下:
    buf:struct utsname 结构体类型指针,指向一个 struct utsname 结构体类型对象。
    返回值:成功返回 0;失败将返回-1,并设置 errno。
    uname()函数用法非常简单,先定义一个 struct utsname 结构体变量,调用 uname()函数时传入变量的地址即可,struct utsname 结构体如下所示:

    struct utsname 
    {
     char sysname[]; /* 当前操作系统的名称 */
     char nodename[]; /* 网络上的名称(主机名) */
     char release[]; /* 操作系统内核版本 */
     char version[]; /* 操作系统发行版本 */
     char machine[]; /* 硬件架构类型 */
     #ifdef _GNU_SOURCE
     char domainname[];/* 当前域名 */
     #endif
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    编写一个简单地程序,获取并打印出当前操作系统名称、主机名、内核版本、操作系统发行版本以及处理器硬件架构类型等信息,测试代码如下:

    #include 
    #include 
    #include 
    
    int main(void)
    {
        struct utsname os_info;
        int ret;
        /* 获取信息 */
        ret = uname(&os_info);
        if (-1 == ret) 
        {
            perror("uname error");
            exit(-1);
        }
        /* 打印信息 */
        printf("操作系统名称: %s\n", os_info.sysname);
        printf("主机名: %s\n", os_info.nodename);
        printf("内核版本: %s\n", os_info.release);
        printf("发行版本: %s\n", os_info.version);
        printf("硬件架构: %s\n", os_info.machine);
        exit(0);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    运行结果:
    在这里插入图片描述

    1.2 sysinfo 函数

    sysinfo 系统调用可用于获取一些系统统计信息,其函数原型如下所示:

    #include 
    
    int sysinfo(struct sysinfo *info);
    
    • 1
    • 2
    • 3

    函数参数和返回值含义如下:
    info:struct sysinfo 结构体类型指针,指向一个 struct sysinfo 结构体类型对象。
    返回值:成功返回 0;失败将返回-1,并设置 errno。
    同样 sysinfo()函数用法也非常简单,先定义一个 struct:

    struct sysinfo {
    	 long uptime; /* 自系统启动之后所经过的时间(以秒为单位) */
    	 unsigned long loads[3]; /* 1, 5, and 15 minute load averages */
    	 unsigned long totalram; /* 总的可用内存大小 */
    	 unsigned long freeram; /* 还未被使用的内存大小 */
    	 unsigned long sharedram; /* Amount of shared memory */
    	 unsigned long bufferram; /* Memory used by buffers */
    	 unsigned long totalswap; /* Total swap space size */
    	 unsigned long freeswap; /* swap space still available */
    	 unsigned short procs; /* 系统当前进程数量 */
    	 unsigned long totalhigh; /* Total high memory size */
    	 unsigned long freehigh; /* Available high memory size */
    	 unsigned int mem_unit; /* 内存单元大小(以字节为单位) */
    	 char _f[20-2*sizeof(long)-sizeof(int)]; /* Padding to 64 bytes */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    测试代码:

    #include 
    #include 
    #include 
    int main(void)
    {
    	 struct sysinfo sys_info;
    	 int ret;
    	 /* 获取信息 */
    	 ret = sysinfo(&sys_info);
    	 if (-1 == ret) 
    	 {
    		 perror("sysinfo error");
    		 exit(-1);
    	 }
    	 /* 打印信息 */
    	 printf("uptime: %ld\n", sys_info.uptime);
    	 printf("totalram: %lu\n", sys_info.totalram);
    	 printf("freeram: %lu\n", sys_info.freeram);
    	 printf("procs: %u\n", sys_info.procs);
    	 exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述

    1.3 gethostname 函数

    此函数可用于单独获取 Linux 系统主机名,与 struct utsname 数据结构体中的 nodename 变量一样,gethostname 函数原型如下所示(可通过"man 2 gethostname"命令查看):

    #include 
    
    int gethostname(char *name, size_t len);
    
    • 1
    • 2
    • 3

    函数参数和返回值含义如下:
    name:指向用于存放主机名字符串的缓冲区。
    len:缓冲区长度。
    返回值:成功返回 0,;失败将返回-1,并会设置 errno。

    #include 
    #include 
    #include 
    #include 
    
    int main(void)
    {
        char hostname[20];
        int ret;
        memset(hostname, 0x0, sizeof(hostname));
        ret = gethostname(hostname, sizeof(hostname));
        if (-1 == ret) 
        {
            perror("gethostname error");
            exit(ret);
        }
        puts(hostname);
        exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    1.4 sysconf()函数

    sysconf()函数是一个库函数,可在运行时获取系统的一些配置信息,譬如页大小(page size)、主机名
    的最大长度、进程可以打开的最大文件数、每个用户 ID 的最大并发进程数等。其函数原型如下所示:

    #include 
    
    long sysconf(int name);
    
    • 1
    • 2
    • 3

    调用 sysconf()函数获取系统的配置信息,参数 name 指定了要获取哪个配置信息,参数 name 可取以下
    任何一个值(都是宏定义,可通过 man 手册查询):

    1. _SC_ARG_MAX:exec 族函数的参数的最大长度,exec 族函数后面会介绍,这里先不管!
    2. _SC_CHILD_MAX:每个用户的最大并发进程数,也就是同一个用户可以同时运行的最大进程数。
    3. _SC_HOST_NAME_MAX:主机名的最大长度。
    4. _SC_LOGIN_NAME_MAX:登录名的最大长度。
    5. _SC_CLK_TCK:每秒时钟滴答数,也就是系统节拍率。
    6. _SC_OPEN_MAX:一个进程可以打开的最大文件数。
    7. _SC_PAGESIZE:系统页大小(page size)。
    8. _SC_TTY_NAME_MAX:终端设备名称的最大长度。
      除以上之外,还有很多,这里就不再一一列举了,可以通过 man 手册进行查看,用的比较多的是_SC_PAGESIZE 和_SC_CLK_TCK,在后面章节示例代码中有使用到。
    #include 
    #include 
    #include 
    int main(void)
    {
    	 printf("每个用户的最大并发进程数: %ld\n", sysconf(_SC_CHILD_MAX));
    	 printf("系统节拍率: %ld\n", sysconf(_SC_CLK_TCK));
    	 printf("系统页大小: %ld\n", sysconf(_SC_PAGESIZE));
    	 exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2、系统时间/时钟

    2.1 时间概述

    在正式介绍这些时间、日期相关的系统调用或 C 库函数之前,需要向大家介绍一些时间相关的基本概念,譬如 GMT 时间、UTC 时间以及时区等。
    地球总是自西向东自转,东边总比西边先看到太阳,东边的时间也总比西边的早。东边时刻与西边时刻的差值不仅要以时计,而且还要以分和秒来计算,这给人们的日常生活和工作都带来许多不便。

    GMT 时间
    GMT(Greenwich Mean Time)中文全称是格林威治标准时间,这个时间系统的概念在 1884 年被确立,由英国伦敦的格林威治皇家天文台计算并维护,并在之后的几十年向欧陆其它国家扩散。
    由于从 19 实际开始,因为世界各国往来频繁,而欧洲大陆、美洲大陆以及亚洲大陆都有各自的时区,所以为了避免时间混乱,1884 年,各国代表在美国华盛顿召开国际大会,通过协议选出英国伦敦的格林威治作为全球时间的中心点,决定以通过格林威治的子午线作为划分东西两半球的经线零度线(本初子午线、零度经线),由此格林威治标准时间因而诞生!
    所以 GMT 时间就是英国格林威治当地时间,也就是零时区(中时区)所在时间,譬如 GMT 12:00 就是指英国伦敦的格林威治皇家天文台当地的中午 12:00,与我国的标准时间北京时间(东八区)相差 8 个小时,即早八个小时,所以 GMT 12:00 对应的北京时间是 20:00。

    UTC 时间
    UTC(Coordinated Universal Time)指的是世界协调时间(又称世界标准时间、世界统一时间),是经过平均太阳时(以格林威治时间 GMT 为准)、地轴运动修正后的新时标以及以「秒」为单位的国际原子时所综合精算而成的时间,计算过程相当严谨精密,因此若以「世界标准时间」的角度来说,UTC 比 GMT 来得更加精准。

    GMT 与 UTC 这两者几乎是同一概念,它们都是指格林威治标准时间,也就是国际标准时间,只不过UTC 时间比 GMT 时间更加精准,所以在我们的编程当中不用刻意去区分它们之间的区别。

    2.2 Linux 系统中的时间

    点时间和段时间
    通常描述时间有两种方式:点时间和段时间;点时间顾名思义指的是某一个时间点,譬如当前时间是2021 年 2 月 22 日星期一 11:12 分 35 秒,所以这里指的就是某一个时间点;而对于段时间来说,顾名思义指的是某一个时间段,譬如早上 8:00 到中午 12:00 这段时间。

    实时时钟 RTC
    要是经常玩儿操作系统的大佬们,肯定对这个特别熟悉哈,不过还是简单介绍一下,操作系统中一般会有两个时钟,一个系统时钟(system clock),一个实时时钟(Real time clock),也叫 RTC;系统时钟由系统启动之后由内核来维护,譬如使用 date 命令查看到的就是系统时钟,所以在系统关机情况下是不存在的;而实时时钟一般由 RTC 时钟芯片提供,RTC 芯片有相应的电池为其供电,以保证系统在关机情况下 RTC 能够继续工作、继续计时。

    Linux 系统如何记录时间
    Linux 系统在开机启动之后首先会读取 RTC 硬件获取实时时钟作为系统时钟的初始值,之后内核便开始维护自己的系统时钟。所以由此可知,RTC 硬件只有在系统开机启动时会读取一次,目的是用于对系统时钟进行初始化操作,之后的运行过程中便不会再对其进行读取操作了。
    而在系统关机时,内核会将系统时钟写入到 RTC 硬件、已进行同步操作。

    jiffies 的引入
    jiffies 是内核中定义的一个全局变量,内核使用 jiffies 来记录系统从启动以来的系统节拍数,所以这个变量用来记录以系统节拍时间为单位的时间长度,Linux 内核在编译配置时定义了一个节拍时间,使用节拍率(一秒钟多少个节拍数)来表示,譬如常用的节拍率为 100Hz(一秒钟 100 个节拍数,节拍时间为 1s / 100)、200Hz(一秒钟 200 个节拍,节拍时间为 1s / 200)、250Hz(一秒钟 250 个节拍,节拍时间为 1s / 250)、300Hz(一秒钟 300 个节拍,节拍时间为 1s / 300)、500Hz(一秒钟 500 个节拍,节拍时间为 1s / 500)等。
    由此可以发现配置的节拍率越低,每一个系统节拍的时间就越短,也就意味着 jiffies 记录的时间精度越高,当然,高节拍率会导致系统中断的产生更加频繁,频繁的中断会加剧系统的负担,一般默认情况下都是采用 100Hz 作为系统节拍率。

    2.3 获取时间 time/gettimeofday

    (1)time 函数
    系统调用 time()用于获取当前时间,以秒为单位,返回得到的值是自 1970-01-01 00:00:00 +0000 (UTC)
    以来的秒数,其函数原型如下所示(可通过"man 2 time"命令查看):

    #include 
    
    time_t time(time_t *tloc);
    
    • 1
    • 2
    • 3

    函数参数和返回值含义如下:
    tloc:如果 tloc 参数不是 NULL,则返回值也存储在 tloc 指向的内存中。
    返回值:成功则返回自 1970-01-01 00:00:00 +0000 (UTC)以来的时间值(以秒为单位);失败则返回-1,并会设置 errno。
    大家可以测试一下下面的demo,我就不运行了:

    #include 
    #include 
    #include 
    int main(void)
    {
    	 time_t t;
    	 t = time(NULL);
    	 if (-1 == t) 
    	 {
    		 perror("time error");
    		 exit(-1);
    	 }
    	 printf("时间值: %ld\n", t);
    	 exit(0);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    (2)gettimeofday 函数
    time()获取到的时间只能精确到秒,如果想要获取更加精确的时间可以使用系统调用 gettimeofday 来实现,gettimeofday()函数提供微秒级时间精度,函数原型如下所示(可通过"man 2 gettimeofday"命令查看):

    #include 
    
    int gettimeofday(struct timeval *tv, struct timezone *tz);
    
    • 1
    • 2
    • 3

    函数参数和返回值含义如下:
    tv:参数 tv 是一个 struct timeval 结构体指针变量;
    tz:参数 tz 是个历史产物,早期实现用其来获取系统的时区信息,目前已遭废弃,在调用 gettimeofday()函数时应将参数 tz 设置为 NULL。
    返回值:成功返回 0;失败将返回-1,并设置 errno。
    获取得到的时间值存储在参数 tv 所指向的 struct timeval 结构体变量中,该结构体包含了两个成员变量tv_sec 和 tv_usec,分别用于表示秒和微秒,所以获取得到的时间值就是 tv_sec(秒)+tv_usec(微秒),同样获取得到的秒数与 time()函数一样,也是自 1970-01-01 00:00:00 +0000 (UTC)到现在这段时间所经过的秒数,也就是日历时间,所以由此可知 time()返回得到的值和函数 gettimeofday()所返回的 tv 参数中 tv_sec 字段的数值相同。

    #include 
    #include 
    #include 
    int main(void)
    {
    	 struct timeval tval;
    	 int ret;
    	 ret = gettimeofday(&tval, NULL);
    	 if (-1 == ret) 
    	 {
    		 perror("gettimeofday error");
    		 exit(-1);
    	 }
    	 printf("时间值: %ld 秒+%ld 微秒\n", tval.tv_sec, tval.tv_usec);
    	 exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2.4 时间转换函数

    通过 time()或 gettimeofday()函数可以获取到当前时间点相对于 1970-01-01 00:00:00 +0000 (UTC)这个时间点所经过时间(日历时间),所以获取得到的是一个时间段的长度,但是这并不利于我们查看当前时间,这个结果对于我们来说非常不友好,那么本小节将向大家介绍一些系统调用或 C 库函数,通过这些 API 可以将 time()或 gettimeofday()函数获取到的秒数转换为利于查看和理解的形式。

    (1)ctime 函数

    #include 
    
    char *ctime(const time_t *timep);
    char *ctime_r(const time_t *timep, char *buf);
    
    • 1
    • 2
    • 3
    • 4

    函数参数和返回值含义如下:
    timep:time_t 时间变量指针。
    返回值:成功将返回一个 char *类型指针,指向转换后得到的字符串;失败将返回 NULL。

    所以由此可知,使用 ctime 函数非常简单,只需将 time_t 时间变量的指针传入即可,调用成功便可返回字符串指针,拿到字符串指针之后,可以使用 printf 将其打印输出。但是 ctime()是一个不可重入函数,存在一些安全上面的隐患,ctime_r()是 ctime()的可重入版本,一般推荐大家使用可重入函数 ctime_r(),可重入函数 ctime_r()多了一个参数 buf,也就是缓冲区首地址,所以 ctime_r()函数需要调用者提供用于存放字符串的缓冲区。

    #include 
    #include 
    #include 
    int main(void)
    {
    	 char tm_str[100] = {0};
    	 time_t tm;
    	 /* 获取时间 */
    	 tm = time(NULL);
    	 if (-1 == tm) 
    	 {
    		 perror("time error");
    		 exit(-1);
    	 }
    	 /* 将时间转换为字符串形式 */
    	 ctime_r(&tm, tm_str);
    	 /* 打印输出 */
    	 printf("当前时间: %s", tm_str);
    	 exit(0);
    }	 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    (2)gmtime 函数
    gmtime()函数也可以把 time_t 时间变成一个 struct tm 结构体所表示的时间,与 localtime()所不同的是,gmtime()函数所得到的是 UTC 国际标准时间,并不是计算机的本地时间,这是它们之间的唯一区别。gmtime()函数原型如下所示:

    #include 
    
    struct tm *gmtime(const time_t *timep);
    struct tm *gmtime_r(const time_t *timep, struct tm *result);
    
    • 1
    • 2
    • 3
    • 4

    方法基本与上边相同,参考下方代码即可:

    #include 
    #include 
    #include 
    int main(void)
    {
    	 struct tm local_t;
    	 struct tm utc_t;
    	 time_t sec;
    	 /* 获取时间 */
    	 sec = time(NULL);
    	 if (-1 == sec) 
    	 {
    		 perror("time error");
    		 exit(-1);
    	 }
    	 /* 转换得到本地时间 */
    	 localtime_r(&sec, &local_t);
    	 /* 转换得到国际标准时间 */
    	 gmtime_r(&sec, &utc_t);
    	 /* 打印输出 */
    	 printf("本地时间: %d 年%d 月%d 日 %d:%d:%d\n",
    	 local_t.tm_year + 1900, local_t.tm_mon, local_t.tm_mday,
    	 local_t.tm_hour, local_t.tm_min, local_t.tm_sec);
    	 printf("UTC 时间: %d 年%d 月%d 日 %d:%d:%d\n",
    	 utc_t.tm_year + 1900, utc_t.tm_mon, utc_t.tm_mday,
    	 utc_t.tm_hour, utc_t.tm_min, utc_t.tm_sec);
    	 exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    (3)strftime 函数
    这里再给大家介绍一个 C 库函数 strftime(),此函数也可以将一个 struct tm 变量表示的分解时间转换为为格式化字符串,并且在功能上比 asctime()和 ctime()更加强大,它可以根据自己的喜好自定义时间的显示格式,而 asctime()和 ctime()转换得到的字符串时间格式的固定的。

    #include 
    
    size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
    
    • 1
    • 2
    • 3

    函数参数和返回值含义如下:
    s:指向一个缓存区的指针,该缓冲区用于存放生成的字符串。
    max:字符串的最大字节数。
    format:这是一个用字符串表示的字段,包含了普通字符和特殊格式说明符,可以是这两种字符的任意组合。特殊格式说明符将会被替换为 struct tm 结构体对象所指时间的相应值,这些特殊格式说明符如下:
    在这里插入图片描述
    在这里插入图片描述

    #include 
    #include 
    #include 
    int main(void)
    {
    	struct tm local_t;
    	char tm_str[100] = {0};
    	time_t sec;
    	/* 获取时间 */
    	sec = time(NULL);
    	if (-1 == sec) 
    	{
    		perror("time error");
    		exit(-1);
    	}
    	localtime_r(&sec, &local_t);
    	strftime(tm_str, sizeof(tm_str), "%Y-%m-%d %A %H:%M:%S", &local_t);
    	printf("本地时间: %s\n", tm_str);
    	exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2.5 进程时间

    进程时间指的是进程从创建后(也就是程序运行后)到目前为止这段时间内使用 CPU 资源的时间总数,出于记录的目的,内核把 CPU 时间(进程时间)分为以下两个部分:

    1. 用户 CPU 时间:进程在用户空间(用户态)下运行所花费的 CPU 时间。有时也成为虚拟时间(virtual time)。
    2. 系统 CPU 时间:进程在内核空间(内核态)下运行所花费的 CPU 时间。这是内核执行系统调用或代表进程执行的其它任务(譬如,服务页错误)所花费的时间。

    一般来说,进程时间指的是用户 CPU 时间和系统 CPU 时间的总和,也就是总的 CPU 时间。
    注意:进程时间不等于程序的整个生命周期所消耗的时间,如果进程一直处于休眠状态(进程被挂起、不会得到系统调度),那么它并不会使用 CPU 资源,所以休眠的这段时间并不计算在进程时间中。

    (1)times 函数

    #include 
    
    clock_t times(struct tms *buf);
    
    • 1
    • 2
    • 3

    函数参数和返回值含义如下:
    buf:times()会将当前进程时间信息存在一个 struct tms 结构体数据中,所以我们需要提供 struct tms 变量,使用参数 buf 指向该变量。
    返回值:返回值类型为 clock_t(实质是 long 类型),调用成功情况下,将返回从过去任意的一个时间点(譬如系统启动时间)所经过的时钟滴答数(其实就是系统节拍数),将(节拍数 / 节拍率)便可得到秒数,返回值可能会超过 clock_t 所能表示的范围(溢出);调用失败返回-1,并设置 errno。

    struct tms {
     clock_t tms_utime; /* user time, 进程的用户 CPU 时间, tms_utime 个系统节拍数 */
     clock_t tms_stime; /* system time, 进程的系统 CPU 时间, tms_stime 个系统节拍数 */
     clock_t tms_cutime; /* user time of children, 已死掉子进程的 tms_utime + tms_cutime 时间总和 */
     clock_t tms_cstime; /* system time of children, 已死掉子进程的 tms_stime + tms_cstime 时间总和 */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果我们想查看程序运行到某一个位置时的进程时间,或者计算出程序中的某一段代码执行过程所花费的进程时间,都可以使用 times()函数来实现。可以参考一下代码:

    #include 
    #include 
    #include 
    #include 
    int main(int argc, char *argv[])
    {
    	 struct tms t_buf_start;
    	 struct tms t_buf_end;
    	 clock_t t_start;
    	 clock_t t_end;
    	 long tck;
    	 int i, j;
    	 /* 获取系统的节拍率 */
    	 tck = sysconf(_SC_CLK_TCK);
    	 /* 开始时间 */
    	 t_start = times(&t_buf_start);
    	 if (-1 == t_start) 
    	 {
    		 perror("times error");
    		 exit(-1);
    	 }
    	 /* *****需要进行测试的代码段***** */
    	 for (i = 0; i < 20000; i++)
    	 	for (j = 0; j < 20000; j++);
    	 sleep(1); //休眠挂起
     /* *************end************** */
    	 /* 结束时间 */
    	 t_end = times(&t_buf_end);
    	 if (-1 == t_end) 
    	 {
    		 perror("times error");
    		 exit(-1);
    	 }
    	 /* 打印时间 */
    	 printf("时间总和: %f 秒\n", (t_end - t_start) / (double)tck);
    	 printf("用户 CPU 时间: %f 秒\n", (t_buf_end.tms_utime - t_buf_start.tms_utime) / (double)tck);
    	 printf("系统 CPU 时间: %f 秒\n", (t_buf_end.tms_stime - t_buf_start.tms_stime) / (double)tck);
    	 exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    在这里插入图片描述
    可以看到用户 CPU 时间为 0.68 秒,系统 CPU 时间为 0 秒,也就是说测试的这段代码并没有进入内核态运行,所以总的进程时间 = 用户 CPU 时间 + 系统 CPU 时间 = 0.68 秒。
    tips:上图中显示的时间总和并不是总的进程时间,前面也给大家解释过,这个时间总和指的是从起点到终点锁经过的时间,并不是进程时间,时间总和包括了进程处于休眠状态时消耗的时间(sleep 等会让进程挂起、进入休眠状态),可以发现时间总和比进程时间多 1 秒,其实这一秒就是进程处于休眠状态的时间。

    (2)clock函数
    库函数 clock()提供了一个更为简单的方式用于进程时间,它的返回值描述了进程使用的总的 CPU 时间(也就是进程时间,包括用户 CPU 时间和系统 CPU 时间),其函数原型如下所示:

    #include 
    
    clock_t clock(void);
    
    • 1
    • 2
    • 3

    函数参数和返回值含义如下:
    无参数。
    返回值:返回值是到目前为止程序的进程时间,为 clock_t 类型,注意 clock()的返回值并不是系统节拍数,如果想要获得秒数,请除以 CLOCKS_PER_SEC(这是一个宏)。如果返回的进程时间不可用或其值无法表示,则该返回值是-1。

    #include 
    #include 
    #include 
    int main(int argc, char *argv[])
    {
        clock_t t_start;
        clock_t t_end;
        int i, j;
        /* 开始时间 */
        t_start = clock();
        if (-1 == t_start)
        exit(-1);
        /* *****需要进行测试的代码段***** */
        for (i = 0; i < 20000; i++)
        for (j = 0; j < 20000; j++);
        /* *************end************** */
        /* 结束时间 */
        t_end = clock();
        if (-1 == t_end)
        exit(-1);
        /* 打印时间 */
        printf("总的 CPU 时间: %f\n", (t_end - t_start) / (double)CLOCKS_PER_SEC);
        exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述

    3.休眠

    3.1 为啥要休眠

    当程序运行过一段时间过后,有时是需要将进程暂停或休眠一段时间的,例如:你要一边吃饭,一边发消息,我们带入这个情景,看似是两件事,但却又是同一时间段发生的,程序里,我们规范点叫做线程同步,但是在执行线程的时候总会有一个先做,一个后做,这个就叫做系统调度,假设你吃饭的时候,你给你的朋友/老板发消息,吃一口,打一个字,这个时候,当你吃饭的时候,打字这部分动作是休眠的,而当你打字的时候,吃饭的动作也同样是休眠的;所以进入休眠状态之后,程序将暂停运行,直到休眠结束。常用的系统调用和 C 库函数有 sleep()、usleep()以及 nanosleep(),这些函数在应用程序当中通常作为延时使用。
    下面给大家简单讲解这几个休眠函数的使用的方法:

    3.2 秒级休眠: sleep

    sleep()是一个 C 库函数,从函数名字面意思便可以知道该函数的作用了,简单地说,sleep()就是让程序“休息”一会,然后再继续工作。

    #include 
    
    unsigned int sleep(unsigned int seconds);
    
    • 1
    • 2
    • 3

    函数参数和返回值含义如下
    seconds:休眠时长,以秒为单位。
    返回值:如果休眠时长为参数 seconds 所指定的秒数,则返回 0;若被信号中断则返回剩余的秒数。

    #include 
    #include 
    #include 
    int main(void)
    {
    	 puts("Sleep Start!");
    	 /* 让程序休眠 3 秒钟 */
    	 sleep(3);
    	 puts("Sleep End!");
    	 exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.3 微秒级休眠: usleep

    usleep()同样也是一个 C 库函数,与 sleep()的区别在于休眠时长精度不同,usleep()支持微秒级程序休眠;

    #include 
    
    int usleep(useconds_t usec);
    
    • 1
    • 2
    • 3

    函数参数和返回值含义如下:
    usec:休眠时长,以微秒为单位。
    返回值:成功返回 0;失败返回-1,并设置 errno。

    #include 
    #include 
    #include 
    int main(void)
    {
     puts("Sleep Start!");
     /* 让程序休眠 3 秒钟(3*1000*1000 微秒) */
     usleep(3 * 1000 * 1000);
     puts("Sleep End!");
     exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.4 高精度休眠: nanosleep

    nanosleep()与 sleep()以及 usleep()类似,都用于程序休眠,但 nanosleep()具有更高精度来设置休眠时间长度,支持纳秒级时长设置。与 sleep()、usleep()不同的是,nanosleep()是一个 Linux 系统调用的函数;

    #include 
    
    int nanosleep(const struct timespec *req, struct timespec *rem);
    
    • 1
    • 2
    • 3

    函数参数与返回值含义如下
    req:一个 struct timespec 结构体指针,指向一个 struct timespec 变量,用于设置休眠时间长度,可精确到纳秒级别。
    rem:也是一个 struct timespec 结构体指针,指向一个 struct timespec 变量,也可设置 NULL。
    返回值:在成功休眠达到请求的时间间隔后,nanosleep()返回 0;如果中途被信号中断或遇到错误,则返回-1,并将剩余时间记录在参数 rem 指向的 struct timespec 结构体变量中(参数 rem 不为 NULL 的情况下,如果为 NULL 表示不接收剩余时间),还会设置 errno 标识错误类型。

    #include 
    #include 
    #include 
    int main(void)
    {
    	 struct timespec request_t;
    	 puts("Sleep Start!");
    	 /* 让程序休眠 3 秒钟 */
    	 request_t.tv_sec = 3;
    	 request_t.tv_nsec = 0;
    	 nanosleep(&request_t, NULL);
    	 puts("Sleep End!");
    	 exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4.proc 文件系统

    4.1 概述

    proc 文件系统是一个虚拟文件系统,它以文件系统的方式为应用层访问系统内核数据提供了接口,用户和应用程序可以通过 proc 文件系统得到系统信息和进程相关信息,对 proc 文件系统的读写作为与内核进行通信的一种手段。但是与普通文件不同的是,proc 文件系统是动态创建的,文件本身并不存在于磁盘当中、只存在于内存当中,与 devfs 一样,都被称为虚拟文件系统。

    最初构建 proc 文件系统是为了提供有关系统中进程相关的信息,但是由于这个文件系统非常有用,因此内核中的很多信息也开始使用它来报告,或启用动态运行时配置。内核构建 proc 虚拟文件系统,它会将内核运行时的一些关键数据信息以文件的方式呈现在 proc 文件系统下的一些特定文件中,这样相当于将一些不可见的内核中的数据结构以可视化的方式呈现给应用层。

    proc 文件系统挂载在系统的/proc 目录下,对于内核开发者(譬如驱动开发工程师)来说,proc 文件系统给了开发者一种调试内核的方法:通过查看/proc/xxx 文件来获取到内核特定数据结构的值,在添加了新功能前后进行对比,就可以判断此功能所产生的影响是否合理。

    如下方图中所示:
    在这里插入图片描述
    可以看到/proc 目录下有很多以数字命名的文件夹,譬如 1, 12715 ,17等,这些数字对应的其实就是一个一个的进程 PID 号,每一个进程在内核中都会存在一个编号,通过此编号来区分不同的进程,这个编号就是 PID 号。

    /proc 目录下除了文件夹之外,还有很多的虚拟文件,譬如 buddyinfo、cgroups、cmdline、version 等等

    1. cmdline:内核启动参数;
    2. cpuinfo:CPU 相关信息;
    3. iomem:IO 设备的内存使用情况;
    4. interrupts:显示被占用的中断号和占用者相关的信息;
    5. ioports:IO 端口的使用情况;
    6. kcore:系统物理内存映像,不可读取;
    7. loadavg:系统平均负载;
    8. meminfo:物理内存和交换分区使用情况;
    9. modules:加载的模块列表;
    10. mounts:挂载的文件系统列表;
    11. partitions:系统识别的分区表;
    12. swaps:交换分区的利用情况;
    13. version:内核版本信息;
    14. uptime:系统运行时间;

    4.2 proc 文件系统的使用

    proc 文件系统的使用就是去读取/proc 目录下的这些文件,获取文件中记录的信息,可以直接使用 cat 命令读取,也可以在应用程序中调用 open()打开、然后再使用 read()函数读取。

    在终端查看信息:
    在这里插入图片描述

    通过编程实现:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(int argc, char *argv[])
    {
        char buf[512] = {0};
        int fd;
        int ret;
        /* 打开文件 */
        fd = open("/proc/version", O_RDONLY);
        if (-1 == fd) 
        {
            perror("open error");
            exit(-1);
        }
        /* 读取文件 */
        ret = read(fd, buf, sizeof(buf));
        if (-1 == ret) 
        {
            perror("read error");
            exit(-1);
        }
        /* 打印信息 */
        puts(buf);
        /* 关闭文件 */
        close(fd);
        exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    测试结果:
    在这里插入图片描述

    本文参考正点原子的嵌入式LinuxC应用编程。

  • 相关阅读:
    设计模式系列之MVC模式
    海信电视U8发布,一场针对画质的“定向跨越”
    自动激光分板系统(自动PCB激光分板系统)市场现状分析
    系统告警 ProcessUnpackaged is set to ‘no‘
    JavaSE - 数组的定义、使用、内存分布、应用
    Pycharm与Gitlab交互
    [附源码]java毕业设计药品管理系统
    探索 Docker:容器化技术的未来
    嵌入式开发中的滤波器设计
    Ceres学习笔记001--初识Ceres
  • 原文地址:https://blog.csdn.net/weixin_52694360/article/details/128043789