参考引用
- UNIX 环境高级编程 (第3版)
- 嵌入式Linux C应用编程-正点原子
1. 系统信息
1.1 系统标识 uname
-
系统调用 uname() 用于获取有关当前操作系统内核的名称和信息
#include <sys/utsname.h> // buf:struct utsname 结构体类型指针,指向一个 struct utsname 结构体类型对象。 // 返回值:成功返回 0;失败将返回-1,并设置 errno int uname(struct utsname *buf);
-
先定义一个 struct utsname 结构体变量,调用 uname() 函数时传入变量地址即可
struct utsname { char sysname[]; /* 当前操作系统的名称 */ char nodename[]; /* 网络上的名称(主机名) */ char release[]; /* 操作系统内核版本 */ char version[]; /* 操作系统发行版本 */ char machine[]; /* 硬件架构类型 */ #ifdef _GNU_SOURCE char domainname[]; /* 当前域名 */ #endif };
-
示例
#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); }
$ gcc uname.c -o uname $ ./uname 操作系统名称: Linux 主机名: yxd-VirtualBox 内核版本: 5.4.0-150-generic 发行版本: #167~18.04.1-Ubuntu SMP Wed May 24 00:51:42 UTC 2023 硬件架构: x86_64
1.2 sysinfo 函数
- sysinfo 系统调用可用于获取一些系统统计信息
#include <sys/sysinfo.h> // info:struct sysinfo 结构体类型指针,指向一个 struct sysinfo 结构体类型对象 // 返回值:成功返回 0;失败将返回-1,并设置 errno int sysinfo(struct sysinfo *info);
- 先定义一个 struct sysinfo 结构体变量,调用 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); }
$ gcc sysinfo.c -o sysinfo $ ./sysinfo uptime: 254 totalram: 8343367680 freeram: 6519500800 procs: 595
1.3 gethostname 函数
- 此函数可用于单独获取 Linux 系统主机名,与 struct utsname 数据结构体中的 nodename 变量一样
#include <unistd.h> // name:指向用于存放主机名字符串的缓冲区 // len:缓冲区长度 // 返回值:成功返回 0,;失败将返回-1,并会设置 errno int gethostname(char *name, size_t len);
- 示例
#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); }
$ gcc hostname.c -o hostname $ ./hostname yxd-VirtualBox
1.4 sysconf() 函数
-
sysconf() 函数是一个库函数,可在运行时获取系统的一些配置信息,如:页大小(page size)、主机名的最大长度、进程可以打开的最大文件数、每个用户 ID 的最大并发进程数等
#include <unistd.h> long sysconf(int name);
-
参数 name 指定了要获取哪个配置信息,参数 name 可取以下任何一个值(加粗用的最多)
- _SC_ARG_MAX: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:终端设备名称的最大长度
-
示例
#include <stdio.h> #include <stdlib.h> #include <unistd.h> 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); }
$ gcc conf.c -o conf $ ./conf 每个用户的最大并发进程数: 31623 系统节拍率: 100 系统页大小: 4096
2. 时间、日期
2.1 时间的概念
-
GMT 时间
- GMT(Greenwich Mean Time)中文全称是格林威治标准时间,这个时间系统的概念在 1884 年被确立,由英国伦敦的格林威治皇家天文台计算并维护
- 1884 年,各国代表在美国华盛顿召开国际大会,通过协议选出英国伦敦的格林威治作为全球时间的中心点,决定以通过格林威治的子午线作为划分东西两半球的经线零度线(本初子午线、零度经线)
- 如 GMT 12:00 就是指英国伦敦的格林威治皇家天文台当地的中午 12:00,与北京时间(东八区)相差 8 个小时,即早八个小时,所以 GMT 12:00 对应的北京时间是 20:00
-
UTC 时间
- UTC(Coordinated Universal Time)指的是世界协调时间(又称世界标准时间、世界统一时间),是经过平均太阳时 (以格林威治时间 GMT 为准)、地轴运动修正后的新时标以及以「秒」为单位的国际原子时所综合精算而成的时间,计算过程相当严谨精密,因此若以「世界标准时间」的角度来说,UTC 比 GMT 来得更加精准
- 在 Ubuntu 系统下,可以使用 “date -u” 命令查看到当前的 UTC 时间
$ date -u 2023年 12月 16日 星期六 07:27:19 UTC
-
时区
- 全球被划分为 24 个时区,每一个时区横跨经度 15 度,以英国格林威治的本初子午线作为零度经线,将全球划分为东西两半球,分为东一区、东二区、东三区……东十二区以及西一区、西二区、西三区……西十二区,而本初子午线所在时区被称为中时区(或者叫零时区)
- 东十二区和西十二区其实是一个时区,就是十二区,东十二区与西十二区各横跨经度 7.5 度,以 180 度经线作为分界线
- 每个时区的中央经线上的时间就是这个时区内统一采用的时间,称为区时。相邻两个时区的时间相差 1 小时
- 如在 Ubuntu 系统下,可以使用 date 命令查看系统当前的本地时间
$ date 2023年 12月 16日 星期六 15:30:51 CST # CST 指的是 China Standard Time(中国标准时间)
- 在 Ubuntu 系统下,时区信息通常以标准格式保存在一些文件当中,这些文件通常位于/usr/share/zoneinfo 目录下
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 在系统开机启动时会设置一个初始值,RTC 实时时钟会在系统开机启动时读取一次,目的是用于对系统时钟进行初始化,这里说的初始化其实指的就是对内核的 jiffies 变量进行初始化操作
2.3 获取时间 time/gettimeofday
2.3.1 time 函数
- 系统调用 time() 用于获取当前时间,以秒为单位,返回自 1970-01-01 00:00:00 +0000 (UTC) 以来的秒数值
- time 函数获取得到的是一个时间段,称之为日历时间或 time_t 时间
#include <time.h> // tloc :如果 tloc 参数不是 NULL,则返回值也存储在 tloc 指向的内存中 // 返回值:成功则返回自 1970-01-01 00:00:00 +0000 (UTC)以来的时间值(以秒为单位);失败则返回-1,并会设置 errno time_t time(time_t *tloc);
2.3.2 gettimeofday 函数
- time() 获取到的时间只能精确到秒,如果想要获取更加精确的时间可以使用系统调用 gettimeofday 来现,gettimeofday() 函数提供微秒级时间精度
- time() 返回得到的值和函数 gettimeofday() 所返回的 tv 参数中 tv_sec 字段的数值相同
#include <sys/time.h> // tv:一个 struct timeval 结构体指针变量,包含了两个成员变量 tv_sec 和 tv_usec,分别表示秒和微秒 // tz:早期实现用其来获取系统的时区信息,目前已遭废弃,在调用 gettimeofday() 函数时应将参数 tz 设置为 NULL // 返回值:成功返回 0;失败将返回-1,并设置 errno int gettimeofday(struct timeval *tv, struct timezone *tz);
2.4 时间转换函数
- 将 time() 或 gettimeofday() 函数获取到的秒数转换为利于查看和理解的形式
2.4.1 ctime 函数
- ctime() 是一个 C 库函数,可以将日历时间转换为可打印输出的字符串形式
- ctime()是一个不可重入函数,存在一些安全上面的隐患,ctime_r() 是 ctime() 的可重入版本,推荐使用可重入函数 ctime_r(),可重入函数 ctime_r() 多了一个参数 buf,也就是缓冲区首地址,所以 ctime_r() 函数需要调用者提供用于存放字符串的缓冲区
- ctime(或 ctime_r)转换得到的时间是计算机所在地对应的本地时间,并不是 UTC 时间
#include <time.h> char *ctime(const time_t *timep); char *ctime_r(const time_t *timep, char *buf);
2.4.2 localtime 函数
- localtime() 函数可以把 time() 或 gettimeofday() 得到的秒数(time_t 时间或日历时间)变成一个 struct tm 结构体所表示的时间,该时间对应的是本地时间
- 一般不推荐使用不可重入版本,使用可重入版本 localtime_r() 调用者需要自己定义 struct tm 结构体变量、并将该变量指针赋值给参数 result,在函数内部会对该结构体变量进行赋值操作
#include <time.h> // timep :需要进行转换的 time_t 时间变量对应的指针,可通过 time() 或 gettimeofday() 获取得到 // result :是一个 struct tm 结构体类型指针,参数 result 是可重入函数 localtime_r() 需要额外提供的参数 struct tm *localtime(const time_t *timep); struct tm *localtime_r(const time_t *timep, struct tm *result);
- struct tm 结构体如下
// 使用 localtime/localtime_r() 可将 time_t 时间总秒数分解成了各个独立的时间信息,易于查看和理解 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; /* 夏令时 */ };
2.4.3 gmtime 函数
-
gmtime() 函数也可以把 time_t 时间变成一个 struct tm 结构体所表示的时间,与 localtime() 所不同的是,gmtime() 函数所得到的是 UTC 国际标准时间,并不是计算机的本地时间
#include <time.h> struct tm *gmtime(const time_t *timep); struct tm *gmtime_r(const time_t *timep, struct tm *result);
-
测试案例
#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, 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); }
$ gcc test.c -o test $ ./test 本地时间: 2023 年11 月17 日 16:40:6 UTC 时间: 2023 年11 月17 日 8:40:6
2.4.4 其他时间函数
#include <time.h>
// 将使用 struct tm 结构体表示的分解时间转换为 time_t 时间(日历时间),是一个 C 库函数
time_t mktime(struct tm *tm);
// asctime()函数与 ctime()函数的作用一样
// ctime() 是将 time_t 时间转换为固定格式字符串,但 asctime() 是将 struct tm 表示的分解时间转换为固定格式的字符串
char *asctime(const struct tm *tm);
char *asctime_r(const struct tm *tm, char *buf);
// C 库函数 strftime() 也可将一个 struct tm 变量表示的分解时间转换为为格式化字符串
// 并且在功能上比 asctime() 和 ctime() 更强大,可自定义时间的显示格式
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
2.5 设置时间 settimeofday
- 使用 settimeofday() 函数可以设置时间,也就是设置系统的本地时间
- 使用 settimeofday 设置系统时间时内核会进行权限检查,只有超级用户(root)才可以设置系统时间,普通用户将无操作权限
#include <sys/time.h> int settimeofday(const struct timeval *tv, const struct timezone *tz);
2.6 总结
3. 进程时间
- 进程时间指的是进程从创建后(也就是程序运行后)到目前为止这段时间内使用 CPU 资源的时间总数,出于记录的目的,内核把 CPU 时间(进程时间)分为以下两个部分
- 用户 CPU 时间:进程在用户空间(用户态)下运行所花费的 CPU 时间
- 有时也称为虚拟时间(virtual time)
- 系统 CPU 时间:进程在内核空间(内核态)下运行所花费的 CPU 时间
- 这是内核执行系统调用或代表进程执行的其它任务(如,服务页错误)所花费的时间
- 用户 CPU 时间:进程在用户空间(用户态)下运行所花费的 CPU 时间
一般来说,进程时间指的是用户 CPU 时间和系统 CPU 时间的总和,也就是总的 CPU 时间。进程时间不等于程序的整个生命周期所消耗的时间,如果进程一直处于休眠状态(进程被挂起、不会得到系统调度),那么它并不会使用 CPU 资源,所以休眠的这段时间并不计算在进程时间中
3.1 times 函数
- times() 函数用于获取当前进程时间
#include <sys/times.h> clock_t times(struct tms *buf);
- 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); }
$ gcc times.c -o times $ ./times # 进程时间 = 用户 CPU 时间 + 系统 CPU 时间 = 0.73 秒 # 时间总和比进程时间多 1 秒,其实这一秒就是进程处于休眠状态的时间 时间总和: 1.730000 秒 用户 CPU 时间: 0.730000 秒 系统 CPU 时间: 0.000000 秒
3.2 clock 函数
-
库函数 clock() 提供了一个更为简单的方式用于进程时间,它的返回值描述了进程使用的总的 CPU 时间(也就是进程时间,包括用户 CPU 时间和系统 CPU 时间)
- clock() 函数可以很方便的获取总的进程时间,但不能获取单独的用户 CPU 时间和系统 CPU 时间
#include <time.h> // 返回值:返回值是到目前为止程序的进程时间,为 clock_t 类型 // clock() 的返回值并不是系统节拍数,如果想要获得秒数,请除以宏 CLOCKS_PER_SEC clock_t clock(void);
-
示例
#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); }
$ gcc clock.c -o clock $ ./clock 总的 CPU 时间: 0.849921
4. 产生随机数
4.1 随机数与伪随机数
- 随机数是随机出现、没有任何规律的一组数列。实际编程中,没有办法获得真正意义上的随机数列,这是一种理想的情况,在程序当中想要使用随机数列,只能通过算法得到一个伪随机数序列
- C 函数库中提供了很多函数用于产生伪随机数,其中最常用的是通过 rand() 和 srand() 产生随机数
4.2 rand 函数
- rand() 函数用于获取随机数,多次调用 rand() 可得到一组随机数序列
- 存在问题:每一次运行程序所得到的随机数序列都是相同的,如何使得每一次启动应用程序所得到的随机数序列是不一样的?通过 srand() 设置随机数种子
- 如果没有调用 srand() 设置随机数种子的情况下,rand() 会将 1 作为随机数种子,如果随机数种子相同,那么每一次启动应用程序所得到的随机数序列就是一样的,所以每次启动应用程序需要设置不同的随机数种子
#include <stdlib.h> // 返回值:返回一个介于 0 到 RAND_MAX(包含)之间的值,也就是数学上的 [0, RAND_MAX] int rand(void);
4.3 srand 函数
- 使用 srand() 函数为 rand() 设置随机数种子
#include <stdlib.h> // seed :指定一个随机数中,int 类型的数据,通常将当前时间作为随机数种子赋值给参数 seed,如 time(NULL) // 常用的用法:srand(time(NULL)); void srand(unsigned int seed);
- 测试
#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); }
$ gcc rand.c -o rand # 程序中将 rand()的随机数种子设置为 srand(time(NULL)),直接等于 time_t 时间值 # 意味着每次启动种子都不一样,所以能够产生不同的随机数数组 $ ./rand [17, 44, 24, 15, 58, 27, 20, 56] $ ./rand [14, 38, 50, 8, 12, 21, 9, 83]
5. 休眠
- 有时需要将进程暂停或休眠一段时间,进入休眠状态之后,程序将暂停运行,直到休眠结束。常用的系统调用和 C 库函数有 sleep()、usleep() 以及 nanosleep(),这些函数在应用程序当中通常作为延时使用,如:延时 1 秒钟
5.1 秒级休眠: sleep
- sleep() 是一个秒级别休眠 C 库函数,程序在休眠过程中,是可以被其它信号所打断的
#include <unistd.h> // seconds :休眠时长,以秒为单位 // 返回值:如果休眠时长为参数 seconds 所指定的秒数,则返回 0;若被信号中断则返回剩余的秒数 unsigned int sleep(unsigned int seconds);
5.2 微秒级休眠: usleep
- usleep() 同样也是一个 C 库函数,与 sleep() 的区别在于休眠时长精度不同,usleep() 支持微秒级程序休眠
#include <unistd.h> // usec :休眠时长,以微秒为单位 // 返回值:成功返回 0;失败返回-1,并设置 errno int usleep(useconds_t usec);
5.3 高精度休眠: nanosleep
- nanosleep() 具有更高精度来设置休眠时间长度,支持纳秒级时长设置,与 sleep()、usleep() 不同的是,nanosleep() 是一个 Linux 系统调用
#include <time.h> // req :一个 struct timespec 结构体指针,指向一个 struct timespec 变量,用于设置休眠时间长度,可精确到纳秒级别 // rem :也是一个 struct timespec 结构体指针,指向一个 struct timespec 变量,也可设置 NULL /* 返回值 在成功休眠达到请求的时间间隔后,nanosleep() 返回 0 如果中途被信号中断或遇到错误,则返回-1,并将剩余时间记录在参数 rem 指向的 struct timespec 结构体变量中(参数 rem 不为 NULL 的情况下,如果为 NULL 表示不接收剩余时间) */ int nanosleep(const struct timespec *req, struct timespec *rem);
休眠状态下,该进程会失去 CPU 使用权,退出系统调度队列,直到休眠结束
6. 申请堆内存
- 在操作系统下,内存资源是由操作系统进行管理、分配的,当应用程序想要内存时(这里指的是堆内存),可以向操作系统申请内存,然后使用内存;当不再需要时,将申请的内存释放、归还给操作系统
6.1 在堆上分配内存:malloc 和 free
- Linux C 程序当中一般使用 malloc() 函数为程序分配一段堆内存,而使用 free() 函数来释放这段内存
- malloc()在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的,所以通常需要程序员对 malloc() 分配的堆内存进行初始化操作
- 在堆上分配的内存,需要开发者自己手动释放掉,通常使用 free() 函数释放堆内存
- void* 是指:返回的指针类型未知,所以在调用 malloc() 时通常需要进行强制类型转换,将 void* 指针类型转换成希望的类型;如果分配内存失败(如系统堆内存不足)或参数 size 为 0,将返回 NULL
#include <stdlib.h> // size:需要分配的内存大小,以字节为单位 // 返回值:返回值为 void* 类型,如果申请分配内存成功,将返回一个指向该段内存的指针 void *malloc(size_t size); // ptr:指向需要被释放的堆内存对应的指针 void free(void *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()?
- Linux 系统中,当一个进程终止时,内核会自动关闭它没有关闭的所有文件(该进程打开的文件,但是在进程终止时未调用 close() 关闭它)。同样,对于内存来说,也是如此。当进程终止时,内核会将其占用的所有内存都返还给操作系统,这包括在堆内存中由 malloc() 函数所分配的内存空间。基于内存的这一自动释放机制,很多应用程序通常会省略对 free() 函数的调用
虽然依靠终止进程来自动释放内存对大多数程序来说是可以接受的,但最好在程序中显式调用 free() 释放内存
- 其一,显式调用 free() 能使程序具有更好的可读性和可维护性
- 其二,对于很多程序来说,申请的内存并不是在程序的生命周期中一直需要,大多数情况下,都是根据代码需求动态申请、释放的,如果申请的内存对程序来说已经不再需要了,那么就已经把它释放、归还给操作系统,如果持续占用,将会导致内存泄漏
6.2 分配对齐内存
- 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);
7. proc 文件系统的使用
-
proc 文件系统是一个虚拟文件系统,它以文件系统的方式为应用层访问系统内核数据提供了接口
- 用户和应用程序可以通过 proc 文件系统得到系统信息和进程相关信息,对 proc 文件系统的读写作为与内核进行通信的一种手段
- 但与普通文件不同的是,proc 文件系统是动态创建的,文件本身并不存在于磁盘当中,只存在于内存当中,与 devfs 一样,都被称为虚拟文件系统
-
proc 文件系统挂载在系统的 /proc 目录下,对于内核开发者来说,proc 文件系统给了开发者一种调试内核的方法:通过查看 /proc/xxx 文件来获取到内核特定数据结构的值,在添加了新功能前后进行对比,就可以判断此功能所产生的影响是否合理
-
/proc 目录下中包含了一些目录和虚拟文件
- 下面的数字对应的其实就是一个一个的进程 PID 号,每一个进程在内核中都会存在一个编号
- 虚拟文件如 buddyinfo、cgroups、cmdline、version 等,不同的文件记录了不同信息
$ cd /proc $ ls 1 1083 121 1432 17 225 2307 2380 2424 2564 29 3175 354 374 404 47 647 845 cmdline iomem mdstat self version_signature 10 109 122 1438 18 226 2317 2381 2425 2566 294 3185 355 377 409 48 648 9 consoles ioports meminfo slabinfo vmallocinfo 1004 1099 123 1440 2 2264 232 2384 2433 2587 299 32 356 38 41 49 658 907 cpuinfo irq misc softirqs vmstat 1007 11 124 1449 20 2269 2322 2385 2435 2597 3 323 36 381 410 50 668 944 crypto kallsyms modules stat zoneinfo 101 1103 126 1450 200 228 2328 2389 244 26 30 324 3624 3842 42 51 670 957 devices kcore mounts swaps 102 1105 128 147 201 2280 2334 2397 2440 264 3017 326 365 3848 420 52 673 968 diskstats keys mtrr sys 103 115 13 1487 203 2281 2338 2398 2471 27 3018 327 3656 3851 423 53 675 972 dma key-users net sysrq-trigger 104 1152 137 15 205 229 2349 24 2473 270 306 329 3658 39 428 54 700 996 driver kmsg pagetypeinfo sysvipc 105 116 14 155 2070 2299 2353 2403 2492 2735 311 33 366 391 44 58 8 acpi execdomains kpagecgroup partitions thread-self 106 1167 140 16 2085 23 2358 2407 2497 2739 3118 330 3664 4 440 6 808 asound fb kpagecount pressure timer_list 107 118 1414 1663 21 230 2367 2420 2534 2741 313 34 3669 40 45 631 811 buddyinfo filesystems kpageflags sched_debug tty 108 12 1415 1677 22 2303 2372 2421 2539 2757 314 35 3679 400 46 635 815 bus fs loadavg schedstat uptime 1082 120 1431 1693 2220 2305 2376 2422 2561 28 3171 3508 3689 402 467 641 843 cgroups interrupts locks scsi version
- proc 文件系统的使用就是去读取 /proc 目录下的这些文件,获取文件中记录的信息
- 可以直接使用 cat 命令读取
$ cat /proc/version Linux version 5.4.0-152-generic (buildd@lcy02-amd64-051) (gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)) #169~18.04.1-Ubuntu SMP Wed Jun 7 22:22:24 UTC 2023
- 也可以在应用程序中调用 open() 打开、然后再使用 read() 函数读取
#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); }
$ gcc proc.c -o proc $ ./proc Linux version 5.4.0-152-generic (buildd@lcy02-amd64-051) (gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)) #169~18.04.1-Ubuntu SMP Wed Jun 7 22:22:24 UTC 2023