Linux系统相关函数总结

在应用程序当中,有时往往需要去获取到一些系统相关的信息,譬如时间、日期、以及其它一些系统相关信息,本章将向大家介绍如何通过 Linux 系统调用或 C 库函数获取这些系统信息。除此之外,还会向大家介绍 Linux 系统下的/proc 虚拟文件系统,包括/proc 文件系统是什么以及如何从/proc 文件系统中读取系统、进程有关信息。 除了介绍系统信息内容外,本章还会向大家介绍有关系统资源的使用,譬如系统内存资源的申请与使用等。

系统信息

系统标识 uname

sysinfo 函数,该系统调用可用于获取一些系统统计信息;
gethostname 函数,此函数可用于单独获取 Linux 系统主机名。
sysconf() 函数
sysconf() 函数是一个库函数,可在运行时获取系统的一些配置信息,譬如页大小( page size )、主机名的最大长度、进程可以打开的最大文件数、每个用户 ID 的最大并发进程数等。

时间和日期

Ubuntu 系统下,可以使用 date 命令查看系统当前的本地时间,如下所示:

可以看到显示出来的字符串后面有一个"CST"字样,CST 在这里其实指的是 China Standard Time(中国 标准时间)的缩写,表示当前查看到的时间是中国标准时间,也就是我国所使用的标准时间--北京时间,一般在安装 Ubuntu 系统的时候会提示用户设置所在城市,那么系统便会根据你所设置的城市来确定系统的本地时间对应的时区,譬如设置的城市为上海,那么系统的本地时间就是北京时间,因为我国统一使用北京时间作为本国的标准时间。

Ubuntu 系统下,时区信息通常以标准格式保存在一些文件当中,这些文件通常位于/usr/share/zoneinfo目录下,该目录下的每一个文件(包括子目录下的文件)都包含了一个特定国家或地区内时区制度的相关信息,且往往根据其所描述的城市或地区缩写来加以命名,譬如 EST(美国东部标准时间)、CET(欧洲中部时间)、UTC(世界标准时间)、HongkongIranJapan(日本标准时间)等,也把这些文件称为时区配置文件,如下图所示:

Linux系统如何记录时间

操作系统中一般会有两个时钟,一个系统时钟(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 作为系统节拍率。

这里其实也是linux系统的任务调度频率,也就是说,linux的任务调度,每10ms切换一次。而在RTOS中,一般都是1ms。这也是为什么Linux不是实时操作系统的原因之一。

内核其实通过 jiffies 来维护系统时钟,全局变量 jiffies 在系统开机启动时会设置一个初始值,上面也给大家提到过,RTC 实时时钟会在系统开机启动时读取一次,目的是用于对系统时钟进行初始化,这里说的初始化其实指的就是对内核的 jiffies 变量进行初始化操作,具体如何将读取到的实时时钟换算成 jiffies 数值,这里便不再给大家介绍了。
所以由此可知,操作系统使用 jiffies 这个全局变量来记录当前时间,当我们需要获取到系统当前时间点时,就可以使用 jiffies 变量去计算,当然并不需要我们手动去计算,Linux 系统提供了相应的系统调用或 C库函数用于获取当前时间,譬如系统调用 time()、gettimeofday(),其实质上就是通过 jiffies 变量换算得到。

由此也可知,通过这种方式得到的时间,精度只有10ms(100Hz系统频率的前提下)

获取日历时间

time函数
系统调用 time()用于获取当前时间,以秒为单位,返回得到的值是自 1970-01-01 00:00:00 +0000 (UTC)以来的秒数。我们把这个称之为日历时间或 time_t 时间。

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

#include <sys/time.h>

int gettimeofday(struct timeval *tv, struct timezone *tz);

使用该函数需要包含头文件<sys/time.h>。 

参数 tv 是一个 struct timeval 结构体指针变量;

struct timeval {
    long tv_sec; /* 秒 */ 
    long tv_usec; /* 微秒 */
};

获取得到的时间值存储在参数 tv 所指向的 struct timeval 结构体变量中,该结构体包含了两个成员变量tv_sec 和 tv_usec,分别用于表示秒和微秒,所以获取得到的时间值就是 tv_sec(秒)和tv_usec(微秒),其中,tv_sec的值和 time()函数获取到的一样。
tz:参数 tz 是个历史产物,早期实现用其来获取系统的时区信息,目前已遭废弃,在调用gettimeofday()函数时应将参数 tz 设置为 NULL。

返回值:成功返回 0;失败将返回-1,并设置 errno。

示例如下:

注意,这个秒和毫秒不是等价的关系,而是叠加的关系。

只是time函数省略掉了秒以下的时间精度。

时间转换 

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

ctime函数
ctime()是一个 C 库函数,可以将日历时间转换为可打印输出的字符串形式,ctime()函数原型如下所示:

#include <time.h>
char *ctime(const time_t *timep);
char *ctime_r(const time_t *timep, char *buf);

使用该函数需要包含头文件<time.h>。
函数参数和返回值含义如下:
timep:time_t 时间变量指针。
返回值:成功将返回一个 char *类型指针,指向转换后得到的字符串;失败将返回 NULL。

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

示例如下:

结果如下:

不过,这种形式的可读性也一般。

localtime函数
localtime()函数可以把 time()或 gettimeofday()得到的秒数(time_t 时间或日历时间)变成一个 struct tm结构体所表示的时间,该时间对应的是本地时间。localtime 函数原型如下:

#include <time.h>
struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);

