在应用程序当中,有时往往需要去获取到一些系统相关的信息,譬如时间、日期、以及其它一些系统相关信息,本章将向大家介绍如何通过 Linux 系统调用或
C
库函数获取系统信息,譬如获取系统时间、日期以及设置系统时间、日期等;除此之外,还会向大家介绍 Linux
系统下的
/proc
虚拟文件系统,包括
/proc
文件系统是什么以及如何从/proc
文件系统中读取系统、进程有关信息。
除了介绍系统信息内容外,本章还会向大家介绍有关系统资源的使用,譬如系统内存资源的申请与使用等。好了,废话不多少,开始本章内容的学习吧!
7.1 系统信息
7.1.1 系统标识 uname
系统调用 uname()
用于获取有关当前操作系统内核的名称和信息,函数原型如下所示(可通过
"man 2 uname"命令查看):
使用该函数需要包含头文件<sys/utsname.h>
。
函数参数和返回值含义如下:
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
};
可以看到,struct utsname
结构体中的所有成员变量都是字符数组,所以获取到的信息都是字符串。
测试
编写一个简单地程序,获取并打印出当前操作系统名称、主机名、内核版本、操作系统发行版本以及处理器硬件架构类型等信息,测试代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <sys/utsname.h>
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);
}
7.1.2 sysinfo 函数
sysinfo 系统调用可用于获取一些系统统计信息,其函数原型如下所示:
#include <sys/sysinfo.h>
int sysinfo(struct sysinfo *info);
函数参数和返回值含义如下:
info:
struct sysinfo
结构体类型指针,指向一个
struct sysinfo
结构体类型对象。
返回值:成功返回
0
;失败将返回
-1
,并设置
errno
。
同样 sysinfo()
函数用法也非常简单,先定义一个
struct sysinfo
结构体变量,调用
sysinfo()
函数时传入变量的地址即可,struct sysinfo
结构体如下所示:
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 */
};
测试
#include <stdio.h>
#include <stdlib.h>
#include <sys/sysinfo.h>
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);
}
7.1.3 gethostname 函数
此函数可用于单独获取 Linux
系统主机名,与
struct utsname
数据结构体中的
nodename
变量一样, gethostname 函数原型如下所示(可通过
"man 2 gethostname"
命令查看):
#include <unistd.h>
int gethostname(char *name, size_t len);
使用此函数需要包含头文件<unistd.h>
。
函数参数和返回值含义如下:
name:
指向用于存放主机名字符串的缓冲区。
len:
缓冲区长度。
返回值:成功返回
0,
;失败将返回
-1
,并会设置
errno
。
测试
使用 gethostname
函数获取系统主机名:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
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);
}
7.1.4 sysconf()函数
sysconf()函数是一个库函数,可在运行时获取系统的一些配置信息,譬如页大小(
page size
)、主机名的最大长度、进程可以打开的最大文件数、每个用户 ID
的最大并发进程数等。其函数原型如下所示:
#include <unistd.h>
long sysconf(int name);
使用该函数需要包含头文件<unistd.h>
。
调用 sysconf()
函数获取系统的配置信息,参数
name
指定了要获取哪个配置信息,参数
name
可取以下任何一个值(都是宏定义,可通过 man
手册查询):
- _SC_ARG_MAX:exec 族函数的参数的最大长度,exec 族函数后面会介绍,这里先不管!
- _SC_CHILD_MAX:每个用户的最大并发进程数,也就是同一个用户可以同时运行的最大进程数。
- _SC_HOST_NAME_MAX:主机名的最大长度。
- _SC_LOGIN_NAME_MAX:登录名的最大长度。
- _SC_CLK_TCK:每秒时钟滴答数,也就是系统节拍率。
- _SC_OPEN_MAX:一个进程可以打开的最大文件数。
- _SC_PAGESIZE:系统页大小(page size)。
- _SC_TTY_NAME_MAX:终端设备名称的最大长度。
- ……
除以上之外,还有很多,这里就不再一一列举了,可以通过 man
手册进行查看,用的比较多的是 _SC_PAGESIZE 和
_SC_CLK_TCK
,在后面章节示例代码中有使用到。
若指定的参数 name
为无效值,则
sysconf()
函数返回
-1
,并会将
errno
设置为
EINVAL
。否则返回的值便是对应的配置值。注意,返回值是一个 long
类型的数据。
使用示例
获取每个用户的最大并发进程数、系统节拍率和系统页大小。
7.2 时间、日期
本小节向大家介绍下时间、日期相关的系统调用或 C
库函数以及它们的使用方法。
7.2.1 时间的概念
时间相关的基本概念:GMT
时间、
UTC
时间以及时区等。
GMT(
Greenwich Mean Time
)中文全称是格林威治标准时间。
UTC(
Coordinated Universal Time
)指的是世界协调时间(又称世界标准时间、世界统一时间)。
CST 在指的是
China Standard Time
(中国标准时间)的缩写。
7.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
作为系统节拍率。
内核其实通过 jiffies
来维护系统时钟,全局变量
jiffies
在系统开机启动时会设置一个初始值,上面也给大家提到过,RTC
实时时钟会在系统开机启动时读取一次,目的是用于对系统时钟进行初始化,这里说的初始化其实指的就是对内核的 jiffies
变量进行初始化操作,具体如何将读取到的实时时钟换算成
jiffies
数值,这里便不再给大家介绍了。
所以由此可知,操作系统使用 jiffies
这个全局变量来记录当前时间,当我们需要获取到系统当前时间点时,就可以使用 jiffies
变量去计算,当然并不需要我们手动去计算,
Linux
系统提供了相应的系统调用或
C库函数用于获取当前时间,譬如系统调用 time()
、
gettimeofday()
,其实质上就是通过
jiffies
变量换算得到。
7.2.3 获取时间 time/gettimeofday
(1)time 函数
系统调用 time()
用于获取当前时间,以秒为单位,返回得到的值是自
1970-01-01 00:00:00 +0000 (UTC) 以来的秒数,其函数原型如下所示(可通过"man 2 time"
命令查看):
#include <time.h>
time_t time(time_t *tloc);
使用该函数需要包含头文件<time.h>
。
函数参数和返回值含义如下:
tloc:
如果
tloc
参数不是
NULL
,则返回值也存储在
tloc
指向的内存中。
返回值:成功则返回自
1970-01-01 00:00:00 +0000 (UTC)
以来的时间值(以秒为单位);失败则返回
-1
,并会设置 errno
。
所以由此可知,time
函数获取得到的是一个时间段,也就是从
1970-01-01 00:00:00 +0000 (UTC)
到现在这段时间所经过的秒数,所以你要计算现在这个时间点,只需要使用 time()
得到的秒数加
1970-01-01 00:00:00即可!当然,这并不需要我们手动去计算,可以直接使用相关系统调用或 C
库函数来得到当前时间,后面再给大家介绍。
自 1970-01-01 00:00:00 +0000 (UTC)
以来经过的总秒数,我们把这个称之为日历时间或
time_t
时间。
测试
使用系统调用 time()
获取自
1970-01-01 00:00:00 +0000 (UTC)
以来的时间值:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
time_t t;
t = time(NULL);
if (-1 == t) {
perror("time error");
exit(-1);
}
printf("时间值: %ld\n", t);
exit(0);
}
(2)gettimeofday
函数
time()获取到的时间只能精确到秒,如果想要获取更加精确的时间可以使用系统调用
gettimeofday
来实现,gettimeofday()
函数提供微秒级时间精度,函数原型如下所示(可通过
"man 2 gettimeofday"
命令查看):
#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz);
使用该函数需要包含头文件<sys/time.h>
。
函数参数和返回值含义如下:
tv:
参数
tv
是一个
struct timeval
结构体指针变量,
struct timeval
结构体在前面章节内容中已经给大家介绍过,具体参考示例代码 5.6.3
。
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
字段的数值相同。
测试
使用 gettimeofday
获取自
1970-01-01 00:00:00 +0000 (UTC)
以来的时间值:
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
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);
}
7.2.4 时间转换函数
通过 time()
或
gettimeofday()
函数可以获取到当前时间点相对于
1970-01-01 00:00:00 +0000 (UTC)这个时间点所经过时间(日历时间),所以获取得到的是一个时间段的长度,但是这并不利于我们查看当前时间,这个结果对于我们来说非常不友好,那么本小节将向大家介绍一些系统调用或 C 库函数,通过这些 API 可以将 time()或 gettimeofday()函数获取到的秒数转换为利于查看和理解的形式。
(1)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()
函数需要调用者提供用于存放字符串的缓冲区。
Tips:关于可重入函数与不可重入函数将会在后面章节内容中进行介绍,这里暂时先不去管这个问题,在 Linux 系统中,有一些系统调用或 C 库函数提供了可重入版本与不可重入版本的函数接口,可重入版本函数所对应的函数名一般都会有一个" _r "后缀来表明它是一个可重入函数。
ctime(或ctime_r)转换得到的时间是计算机所在地对应的本地时间(譬如在中国对应的便是北京时间),并不是 UTC 时间,接下来编写一段简单地代码进行测试。
测试
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
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);
}
(2)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 结构体如下所示:
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
时间总秒数分解成了各个独立的时间信息,易于我们查看和理解。
测试
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
struct tm t;
time_t sec;
/* 获取时间 */
sec = time(NULL);
if (-1 == sec) {
perror("time error");
exit(-1);
}
/* 转换得到本地时间 */
localtime_r(&sec, &t);
/* 打印输出 */
printf("当前时间: %d 年%d 月%d 日 %d:%d:%d\n",
t.tm_year + 1900, t.tm_mon, t.tm_mday,
t.tm_hour, t.tm_min, t.tm_sec);
exit(0);
}
(3)gmtime 函数
gmtime()函数也可以把
time_t
时间变成一个
struct tm
结构体所表示的时间,与
localtime()
所不同的是,gmtime()函数所得到的是
UTC
国际标准时间,并不是计算机的本地时间,这是它们之间的唯一区别。
gmtime()函数原型如下所示:
#include <time.h>
struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *timep, struct tm *result);
同样使用 gmtime()
函数需要包含头文件
<time.h>
。
gmtime_r()是
gmtime()
的可重入版本,同样也是推荐大家使用可重入版本函数
gmtime_r
。关于该函数的参数和返回值,这里便不再介绍,与 localtime()
是一样的。
测试
使用 localtime
获取本地时间、使用
gmtime
获取
UTC
国际标准时间,并进行对比:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
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+1, 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+1, utc_t.tm_mday,
utc_t.tm_hour, utc_t.tm_min, utc_t.tm_sec);
exit(0);
}
(4)mktime 函数
mktime()函数与
localtime()
函数相反,
mktime()
可以将使用
struct tm
结构体表示的分解时间转换为
time_t时间(日历时间),同样这也是一个 C
库函数,其函数原型如下所示:
#include <time.h>
time_t mktime(struct tm *tm);
使用该函数需要包含头文件<time.h>
。
函数参数和返回值含义如下:
tm:
需要进行转换的
struct tm
结构体变量对应的指针。
返回值:成功返回转换得到
time_t
时间值;失败返回
-1
。
测试
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
struct tm local_t;
time_t sec;
/* 获取时间 */
sec = time(NULL);
if (-1 == sec) {
perror("time error");
exit(-1);
}
printf("获取得到的秒数: %ld\n", sec);
localtime_r(&sec, &local_t);
printf("转换得到的秒数: %ld\n", mktime(&local_t));
exit(0);
}
(5)asctime 函数
asctime()函数与
ctime()
函数的作用一样,也可将时间转换为可打印输出的字符串形式,与
ctime()
函数的区别在于,ctime()
是将
time_t
时间转换为固定格式字符串、而
asctime()
则是将
struct tm
表示的分解时间转换为固定格式的字符串。asctime()
函数原型如下所示:
#include <time.h>
char *asctime(const struct tm *tm);
char *asctime_r(const struct tm *tm, char *buf);
使用该函数需要包含头文件<time.h>
。
函数参数和返回值含义如下:
tm:
需要进行转换的
struct tm
表示的时间。
buf:
可重入版本函数
asctime_r
需要额外提供的参数
buf
,指向一个缓冲区,用于存放转换得到的字符串。
返回值:转换失败将返回
NULL
;成功将返回一个
char *
类型指针,指向转换后得到的时间字符串,对于 asctime_r
函数来说,返回值就等于参数
buf
。
测试
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
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);
asctime_r(&local_t, tm_str);
printf("本地时间: %s", tm_str);
exit(0);
}
(6)strftime 函数
除了 asctime()
函数之外,这里再给大家介绍一个
C
库函数
strftime()
,此函数也可以将一个
struct tm
变量表示的分解时间转换为为格式化字符串,并且在功能上比 asctime()
和
ctime()
更加强大,它可以根据自己的喜好自定义时间的显示格式,而 asctime()
和
ctime()
转换得到的字符串时间格式的固定的。
strftime()函数原型如下所示:
#include <time.h>
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
使用该函数需要包含头文件<time.h>
。
函数参数和返回值含义如下:
s:
指向一个缓存区的指针,该缓冲区用于存放生成的字符串。
max:
字符串的最大字节数。
format:
这是一个用字符串表示的字段,包含了普通字符和特殊格式说明符,可以是这两种字符的任意组合。特殊格式说明符将会被替换为 struct tm
结构体对象所指时间的相应值,这些特殊格式说明符如下:
strftime 函数的特殊格式说明符还是比较多的,不用去记它,需要用的时候再去查即可!
通过上表可知,譬如我要想输出"2021-01-14 16:30:25<PM> January Thursday"
这样一种形式表示的时间日期,那么就可以这样来设置 format
参数:
"%Y-%m-%d %H:%M:%S<%p> %B %A"
tm:
指向
struct tm
结构体对象的指针。
返回值:如果转换得到的目标字符串不超过最大字节数(也就是
max
),则返回放置到
s
数组中的字节数;如果超过了最大字节数,则返回 0
。
测试
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
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);
}
7.2.5 设置时间 settimeofday
使用 settimeofday()
函数可以设置时间,也就是设置系统的本地时间,函数原型如下所示:
#include <sys/time.h>
int settimeofday(const struct timeval *tv, const struct timezone *tz);
首先使用该函数需要包含头文件<sys/time.h>
。
函数参数和返回值含义如下:
tv:
参数
tv
是一个
struct timeval
结构体指针变量,
struct timeval
结构体在前面章节内容中已经给大家介绍了,需要设置的时间便通过参数 tv
指向的
struct timeval
结构体变量传递进去。
tz:
参数
tz
是个历史产物,早期实现用其来设置系统的时区信息,目前已遭废弃,在调用
settimeofday()函数时应将参数 tz
设置为
NULL
。
返回值:成功返回
0
;失败将返回
-1
,并设置
errno
。
使用 settimeofday
设置系统时间时内核会进行权限检查,只有超级用户(
root
)才可以设置系统时间,普通用户将无操作权限。
7.2.6 总结
本小节给大家介绍了时间相关的基本概念,譬如 GMT
时间、
UTC
时间以及全球
24
个时区的划分等,并 且 给 大 家 介 绍 了 Linux
系 统 下 常 用 的 时 间 相 关 的 系 统 调 用 和 库 函 数 , 主 要 有
9
个 :time/ctime/localtime/gmtime/mktime/asctime/strftime/gettimeofday/settimeofday,对这些函数的功能、作用总结如下:
7.3 进程时间
进程时间指的是进程从创建后(也就是程序运行后)到目前为止这段时间内使用 CPU
资源的时间总数,出于记录的目的,内核把 CPU
时间(进程时间)分为以下两个部分:
- 用户 CPU 时间:进程在用户空间(用户态)下运行所花费的 CPU 时间。有时也成为虚拟时间(virtual time)。
- 系统 CPU 时间:进程在内核空间(内核态)下运行所花费的 CPU 时间。这是内核执行系统调用或代表进程执行的其它任务(譬如,服务页错误)所花费的时间。
一般来说,进程时间指的是用户 CPU
时间和系统
CPU
时间的总和,也就是总的
CPU
时间。
Tips:进程时间不等于程序的整个生命周期所消耗的时间,如果进程一直处于休眠状态(进程被挂起、不会得到系统调度),那么它并不会使用 CPU 资源,所以休眠的这段时间并不计算在进程时间中。
7.3.1 times 函数
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
结构体内容如下所示:
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()
来计算程序中某一段代码执行所耗费的进程时间和总的时间,测试程序如下所示:
#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);
}
程序中使用 sysconf(_SC_CLK_TCK)
获取到系统节拍率,程序还使用了一个库函数 sleep(),该函数也是本章将要向大家介绍的函数,具体参考
7.5.1
小节中的介绍。
示例代码 7.3.2
中对如下代码段进行了测试:
for (i = 0; i < 20000; i++)
for (j = 0; j < 20000; j++)
;
sleep(1); //休眠挂起
可以看到用户 CPU
时间为
0.11
秒,系统
CPU
时间为
0
秒,也就是说测试的这段代码并没有进入内核态运行,所以总的进程时间 =
用户
CPU
时间
+
系统
CPU
时间
= 0.11
秒。
显示的时间总和并不是总的进程时间,前面也给大家解释过,这个时间总和指的是从起点到终点锁经过的时间,并不是进程时间,这里大家要理解。时间总和包括了进程处于休眠状态时消耗的时间(sleep 等会让进程挂起、进入休眠状态),可以发现时间总和比进程时间多
1
秒,其实这一秒就是进程处于休眠状态的时间。
7.3.2 clock 函数
库函数 clock()
提供了一个更为简单的方式用于进程时间,它的返回值描述了进程使用的总的
CPU
时间(也就是进程时间,包括用户 CPU
时间和系统
CPU
时间),其函数原型如下所示:
#include <time.h>
clock_t clock(void);
使用该函数需要包含头文件<time.h>
。
函数参数和返回值含义如下:
无参数。
返回值:返回值是到目前为止程序的进程时间,为
clock_t
类型,注意
clock()
的返回值并不是系统节拍数,如果想要获得秒数,请除以 CLOCKS_PER_SEC
(这是一个宏)。如果返回的进程时间不可用或其值无法表示,则该返回值是-1
。
clock()函数虽然可以很方便的获取总的进程时间,但并不能获取到单独的用户
CPU
时间和系统
CPU
时间,在实际编程当中,根据自己的需要选择。
测试
使用
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);
}
7.4 产生随机数
在应用编程当中可能会用到随机数,譬如老板让你编写一个抽奖的小程序,编号 0~100
,分为特等奖
1个、一等奖 2
个、二等奖
3
以及三等奖
4
个,也就是说需要从
0~100
个编号中每次随机抽取一个号码,这就需要用到随机数。那在 Linux
应用编程中如何去产生随机数呢?本小节就来学习生成随机数。
随机数与伪随机数
随机数是随机出现,没有任何规律的一组数列。在我们编程当中,是没有办法获得真正意义上的随机数列的,这是一种理想的情况,在我们的程序当中想要使用随机数列,只能通过算法得到一个伪随机数序列,那在编程当中说到的随机数,基本都是指伪随机数。
C 语言函数库中提供了很多函数用于产生伪随机数,其中最常用的是通过
rand()
和
srand()
产生随机数,本小节就以这两个函数为例向大家介绍如何在我们的程序中获得随机数列。
rand 函数
rand()函数用于获取随机数,多次调用
rand()
可得到一组随机数序列,其函数原型如下:
#include <stdlib.h>
int rand(void);
使用该函数需要包含头文件<stdlib.h>
。
函数参数和返回值含义如下:
返回值:返回一个介于
0
到
RAND_MAX
(包含)之间的值,也就是数学上的
[0,RAND_MAX]
。
程度当中调用 rand()
可以得到
[0, RAND_MAX]
之间的伪随机数,多次调用
rand()
便可以生成一组伪随机树序列,但是这里有个问题,就是每一次运行程序所得到的随机数序列都是相同的,那如何使得每一次启动应用程序所得到的随机数序列是不一样的呢?那就通过设置不同的随机数种子,可通过 srand()
设置随机数种子。
如果没有调用 srand()
设置随机数种子的情况下,
rand()
会将
1
作为随机数种子,如果随机数种子相同,那么每一次启动应用程序所得到的随机数序列就是一样的,所以每次启动应用程序需要设置不同的随机数种子,这样就可以使得程序每次运行所得到随机数序列不同。
srand 函数
使用 srand()
函数为
rand()
设置随机数种子,其函数原型如下所示:
#include <stdlib.h>
void srand(unsigned int seed);
函数参数和返回值含义如下:
seed:
指定一个随机数中,
int
类型的数据,一般尝试将当前时间作为随机数种子赋值给参数
seed
,譬如 time(NULL)
,因为每次启动应用程序时间上是一样的,所以就能够使得程序中设置的随机数种子在每次启动程序时是不一样的。
返回值:void
常用的用法 srand(time(NULL));
测试
使用 rand()
和
srand()
产生一组伪随机数,数值范围为
[0~100]
,将其打印出来:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char *argv[])
{
int random_number_arr[8];
int count;
/* 设置随机数种子 */
srand(time(NULL));
/* 生成伪随机数 */
for (count = 0; count < 8; count++)
random_number_arr[count] = rand() % 100;
/* 打印随机数数组 */
printf("[");
for (count = 0; count < 8; count++) {
printf("%d", random_number_arr[count]);
if (count != 8 - 1)
printf(", ");
}
printf("]\n");
exit(0);
}
从图中可以发现,每一次得到的[0~100]
之间的随机数数组都是不同的(数组不同,不是产生的随机数不同),因为程序中将 rand()
的随机数种子设置为
srand(time(NULL))
,直接等于
time_t
时间值,意味着每次启动种子都不一样,所以能够产生不同的随机数数组。
本小节关于在
Linux
下使用随机数就给大家介绍这么多,产生随机数的
API
函数并不仅仅只有这些,除此之外,譬如还有 random()
、
srandom()
、
initstate()
、
setstate()
等,这里便不再给大家一一介绍了,在我们使用 man
手册查看系统调用或
C
库函数帮助信息时,在帮助信息页面
SEE ALSO
栏会列举出与本函数有关联的一些命令、系统调用或 C
库函数等。
7.5 休眠
有时需要将进程暂停或休眠一段时间,进入休眠状态之后,程序将暂停运行,直到休眠结束。常用的系统调用和 C 库函数有
sleep()
、
usleep()
以及
nanosleep()
,这些函数在应用程序当中通常作为延时使用,譬如延时 1
秒钟,本小节将一一介绍。
7.5.1 秒级休眠: sleep
sleep()是一个
C
库函数,从函数名字面意思便可以知道该函数的作用了,简单地说,
sleep()
就是让程序“休息”一会,然后再继续工作。其函数原型如下所示:
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
使用该函数需要包含头文件<unistd.h>
。
函数参数和返回值含义如下:
seconds:
休眠时长,以秒为单位。
返回值:如果休眠时长为参数
seconds
所指定的秒数,则返回
0
;若被信号中断则返回剩余的秒数。
sleep()是一个秒级别休眠函数,程序在休眠过程中,是可以被其它信号所打断的,关于信号这些内容,将会在后面章节向大家介绍。
测试
编写一个简单地程序,调用 sleep()
函数让程序暂停运行(休眠)
3
秒钟。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
puts("Sleep Start!");
/* 让程序休眠 3 秒钟 */
sleep(3);
puts("Sleep End!");
exit(0);
}
7.5.2 微秒级休眠: usleep
usleep()同样也是一个
C
库函数,与
sleep()
的区别在于休眠时长精度不同,
usleep()
支持微秒级程序休眠,其函数原型如下所示:
#include <unistd.h>
int usleep(useconds_t usec);
函数参数和返回值含义如下:
usec:
休眠时长,以微秒为单位。
返回值:成功返回
0
;失败返回
-1
,并设置
errno
。
测试
使用 usleep()
函数让程序休眠
3
秒钟。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
puts("Sleep Start!");
/* 让程序休眠 3 秒钟(3*1000*1000 微秒) */
usleep(3 * 1000 * 1000);
puts("Sleep End!");
exit(0);
}
7.5.3 高精度休眠:nanosleep
nanosleep()与
sleep()
以及
usleep()
类似,都用于程序休眠,但
nanosleep()
具有更高精度来设置休眠时间长度,支持纳秒级时长设置。与 sleep()
、
usleep()
不同的是,
nanosleep()
是一个
Linux
系统调用,其函数原型如下所示:
#include <time.h>
int nanosleep(const struct timespec *req, struct timespec *rem);
使用该函数需要包含头文件<time.h>
。
函数参数与返回值含义如下:
req:
一个
struct timespec
结构体指针,指向一个
struct timespec
变量,用于设置休眠时间长度,可精确到纳秒级别。
rem:
也是一个
struct timespec
结构体指针,指向一个
struct timespec
变量,也可设置
NULL
。
返回值:在成功休眠达到请求的时间间隔后,
nanosleep()
返回
0
;如果中途被信号中断或遇到错误,则返回-1
,并将剩余时间记录在参数
rem
指向的
struct timespec
结构体变量中(参数
rem
不为
NULL
的情况下,如果为 NULL
表示不接收剩余时间),还会设置
errno
标识错误类型。
在
5.2.3
小节中介绍了
struct timespec
结构体,该结构体包含了两个成员变量,秒(
tv_sec
)和纳秒(tv_nsec),具体定义可参考示例代码 5.2.2
。
测试
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
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);
}
前面说到,在应用程序当中,通常使用这些函数作为延时功能,譬如在程序当中需要延时一秒钟、延时5 毫秒等应用场景时,那么就可以使用这些函数来实现;但是大家需要注意,休眠状态下,该进程会失去 CPU使用权,退出系统调度队列,直到休眠结束。在一个裸机程序当中,通常使用 for
循环(或双重
for
循环)语句来实现延时等待,譬如在 for
循环当中执行
nop
空指令,也就意味着即使在延时等待情况下,
CPU
也是一直都在工作;由此可知,应用程序当中使用休眠用作延时功能,并不是裸机程序中的 nop
空指令延时,一旦执行 sleep()
,进程便主动交出
CPU
使用权,暂时退出系统调度队列,在休眠结束前,该进程的指令将得不到执行。
7.6 申请堆内存
在操作系统下,内存资源是由操作系统进行管理、分配的,当应用程序想要内存时(这里指的是堆内存),可以向操作系统申请内存,然后使用内存;当不再需要时,将申请的内存释放、归还给操作系统;在许多的应用程序当中,往往都会有这种需求,譬如为一些数据结构动态分配/释放内存空间,本小节向大家介绍应用程序如何向操作系统申请堆内存。
7.6.1 在堆上分配内存:malloc 和 free
Linux C 程序当中一般使用
malloc()
函数为程序分配一段堆内存,而使用
free()
函数来释放这段内存,先来看下 malloc()
函数原型,如下所示:
#include <stdlib.h>
void *malloc(size_t size);
使用该函数需要包含头文件<stdlib.h>
。
函数参数和返回值含义如下:
size:
需要分配的内存大小,以字节为单位。
返回值:返回值为
void *
类型,如果申请分配内存成功,将返回一个指向该段内存的指针,
void *
并不是说没有返回值或者返回空指针,而是返回的指针类型未知,
所以在调用
malloc()
时通常需要进行强制类型转换,将 void *
指针类型转换成我们希望的类型;如果分配内存失败(譬如系统堆内存不足)将返回
NULL
,如果参数 size
为
0
,返回值也是
NULL
。
malloc()在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的,所以通常需要程序员对 malloc()
分配的堆内存进行初始化操作。
在堆上分配的内存,需要开发者自己手动释放掉,通常使用 free()
函数释放堆内存,
free()
函数原型如下所示:
#include <stdlib.h>
void free(void *ptr);
使用该函数同样需要包含头文件<stdlib.h>
。
函数参数和返回值含义如下:
ptr:
指向需要被释放的堆内存对应的指针。
返回值:无返回值。
测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MALLOC_MEM_SIZE (1 * 1024 * 1024)
int main(int argc, char *argv[])
{
char *base = NULL;
/* 申请堆内存 */
base = (char *)malloc(MALLOC_MEM_SIZE);
if (NULL == base) {
printf("malloc error\n");
exit(-1);
}
/* 初始化申请到的堆内存 */
memset(base, 0x0, MALLOC_MEM_SIZE);
/* 使用内存 */
/* ...... */
/* 释放内存 */
free(base);
exit(0);
}
调用 free()
还是不调用
free()
在学习文件 IO
基础章节内容时曾向大家介绍过,
Linux
系统中,当一个进程终止时,内核会自动关闭它没有关闭的所有文件(该进程打开的文件,但是在进程终止时未调用 close()
关闭它)。同样,对于内存来说,也是如此!当进程终止时,内核会将其占用的所有内存都返还给操作系统,这包括在堆内存中由 malloc()函数所分配的内存空间。基于内存的这一自动释放机制,很多应用程序通常会省略对 free()
函数的调用。
这在程序中分配了多块内存的情况下可能会特别有用,因为加入多次对 free()
的调用不但会消耗品大量的 CPU
时间,而且可能会使代码趋于复杂。
虽然依靠终止进程来自动释放内存对大多数程序来说是可以接受的,但最好能够在程序中显式调用 free()释放内存,首先其一,显式调用
free()
能使程序具有更好的可读性和可维护性;其二,对于很多程序来说,申请的内存并不是在程序的生命周期中一直需要,大多数情况下,都是根据代码需求动态申请、释放的,如果申请的内存对程序来说已经不再需要了,那么就已经把它释放、归还给操作系统,如果持续占用,将会导致内存泄漏,也就是人们常说的“你的程序在吃内存”!
7.6.2 在堆上分配内存的其它方法
除了 malloc()
外,
C
函数库中还提供了一系列在堆上分配内存的其它函数,本小节将逐一介绍。
用 calloc()
分配内存
calloc()函数用来动态地分配内存空间并初始化为
0
,其函数原型如下所示:
#include <stdlib.h>
void *calloc(size_t nmemb, size_t size);
使用该函数同样也需要包含头文件<stdlib.h>
。
calloc()在堆中动态地分配
nmemb
个长度为
size
的连续空间,并将每一个字节都初始化为
0
。所以它的结果是分配了 nmemb * size
个字节长度的内存空间,并且每个字节的值都是
0
。
返回值:分配成功返回指向该内存的地址,失败则返回
NULL
。
calloc()与
malloc()
的一个重要区别是:
calloc()
在动态分配完内存后,自动初始化该内存空间为零,而malloc()不初始化,里边数据是未知的垃圾数据。下面的两种写法是等价的:
// calloc()分配内存空间并初始化
char *buf1 = (char *)calloc(10, 2);
// malloc()分配内存空间并用 memset()初始化
char *buf2 = (char *)malloc(10 * 2);
memset(buf2, 0, 20);
测试
编写测试代码,将用户输入的一组数字存放到堆内存中,并打印出来。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int *base = NULL;
int i;
/* 校验传参 */
if (2 > argc)
exit(-1);
/* 使用 calloc 申请内存 */
base = (int *)calloc(argc - 1, sizeof(int));
if (NULL == base) {
printf("calloc error\n");
exit(-1);
}
/* 将字符串转为 int 型数据存放在 base 指向的内存中 */
for (i = 0; i < argc - 1; i++)
base[i] = atoi(argv[i+1]);
/* 打印 base 数组中的数据 */
printf("你输入的数据是: ");
for (i = 0; i < argc - 1; i++)
printf("%d ", base[i]);
putchar('\n');
/* 释放内存 */
free(base);
exit(0);
}
7.6.3 分配对其内存
C 函数库中还提供了一系列在堆上分配对齐内存的函数,对齐内存在某些应用场合非常有必要,常用于分配对其内存的库函数有:posix_memalign()
、
aligned_alloc()
、
memalign()
、
valloc()
、
pvalloc()
,它们的函数原型如下所示:
#include <stdlib.h>
int posix_memalign(void **memptr, size_t alignment, size_t size);
void *aligned_alloc(size_t alignment, size_t size);
void *valloc(size_t size);
#include <malloc.h>
void *memalign(size_t alignment, size_t size);
void *pvalloc(size_t size);
使用 posix_memalign()
、
aligned_alloc()
、
valloc()
这三个函数时需要包含头文件
<stdlib.h>
,而使用 memalign()、
pvalloc()
这两个函数时需要包含头文件
<malloc.h>
。前面介绍的
malloc()
、
calloc()
分配内存返回的地址其实也是对齐的,但是它俩的对齐都是固定的,并且对其的字节边界比较小,譬如在 32
位系统中,通常是以 8
字节为边界进行对其,在
64
位系统中是以
16
字节进行对其。如果想实现更大字节的对齐,则需要使用本小节介绍的函数。
posix_memalign()函数
posix_memalign()函数用于在堆上分配
size
个字节大小的对齐内存空间,将
*memptr
指向分配的空间,分配的内存地址将是参数 alignment
的整数倍。参数
alignment
表示对齐字节数,
alignment
必须是
2
的幂次方(譬如 2^4
、
2^5
、
2^8
等),同时也要是
sizeof(void *)
的整数倍,对于
32
位系统来说,
sizeof(void *)
等于4,如果是
64
位系统
sizeof(void *)
等于
8
。
函数参数和返回值含义如下:
memptr:
void **
类型的指针,内存申请成功后会将分配的内存地址存放在
*memptr
中。
alignment:
设置内存对其的字节数,
alignment
必须是
2
的幂次方(譬如
2^4
、
2^5
、
2^8
等),同时也要是 sizeof(void *)
的整数倍。
size:
设置分配的内存大小,以字节为单位,如果参数
size
等于
0
,那么
*memptr
中的值是
NULL
。
返回值:成功将返回
0
;失败返回非
0
值。
示例代码
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int *base = NULL;
int ret;
/* 申请内存: 256 字节对齐 */
ret = posix_memalign((void **)&base, 256, 1024);
if (0 != ret) {
printf("posix_memalign error\n");
exit(-1);
}
/* 使用内存 */
// base[0] = 0;
// base[1] = 1;
// base[2] = 2;
// base[3] = 3;
/* 释放内存 */
free(base);
exit(0);
}
aligned_alloc()函数
aligned_alloc()函数用于分配
size
个字节大小的内存空间,返回指向该空间的指针。
函数参数和返回值含义如下:
alignment:
用于设置对齐字节大小,
alignment
必须是
2
的幂次方(譬如
2^4
、
2^5
、
2^8
等)。
size:
设置分配的内存大小,以字节为单位。参数
size
必须是参数
alignment
的整数倍。
返回值:成功将返回内存空间的指针,内存空间的起始地址是参数
alignment
的整数倍;失败返回
NULL
。
使用示例
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int *base = NULL;
/* 申请内存: 256 字节对齐 */
base = (int *)aligned_alloc(256, 256 * 4);
if (base == NULL) {
printf("aligned_alloc error\n");
exit(-1);
}
/* 使用内存 */
// base[0] = 0;
// base[1] = 1;
// base[2] = 2;
// base[3] = 3;
/* 释放内存 */
free(base);
exit(0);
}
memalign()函数
memalign()与
aligned_alloc()
参数是一样的,它们之间的区别在于:对于参数
size
必须是参数
alignment 的整数倍这个限制条件,memalign()
并没有这个限制条件。
Tips:memalign()函数已经过时了,并不提倡使用!
使用示例
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int main(int argc, char *argv[])
{
int *base = NULL;
/* 申请内存: 256 字节对齐 */
base = (int *)memalign(256, 1024);
if (base == NULL) {
printf("memalign error\n");
exit(-1);
}
/* 使用内存 */
// base[0] = 0;
// base[1] = 1;
// base[2] = 2;
// base[3] = 3;
/* 释放内存 */
free(base);
exit(0);
}
valloc()函数
valloc()分配
size
个字节大小的内存空间,返回指向该内存空间的指针,内存空间的地址是页大小 (pagesize
)的倍数。
valloc()与
memalign()
类似,只不过
valloc()
函数内部实现中,使用了页大小作为对齐的长度,在程序当中,可以通过系统调用 getpagesize()
来获取内存的页大小。
Tips:valloc()函数已经过时了,并不提倡使用!
使用示例
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int *base = NULL;
/* 申请内存: 1024 个字节 */
base = (int *)valloc(1024);
if (base == NULL) {
printf("valloc error\n");
exit(-1);
}
/* 使用内存 */
// base[0] = 0;
// base[1] = 1;
// base[2] = 2;
// base[3] = 3;
/* 释放内存 */
free(base);
exit(0);
}
7.7 proc 文件系统
proc 文件系统是一个虚拟文件系统,它以文件系统的方式为应用层访问系统内核数据提供了接口
,
用户和应用程序可以通过 proc 文件系统得到系统信息和进程相关信息
,对 proc 文件系统的读写作为与内核进行通信的一种手段。但是与普通文件不同的是,proc 文件系统是动态创建的,文件本身并不存在于磁盘当中、
只存在于内存当中,与 devfs 一样,都被称为虚拟文件系统。
最初构建 proc 文件系统是为了提供有关系统中进程相关的信息,但是由于这个文件系统非常有用,因此内核中的很多信息也开始使用它来报告,或启用动态运行时配置。内核构建 proc 虚拟文件系统,它会将内核运行时的一些关键数据信息以文件的方式呈现在 proc 文件系统下的一些特定文件中,这样相当于将一些不可见的内核中的数据结构以可视化的方式呈现给应用层。
proc 文件系统挂载在系统的/proc 目录下,对于内核开发者(譬如驱动开发工程师)来说,proc 文件系统给了开发者一种调试内核的方法:通过查看/proc/xxx 文件来获取到内核特定数据结构的值,在添加了新功能前后进行对比,就可以判断此功能所产生的影响是否合理。
/proc 目录下中包含了一些目录和虚拟文件,如下所示:
可以看到/proc
目录下有很多以数字命名的文件夹,譬如
100038
、
2299
、
98560
,这些数字对应的其实就是一个一个的进程 PID
号,每一个进程在内核中都会存在一个编号,通过此编号来区分不同的进程,这个编号就是 PID
号,关于
PID
、以及进程相关的信息将会在后面章节内容中向大家介绍。
所以这些以数字命名的文件夹中记录了这些进程相关的信息,不同的信息通过不同的虚拟文件呈现出来,关于这些信息将会在后面章节内容中向大家介绍。
/proc 目录下除了文件夹之外,还有很多的虚拟文件,譬如
buddyinfo
、
cgroups
、
cmdline
、
version
等等,不同的文件记录了不同信息,关于这些文件记录的信息和意思如下:
- cmdline:内核启动参数;
- cpuinfo:CPU 相关信息;
- iomem:IO 设备的内存使用情况;
- interrupts:显示被占用的中断号和占用者相关的信息;
- ioports:IO 端口的使用情况;
- kcore:系统物理内存映像,不可读取;
- loadavg:系统平均负载;
- meminfo:物理内存和交换分区使用情况;
- modules:加载的模块列表;
- mounts:挂载的文件系统列表;
- partitions:系统识别的分区表;
- swaps:交换分区的利用情况;
- version:内核版本信息;
- uptime:系统运行时间;
7.7.1 proc 文件系统的使用
proc 文件系统的使用就是去读取
/proc
目录下的这些文件,获取文件中记录的信息,可以直接使用
cat
命令读取,也可以在应用程序中调用 open()
打开、然后再使用
read()
函数读取。
使用 cat
命令读取
在 Linux
系统下直接使用
cat
命令查看
/proc
目录下的虚拟文件,譬如
"cat /proc/version"
查看内核版本相关信息:
使用 read()
函数读取
编写一个简单地程序,使用 read()
函数读取
/proc/version
文件。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
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);
}