参考引用
系统调用 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
#include <sys/sysinfo.h>
// info:struct sysinfo 结构体类型指针,指向一个 struct sysinfo 结构体类型对象
// 返回值:成功返回 0;失败将返回-1,并设置 errno
int sysinfo(struct sysinfo *info);
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
#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
sysconf() 函数是一个库函数,可在运行时获取系统的一些配置信息,如:页大小(page size)、主机名的最大长度、进程可以打开的最大文件数、每个用户 ID 的最大并发进程数等
#include <unistd.h>
long sysconf(int name);
参数 name 指定了要获取哪个配置信息,参数 name 可取以下任何一个值(加粗用的最多)
示例
#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
GMT 时间
UTC 时间
$ date -u
2023年 12月 16日 星期六 07:27:19 UTC
时区
$ date
2023年 12月 16日 星期六 15:30:51 CST # CST 指的是 China Standard Time(中国标准时间)
点时间和段时间
实时时钟 RTC
Linux 系统如何记录时间
jiffies 的引入
#include <time.h>
// tloc :如果 tloc 参数不是 NULL,则返回值也存储在 tloc 指向的内存中
// 返回值:成功则返回自 1970-01-01 00:00:00 +0000 (UTC)以来的时间值(以秒为单位);失败则返回-1,并会设置 errno
time_t time(time_t *tloc);
#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);
#include <time.h>
char *ctime(const time_t *timep);
char *ctime_r(const time_t *timep, char *buf);
#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);
// 使用 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; /* 夏令时 */
};
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
#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);
#include <sys/time.h>
int settimeofday(const struct timeval *tv, const struct timezone *tz);
一般来说,进程时间指的是用户 CPU 时间和系统 CPU 时间的总和,也就是总的 CPU 时间。进程时间不等于程序的整个生命周期所消耗的时间,如果进程一直处于休眠状态(进程被挂起、不会得到系统调度),那么它并不会使用 CPU 资源,所以休眠的这段时间并不计算在进程时间中
#include <sys/times.h>
clock_t times(struct tms *buf);
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 时间总和 */
};
#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 秒
库函数 clock() 提供了一个更为简单的方式用于进程时间,它的返回值描述了进程使用的总的 CPU 时间(也就是进程时间,包括用户 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
#include <stdlib.h>
// 返回值:返回一个介于 0 到 RAND_MAX(包含)之间的值,也就是数学上的 [0, RAND_MAX]
int rand(void);
#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]
#include <unistd.h>
// seconds :休眠时长,以秒为单位
// 返回值:如果休眠时长为参数 seconds 所指定的秒数,则返回 0;若被信号中断则返回剩余的秒数
unsigned int sleep(unsigned int seconds);
#include <unistd.h>
// usec :休眠时长,以微秒为单位
// 返回值:成功返回 0;失败返回-1,并设置 errno
int usleep(useconds_t usec);
#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 使用权,退出系统调度队列,直到休眠结束
#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() 能使程序具有更好的可读性和可维护性
- 其二,对于很多程序来说,申请的内存并不是在程序的生命周期中一直需要,大多数情况下,都是根据代码需求动态申请、释放的,如果申请的内存对程序来说已经不再需要了,那么就已经把它释放、归还给操作系统,如果持续占用,将会导致内存泄漏
#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);
proc 文件系统是一个虚拟文件系统,它以文件系统的方式为应用层访问系统内核数据提供了接口
proc 文件系统挂载在系统的 /proc 目录下,对于内核开发者来说,proc 文件系统给了开发者一种调试内核的方法:通过查看 /proc/xxx 文件来获取到内核特定数据结构的值,在添加了新功能前后进行对比,就可以判断此功能所产生的影响是否合理
/proc 目录下中包含了一些目录和虚拟文件
$ 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
$ 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
#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