使用该函数需要包含头文件<time.h>,localtime()的可重入版本为 localtime_r()。

函数参数和返回值含义如下:

timep:需要进行转换的 time_t 时间变量对应的指针,可通过 time()或 gettimeofday()获取得到。

result:是一个 struct tm 结构体类型指针,稍后给大家介绍 struct tm 结构体,参数 result 是可重入函数localtime_r()需要额外提供的参数。

返回值:对于不可重入版本 localtime()来说,成功则返回一个有效的 struct tm 结构体指针,而对于可重入版本 localtime_r()来说,成功执行情况下,返回值将会等于参数 result;失败则返回 NULL。使用不可重入函数 localtime()并不需要调用者提供 struct tm 变量,而是它会直接返回出来一个 struct tm结构体指针,然后直接通过该指针访问里边的成员变量即可!虽然很方便,但是存在一些安全隐患,所以一般不推荐使用不可重入版本。使用可重入版本 localtime_r()调用者需要自己定义 struct tm 结构体变量、并将该变量指针赋值给参数result,在函数内部会对该结构体变量进行赋值操作。 

struct tm {
    int tm_sec;  /* 秒(0-60) */
    int tm_min;  /* 分(0-59) */
    int tm_hour; /* 时(0-23) */
    int tm_mday; /* 日(1-31) */
    int tm_mon;  /* 月(0-11) */
    int tm_year; /* 年(这个值表示的是自 1900 年到现在经过的年数) */
    int tm_wday; /* 星期(0-6, 星期日 Sunday = 0、星期一=1…) */
    int tm_yday; /* 一年里的第几天(0-365, 1 Jan = 0) */
    int tm_isdst;/* 夏令时 */
};
从 struct tm 结构体内容可知,该结构体中包含了年月日时分秒星期等信息,使用localtime/localtime_r()便可以将 time_t 时间总秒数分解成了各个独立的时间信息,易于我们查看和理解。
示例如下:

gmtime函数

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

strftime函数
再给大家介绍一个 C 库函数 strftime(),此函数可以将一个 struct tm 变量表示的分解时间转换为为格式化字符串,并且可以根据自己的喜好自定义时间的显示格式。

具体参考:C 库函数 – strftime() | 菜鸟教程 (runoob.com)

其实就是通过特定的符号来取struct tm中的对应数据,然后生成一个字符串而已。

在开发中,如果不是业务上有显示时间的要求,我们一般不需要进行时间转换;在linux开发中,我们可以利用获取时间的功能,来实现定时的功能,比如先获取一次时间点t,然后我们要定时的时长是x,那么就可以知道定时到期的时间点就是t+x,接着我们在任务中不断获取最新时间点并且和目标到期时间点做比较,当时间点到了之后就可以执行对应的动作了。这样一来,可以不用定时器就能实现一些简单的定时功能。不过,这种定时间的精度并不是很准确,但一般情况下也够用了。 

进程时间

进程时间指的是进程从创建后(也就是程序运行后)到目前为止这段时间内使用 CPU 资源的时间总数,
出于记录的目的,内核把 CPU 时间(进程时间)分为以下两个部分:
⚫ 用户 CPU 时间:进程在用户空间(用户态)下运行所花费的 CPU 时间。有时也成为虚拟时间(virtualtime)。
⚫ 系统 CPU 时间:进程在内核空间(内核态)下运行所花费的 CPU 时间。这是内核执行系统调用或代表进程执行的其它任务(譬如,服务页错误)所花费的时间。
一般来说,进程时间指的是用户 CPU 时间和系统 CPU 时间的总和,也就是总的 CPU 时间。
Tips:进程时间不等于程序的整个生命周期所消耗的时间,如果进程一直处于休眠状态(进程被挂起、不会得到系统调度),那么它并不会使用 CPU 资源,所以休眠的这段时间并不计算在进程时间中。

times函数(注意和time函数区分开)
times()函数用于获取当前进程时间,其函数原型如下所示:

#include <sys/times.h>
clock_t times(struct tms *buf);

使用该函数需要包含头文件<sys/times.h>。

函数参数和返回值含义如下:

buf:times()会将当前进程时间信息存在一个 struct tms 结构体数据中,所以我们需要提供 struct tms 变量,使用参数 buf 指向该变量。

关于 struct tms 结构体稍后给大家介绍。

返回值:返回值类型为 clock_t(实质是 long 类型),调用成功情况下,将返回从过去任意的一个时间点(譬如系统启动时间)所经过的时钟滴答数(其实就是系统节拍数),将(节拍数 / 节拍率)便可得到秒数,返回值可能会超过 clock_t 所能表示的范围(溢出);调用失败返回-1,并设置 errno。

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

注意,进程时间是存储在参数中,但是返回值就是调用该函数时的系统时间(节拍数),注意区分。

struct tms 结构体内容如下所示:

示例代码 7.3.1 struct tms 结构体

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 时间总和 */ 
}; 

测试

以下我们演示了通过 times()来计算程序中某一段代码执行所耗费的进程时间和总的时间,测试程序如下所示:

示例代码 7.3.2 times 函数使用示例 
#include <stdio.h> 
#include <stdlib.h> 
#include <sys/times.h> 
#include <unistd.h> 
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); 
} 

接下来编译运行,测试结果如下:

可以看到用户 CPU 时间为 1.9 秒,系统 CPU 时间为 0 秒,也就是说测试的这段代码并没有进入内核态运行,所以总的进程时间 = 用户 CPU 时间 + 系统 CPU 时间 = 1.9 秒。

图 7.3.1 中显示的时间总和并不是总的进程时间,前面也给大家解释过,这个时间总和指的是从起点到终点锁经过的时间,并不是进程时间,这里大家要理解。时间总和包括了进程处于休眠状态时消耗的时间(sleep 等会让进程挂起、进入休眠状态),可以发现时间总和比进程时间多 1 秒,其实这一秒就是进程处于休眠状态的时间。

时间单位一般有两种,要么是自然时间的单位,比如秒、毫秒、微秒等,要么是系统节拍为单位的时间,一般提到处理器时间系统时间cpu时间,单位基本都是时钟节拍,之后,要想换算成自然时间的单位,就需要除以系统的节拍率。time_t、struct timeval、struct tm等都是自然时间单位的类型,cloct_t一般都是时钟节拍为单位的类型。自然时间一般对应的叫法是实时时间、墙上时间;系统节拍一般对应着monotonic时间。要注意对比和区分。 

clock函数

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

#include <time.h>

clock_t clock(void);

使用该函数需要包含头文件<time.h>。

函数参数和返回值含义如下:

无参数。

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

!!!!注意,CLOCKS_PER_SEC并不一定等价于节拍数(滴答数/CLK_TCK)

在头文件<time.h>中定义 #define CLOCKS_PER_SEC / *实现定义* /

这个其实是库函数自行定义的一个量值,每个系统的实现不一定一样,有的系统就等于CLK_TCK,有的系统又不等于。POSIX定义CLOCKS_PER_SEC为一百万,而不管实际精度如何。

clock()函数虽然可以很方便的获取总的进程时间,但并不能获取到单独的用户 CPU 时间和系统 CPU 时间,在实际编程当中,根据自己的需要选择。

测试

对示例代码 7.3.2 进行简单地修改,使用 clock()获取到待测试代码段所消耗的进程时间,如下:

示例代码 7.3.3 clock 函数使用示例 
#include <stdio.h> 
#include <stdlib.h> 
#include <time.h> 
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); 
} 

运行结果:

下面再次实例演示了 clock() 函数的用法。

#include <time.h>
#include <stdio.h>
 
int main()
{
   clock_t start_t, end_t;
   double total_t;
   int i;
 
   start_t = clock();
   printf("程序启动,start_t = %ld\n", start_t);
    
   printf("开始一个大循环,start_t = %ld\n", start_t);
   for(i=0; i< 10000000; i++)
   {
   }
   end_t = clock();
   printf("大循环结束,end_t = %ld\n", end_t);
   
   total_t = (double)(end_t - start_t) / CLOCKS_PER_SEC;
   printf("CPU 占用的总时间:%f\n", total_t  );
   printf("程序退出...\n");
 
   return(0);
}

让我们编译并运行上面的程序,这将产生以下结果:

程序启动,start_t = 2614
开始一个大循环,start_t = 2614
大循环结束,end_t = 28021
CPU 占用的总时间:0.025407
程序退出...

clock_gettime函数

函数"clock_gettime"是基于Linux C语言的时间函数,可以用于计算时间,有秒和纳秒两种精度。在POSIX1003.1中增添了这个函数。

函数原型:

int clock_gettime(clockid_t clk_id, struct timespec *tp);

其中,cld_id类型四种:

CLOCK_REALTIME: 标准POSIX实时时钟

CLOCK_MONOTONIC: POSIX时钟,以恒定速率运行;不会复位和调整,它的取值和CLOCK_REALTIME是一样的.

CLOCK_PROCESS_CPUTIME_ID和CLOCK_THREAD_CPUTIME_ID是CPU中的硬件计时器中实现的.

其中,timespec结构包括:

struct timespec {
    time_t tv_sec; /* 秒*/
    long tv_nsec; /* 纳秒*/
};

该函数执行成功返回0,失败返回-1;

例如:计算时间差

#include <time.h>

struct timespec time1 = {0, 0};

struct timespec time2 = {0, 0};

clock_gettime(CLOCK_REALTIME, &time1);

……

……

clock_gettime(CLOCK_REALTIME, &time2);

cout << "time passed is: " << (time2.tv_sec - time1.tv_sec)*1000 + (time2.tv_nsec - time1.tv_nsec)/1000000 << "ms" << endl;

随机数

具体参考:

C 库函数 – rand() | 菜鸟教程 (runoob.com)

使用 rand()srand()产生一组伪随机数

休眠

秒级休眠: sleep

微秒级休眠: usleep

高精度休眠: nanosleep

注意,没有毫秒级休眠。。。。。。

在应用程序当中,通常使用这些函数作为延时功能,譬如在程序当中需要延时一秒钟、延时
5 毫秒等应用场景时,那么就可以使用这些函数来实现;但是大家需要注意,休眠状态下,该进程会失去 CPU使用权,退出系统调度队列,直到休眠结束。在一个裸机程序当中,通常使用 for 循环(或双重 for 循环)语句来实现延时等待,譬如在 for 循环当中执行 nop 空指令,也就意味着即使在延时等待情况下,CPU 也是一直都在工作;由此可知,应用程序当中使用休眠用作延时功能,并不是裸机程序中的 nop 空指令延时,一旦执行 sleep(),进程便主动交出 CPU 使用权,暂时退出系统调度队列,在休眠结束前,该进程的指令将得不到执行。 

申请堆内存

在堆上分配内存:malloc 和 free
Linux C 程序当中一般使用 malloc()函数为程序分配一段堆内存,而使用 free()函数来释放这段内存。

C 函数库中还提供了一系列在堆上分配对齐内存的函数,对齐内存在某些应用场合非常有必要,常用于分配对其内存的库函数有:posix_memalign()、aligned_alloc()、memalign()、valloc()、pvalloc(),具体需要时查阅即可。

proc文件系统

proc 文件系统是一个虚拟文件系统,它以文件系统的方式为应用层访问系统内核数据提供了接口,用户和应用程序可以通过 proc 文件系统得到系统信息和进程相关信息,对 proc 文件系统的读写作为与内核进行通信的一种手段。但是与普通文件不同的是,proc 文件系统是动态创建的,文件本身并不存在于磁盘当中、只存在于内存当中,与 devfs 一样,都被称为虚拟文件系统。
最初构建 proc 文件系统是为了提供有关系统中进程相关的信息,但是由于这个文件系统非常有用,因此内核中的很多信息也开始使用它来报告,或启用动态运行时配置。内核构建 proc 虚拟文件系统,它会将内核运行时的一些关键数据信息以文件的方式呈现在 proc 文件系统下的一些特定文件中,这样相当于将一些不可见的内核中的数据结构以可视化的方式呈现给应用层。
proc 文件系统挂载在系统的/proc 目录下,对于内核开发者(譬如驱动开发工程师)来说,proc 文件系统给了开发者一种调试内核的方法:通过查看/proc/xxx 文件来获取到内核特定数据结构的值,在添加了新功能前后进行对比,就可以判断此功能所产生的影响是否合理。/proc 目录下中包含了一些目录和虚拟文件,如下所示: 

可以看到 /proc 目录下有很多以数字命名的文件夹,譬如 100038 2299 98560 ,这些数字对应的其实就是一个一个的进程 PID 号,每一个进程在内核中都会存在一个编号,通过此编号来区分不同的进程,这个编号就是 PID 号。
所以这些以数字命名的文件夹中记录了这些进程相关的信息,不同的信息通过不同的虚拟文件呈现出来。
/proc 目录下除了文件夹之外,还有很多的虚拟文件,譬如 buddyinfo、cgroups、cmdline、version 等等,不同的文件记录了不同信息,关于这些文件记录的信息和意思如下:
⚫ cmdline:内核启动参数;
⚫ cpuinfo:CPU 相关信息;
⚫ iomem:IO 设备的内存使用情况;
⚫ interrupts:显示被占用的中断号和占用者相关的信息;
⚫ ioports:IO 端口的使用情况;
⚫ kcore:系统物理内存映像,不可读取;
⚫ loadavg:系统平均负载;
⚫ meminfo:物理内存和交换分区使用情况;
⚫ modules:加载的模块列表;
⚫ mounts:挂载的文件系统列表;
⚫ partitions:系统识别的分区表;
⚫ swaps:交换分区的利用情况;
⚫ version:内核版本信息;
⚫ uptime:系统运行时间;
proc 文件系统的使用
proc 文件系统的使用就是去读取/proc 目录下的这些文件,获取文件中记录的信息,可以直接使用 cat 命令读取,也可以在应用程序中调用 open()打开、然后再使用 read()函数读取。
使用 cat 命令读取
在 Linux 系统下直接使用 cat 命令查看/proc 目录下的虚拟文件,譬如"cat /proc/version"查看内核版本相关信息:
使用 read() 函数读取
编写一个简单地程序,使用 read() 函数读取 /proc/version 文件。
更多待补充。

返回错误处理与 errno

在上一章节中,笔者给大家编写了很多的示例代码,大家会发现这些示例代码会有一个共同的特点,那就是当判断函数执行失败后,会调用 return 退出程序,但是对于我们来说,我们并不知道为什么会出错,什么原因导致此函数执行失败,因为执行出错之后它们的返回值都是-1。

难道我们真的就不知道错误原因了吗?其实不然,在 Linux 系统下对常见的错误做了一个编号,每一个编号都代表着每一种不同的错误类型,当函数执行发生错误的时候,操作系统会将这个错误所对应的编号赋值给 errno 变量,每一个进程(程序)都维护了自己的 errno 变量,它是程序中的全局变量,该变量用于存储就近发生的函数执行错误编号,也就意味着下一次的错误码会覆盖上一次的错误码。所以由此可知道,当程序中调用函数发生错误的时候,操作系统内部会通过设置程序的 errno 变量来告知调用者究竟发生了什么错误!

errno 本质上是一个 int 类型的变量,用于存储错误编号,但是需要注意的是,并不是执行所有的系统调用或 C 库函数出错时,操作系统都会设置 errno,那我们如何确定一个函数出错时系统是否会设置 errno 呢?

其实这个通过 man 手册便可以查到,譬如以 open 函数为例,执行"man 2 open"打开 open 函数的帮助信息,找到函数返回值描述段,如下所示:

从图中红框部分描述文字可知,当函数返回错误时会设置 errno,当然这里是以 open 函数为例,其它的系统调用也可以这样查找,大家可以自己试试!

在我们的程序当中如何去获取系统所维护的这个errno变量呢?只需要在我们程序当中包含<errno.h>头文件即可,你可以直接认为此变量就是在<errno.h>头文件中的申明的,好,我们来测试下:

#include <stdio.h>
#include <errno.h>

int main(void)
{
    printf("%d\n", errno);
    return 0;
}

以上的这段代码是不会报错的,大家可以自己试试!

strerror 函数

前面给大家说到了 errno 变量,但是 errno 仅仅只是一个错误编号,对于开发者来说,即使拿到了 errno也不知道错误为何?还需要对比源码中对此编号的错误定义,可以说非常不友好,这里介绍一个 C 库函数strerror(),该函数可以将对应的 errno 转换成适合我们查看的字符串信息,其函数原型如下所示(可通过"man 3 strerror"命令查看,注意此函数是 C 库函数,并不是系统调用):

#include <string.h>

char *strerror(int errnum);

首先调用此函数需要包含头文件<string.h>。

函数参数和返回值如下:

errnum:错误编号 errno。

返回值:对应错误编号的字符串描述信息。

{
    int fd;
    /* 打开文件 */
    fd = open("./test_file", O_RDONLY);
    if (-1 == fd) {
        printf("Error: %s\n", strerror(errno));
        return -1;
    }
    close(fd);
    return 0;
}

编译源代码,在 Ubuntu 系统下运行测试下,在当前目录下并不存在 test_file 文件,测试打印结果如下:

从打印信息可以知道,strerror 返回的字符串是"No such file or directory",所以从打印信息可知,我们就可以很直观的知道 open 函数执行的错误原因是文件不存在!

errno查询表如下:

Linux errno查询表-CSDN博客 

perror 函数

除了 strerror 函数之外,我们还可以使用 perror 函数来查看错误信息,一般用的最多的还是这个函数,调用此函数不需要传入 errno,函数内部会自己去获取 errno 变量的值,调用此函数会直接将错误提示字符串打印出来,而不是返回字符串,除此之外还可以在输出的错误提示字符串之前加入自己的打印信息,函数原型如下所示(可通过"man 3 perror"命令查看):

#include <stdio.h>

void perror(const char *s);

需要包含<stdio.h>头文件。

函数参数和返回值含义如下:

s:在错误提示字符串信息之前,可加入自己的打印信息,也可不加,不加则传入空字符串即可。

返回值:void 无返回值。

示例片段如下:

{
    int fd;
    /* 打开文件 */
    fd = open("./test_file", O_RDONLY);
    if (-1 == fd) {
        perror("open error");
        return -1;
    }
    close(fd);
    return 0;
}

编译源代码,在 Ubuntu 系统下运行测试下,在当前目录下并不存在 test_file 文件,测试打印结果如下:

从打印信息可以知道,perror 函数打印出来的错误提示字符串是"No such file or directory",跟 strerror 函数返回的字符串信息一样,"open error"便是我们附加的打印信息,而且从打印信息可知,perror 函数会在附加信息后面自动加入冒号和空格以区分。

以上给大家介绍了 strerror、perror 两个 C 库函数,都是用于查看函数执行错误时对应的提示信息,大家用哪个函数都可以,这里笔者推荐大家使用 perror,在实际的编程中这个函数用的还是比较多的,当然除了这两个之外,其它其它一些类似功能的函数,这里就不再给大家介绍了,意义不大!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值