Linux_應用篇(07) 系統信息與系統資源

在應用程序當中,有時往往需要去獲取到一些系統相關的信息,譬如時間、日期、以及其它一些系統相關信息,本章將向大家介紹如何通過 Linux 系統調用或 C 庫函數獲取系統信息, 譬如獲取系統時間、日期以及設置系統時間、日期等;除此之外,還會向大家介紹 Linux 系統下的/proc 虛擬文件系統,包括/proc 文件系統是什么以及如何從/proc 文件系統中讀取系統、進程有關信息。除了介紹系統信息內容外, 本章還會向大家介紹有關系統資源的使用,譬如系統內存資源的申請與使用等。
? 用于獲取系統相關信息的函數;
? 時間、日期;
? 進程時間;
? 使程序進入休眠;
? 在堆中申請內存;
? proc 文件系統介紹;
? 定時器。

系統信息

系統標識 uname

系統調用 uname()用于獲取有關當前操作系統內核的名稱和信息,函數原型如下所示(可通過"man 2uname"命令查看):

#include <sys/utsname.h>
int uname(struct utsname *buf);

使用該函數需要包含頭文件<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_SOURCEchar 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);
}

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);
}

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。

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 類型的數據。

時間、日期

在正式介紹這些時間、日期相關的系統調用或 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 時間更加精準,所以在我們的編程當中不用刻意去區分它們之間的區別。在 Ubuntu 系統下, 可以使用"date -u"命令查看到當前的 UTC 時間。

時區

全球被劃分為 24 個時區,每一個時區橫跨經度 15 度,以英國格林威治的本初子午線作為零度經線,將全球劃分為東西兩半球, 分為東一區、東二區、東三區……東十二區以及西一區、西二區、西三區……西十二區,而本初子午線所在時區被稱為中時區(或者叫零時區),劃分圖如下所示:

東十二區和西十二區其實是一個時區,就是十二區,東十二區與西十二區各橫跨經度 7.5 度,以 180 度經線作為分界線。 每個時區的中央經線上的時間就是這個時區內統一采用的時間,稱為區時。相鄰兩個時區的時間相差 1 小時。例如,我國東 8 區的時間總比泰國東 7 區的時間早 1 小時,而比日本東 9 區的時間晚 1小時。因此,出國旅行的人,必須隨時調整自己的手表,才能和當地時間相一致。凡向西走,每過一個時區,就要把表向前撥 1 小時(比如 2 點撥到 1 點);凡向東走,每過一個時區,就要把表向后撥 1 小時(比如 1 點撥到 2 點)。實際上,世界上不少國家和地區都不嚴格按時區來計算時間。為了在全國范圍內采用統一的時間,一般都把某一個時區的時間作為全國統一采用的時間。例如,我國把首都北京所在的東 8 區的時間作為全國統一的時間,稱為北京時間, 北京時間就作為我國使用的本地時間, 譬如我們電腦上顯示的時間就是北京時間, 我國國土面積廣大,由東到西橫跨了 5 個時區,也就意味著我國最東邊的地區與最西邊的地區實際上相差了 4、 5 個小時。 又例如,英國、法國、荷蘭和比利時等國,雖地處中時區,但為了和歐洲大多數國家時間相一致,則采用東 1 區的時間。
譬如在 Ubuntu 系統下,可以使用 date 命令查看系統當前的本地時間。

可以看到顯示出來的字符串后面有一個"CST"字樣, CST 在這里其實指的是 China Standard Time(中國標準時間)的縮寫,表示當前查看到的時間是中國標準時間,也就是我國所使用的標準時間--北京時間,一般在安裝 Ubuntu 系統的時候會提示用戶設置所在城市,那么系統便會根據你所設置的城市來確定系統的本地時間對應的時區,譬如設置的城市為上海,那么系統的本地時間就是北京時間,因為我國統一使用北京時間作為本國的標準時間。
在 Ubuntu 系統下, 時區信息通常以標準格式保存在一些文件當中, 這些文件通常位于/usr/share/zoneinfo目錄下,該目錄下的每一個文件(包括子目錄下的文件)都包含了一個特定國家或地區內時區制度的相關信息, 且往往根據其所描述的城市或地區縮寫來加以命名,譬如 EST(美國東部標準時間)、 CET(歐洲中部時間)、 UTC(世界標準時間)、 Hongkong、 Iran、 Japan(日本標準時間)等,也把這些文件稱為時區配置文件,如下圖所示:

系統的本地時間由時區配置文件/etc/localtime 定義,通常鏈接到/usr/share/zoneinfo 目錄下的某一個文件(或其子目錄下的某一個文件) :

如果我們要修改 Ubuntu 系統本地時間的時區信息,可以直接將/etc/localtime 鏈接到/usr/share/zoneinfo目錄下的任意一個時區配置文件,譬如 EST(美國東部標準時間),首先進入到/etc 目錄下,執行下面的命令:

sudo rm -rf localtime #刪除原有鏈接文件
sudo ln -s /usr/share/zoneinfo/EST localtime #重新建立鏈接文件

接下來再使用 date 命令查看下系統當前的時間,可以發現后面的標識變成了 EST,也就意味著當前系統的本地時間變成了 EST 時間(美國東部標準時間)。

Linux 系統中的時間

點時間和段時間

通常描述時間有兩種方式:點時間和段時間;點時間顧名思義指的是某一個時間點,譬如當前時間是2024?年 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 變量去計算,當然并不需要我們手動去計算, Linux 系統提供了相應的系統調用或 C庫函數用于獲取當前時間,譬如系統調用 time()、 gettimeofday(),其實質上就是通過 jiffies 變量換算得到。

獲取時間 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 時間。

(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 結構體在前面章節內容中已經給大家介紹過
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 字段的數值相同。

時間轉換函數

通過 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()函數需要調用者提供用于存放字符串的緩沖區。

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()得到的秒數變成一個 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 時間總秒數分解成了各個獨立的時間信息, 易于我們查看和理解。localtime 函數返回的 struct tm 結構中的 tm_year 成員表示的是自 1900 年以來的年份數,因此在使用時需要加上 1900 才能得到實際的年份。這是 C 標準庫的設計規范,了解并正確使用這一點對于正確處理時間和日期非常重要。

(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()是一樣的。

(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。

(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。

(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: 字符串的最大字節數。
tm: 指向 struct tm 結構體對象的指針。
返回值: 如果轉換得到的目標字符串不超過最大字節數(也就是 max),則返回放置到 s 數組中的字節數;如果超過了最大字節數,則返回 0。
format: 這是一個用字符串表示的字段, 包含了普通字符和特殊格式說明符,可以是這兩種字符的任意組合。 特殊格式說明符將會被替換為 struct tm 結構體對象所指時間的相應值,這些特殊格式說明符如下:

說明符表示含義實例
%a星期的縮寫Sun
%A星期的完整名稱Sunday
%b月份的縮寫Mar
%B月份的完整名稱March
%c系統當前語言環境對應的首選日期和時間表示形式
%C世紀(年/100)20
%d十進制數表示一個月中的第幾天(01-31)15、 05
%D相當于%m/%d/%y01/14/21
%e與%d 相同,但是單個數字時,前導 0 會被去掉15、 5
%F相當于%Y-%m-%d2021-01-14
%h相當于%bJan
%H十進制數表示的 24 小時制的小時(范圍 00-23)01、 22
%I十進制數表示的 12 小時制的小時(范圍 01-12)01、 11
%j十進制數表示的一年中的某天(范圍 001-366)050、 285
%k與%H 相同,但是單個數字時,前導 0 會被去掉(范圍 0-23)1、 22
%l與%I 相同,但是單個數字時,前導 0 會被去掉(范圍 1-12)1、 11
%m十進制數表示的月份(范圍 01-12)01、 10
%M十進制數表示的分鐘(范圍 00-59)01、 55
%n換行符
%p根據給定的時間值,添加“AM”或“PM”PM
%P與%p 相同,但會使用小寫字母表示pm
%r相當于%I:%M:%S %p12:15:31 PM
%R相當于%H:%M12:16
%S十進制數表示的秒數(范圍 00-60)05、 30
%T相當于%H:%M:%S12:20:03
%u十進制數表示的星期(范圍 1-7,星期一為 1)1、 5
%U十進制數表示,當前年份的第幾個星期(范圍 00-53),從第
一個星期日作為 01 周的第一天開始
%W十進制數表示,當前年份的第幾個星期(范圍 00-53), 從第
一個星期一作為第 01 周的第一天開始
%w十進制數表示的星期,范圍為 0-6,星期日為 0
%x系統當前語言環境的首選日期表示形式,沒有時間01/14/21
%X系統當前語言環境的首選時間表示形式,沒有日期12:30:16
%y十進制數表示的年份(后兩字數字)21
%Y十進制數表示的年份(4 個數字)2021
%%輸出%符號%

strftime 函數的特殊格式說明符還是比較多的,不用去記它,需要用的時候再去查即可!
通過上表可知,譬如我要想輸出"2021-01-14 16:30:25<PM> January Thursday"這樣一種形式表示的時間日期,那么就可以這樣來設置 format 參數:

"%Y-%m-%d %H:%M:%S<%p> %B %A"

設置時間 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)才可以設置系統時間,普通用戶將無操作權限。

總結

進程時間

進程時間指的是進程從創建后(也就是程序運行后)到目前為止這段時間內使用 CPU 資源的時間總數,出于記錄的目的,內核把 CPU 時間(進程時間) 分為以下兩個部分:
? 用戶 CPU 時間:進程在用戶空間(用戶態)下運行所花費的 CPU 時間。有時也成為虛擬時間(virtualtime)。
? 系統 CPU 時間:進程在內核空間(內核態)下運行所花費的 CPU 時間。這是內核執行系統調用或代表進程執行的其它任務(譬如,服務頁錯誤)所花費的時間。
一般來說,進程時間指的是用戶 CPU 時間和系統 CPU 時間的總和,也就是總的 CPU 時間。
Tips:進程時間不等于程序的整個生命周期所消耗的時間, 如果進程一直處于休眠狀態(進程被掛起、不會得到系統調度),那么它并不會使用 CPU 資源,所以休眠的這段時間并不計算在進程時間中。

times 函數

times()函數用于獲取當前進程時間,其函數原型如下所示:

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

使用該函數需要包含頭文件<sys/times.h>。
函數參數和返回值含義如下:
buf: times()會將當前進程時間信息存在一個 struct tms 結構體數據中,所以我們需要提供 struct tms 變量,使用參數 buf 指向該變量。
返回值: 返回值類型為 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);
}

可以看到用戶 CPU 時間為 1.9 秒,系統 CPU 時間為 0 秒,也就是說測試的這段代碼并沒有進入內核態運行,所以總的進程時間 = 用戶 CPU 時間 + 系統 CPU 時間 = 1.9 秒。
圖中顯示的時間總和并不是總的進程時間, 前面也給大家解釋過, 這個時間總和指的是從起點到終點所經過的時間,并不是進程時間,這里大家要理解。時間總和包括了進程處于休眠狀態時消耗的時間(sleep 等會讓進程掛起、 進入休眠狀態) ,可以發現時間總和比進程時間多 1 秒,其實這一秒就是進程處于休眠狀態的時間。

clock 函數

庫函數 clock()提供了一個更為簡單的方式用于進程時間,它的返回值描述了進程使用的總的 CPU 時間(也就是進程時間,包括用戶 CPU 時間和系統 CPU 時間),其函數原型如下所示:

#include <time.h>
clock_t clock(void);

使用該函數需要包含頭文件<time.h>。
函數參數和返回值含義如下:
無參數。
返回值: 返回值是到目前為止程序的進程時間,為 clock_t 類型,注意 clock()的返回值并不是系統節拍數,如果想要獲得秒數,請除以 CLOCKS_PER_SEC(這是一個宏)。 如果返回的進程時間不可用或其值無法表示,則該返回值是-1。
clock()函數雖然可以很方便的獲取總的進程時間,但并不能獲取到單獨的用戶 CPU 時間和系統 CPU 時間,在實際編程當中,根據自己的需要選擇。

#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);
}

產生隨機數

在應用編程當中可能會用到隨機數,譬如老板讓你編寫一個抽獎的小程序,編號 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));

#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 庫函數等。

休眠

有時需要將進程暫停或休眠一段時間, 進入休眠狀態之后,程序將暫停運行,直到休眠結束。 常用的系統調用和 C 庫函數有 sleep()、 usleep()以及 nanosleep(), 這些函數在應用程序當中通常作為延時使用。

秒級休眠: sleep

sleep()是一個 C 庫函數,從函數名字面意思便可以知道該函數的作用了,簡單地說, sleep()就是讓程序“休息”一會,然后再繼續工作。其函數原型如下所示:

#include <unistd.h>
unsigned int sleep(unsigned int seconds);

使用該函數需要包含頭文件<unistd.h>。
函數參數和返回值含義如下:
seconds: 休眠時長,以秒為單位。
返回值: 如果休眠時長為參數 seconds 所指定的秒數,則返回 0;若被信號中斷則返回剩余的秒數。
sleep()是一個秒級別休眠函數,程序在休眠過程中,是可以被其它信號所打斷的,關于信號這些內容,將會在后面章節向大家介紹。

微秒級休眠: usleep

usleep()同樣也是一個 C 庫函數,與 sleep()的區別在于休眠時長精度不同, usleep()支持微秒級程序休眠,其函數原型如下所示:

#include <unistd.h>
int usleep(useconds_t usec);

函數參數和返回值含義如下:
usec: 休眠時長,以微秒為單位。
返回值: 成功返回 0;失敗返回-1,并設置 errno。

高精度休眠: 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 標識錯誤類型。在前面小節中介紹了 struct timespec 結構體,該結構體包含了兩個成員變量,秒(tv_sec)和納秒(tv_nsec)。

前面說到,在應用程序當中,通常使用這些函數作為延時功能,譬如在程序當中需要延時一秒鐘、延時5 毫秒等應用場景時,那么就可以使用這些函數來實現;但是大家需要注意,休眠狀態下,該進程會失去 CPU使用權, 退出系統調度隊列,直到休眠結束。在一個裸機程序當中,通常使用 for 循環(或雙重 for 循環)語句來實現延時等待,譬如在 for 循環當中執行 nop 空指令,也就意味著即使在延時等待情況下, CPU 也是一直都在工作;由此可知,應用程序當中使用休眠用作延時功能,并不是裸機程序中的 nop 空指令延時,一旦執行 sleep(),進程便主動交出 CPU 使用權, 暫時退出系統調度隊列,在休眠結束前,該進程的指令將得不到執行。

申請堆內存

在操作系統下,內存資源是由操作系統進行管理、分配的,當應用程序想要內存時(這里指的是堆內存),可以向操作系統申請內存,然后使用內存;當不再需要時,將申請的內存釋放、歸還給操作系統; 在許多的應用程序當中,往往都會有這種需求,譬如為一些數據結構動態分配/釋放內存空間, 本小節向大家介紹應用程序如何向操作系統申請堆內存。

在堆上分配內存: 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()能使程序具有更好的可讀性和可維護性;其二,對于很多程序來說,申請的內存并不是在程序的生命周期中一直需要,大多數情況下,都是根據代碼需求動態申請、釋放的,如果申請的內存對程序來說已經不再需要了,那么就已經把它釋放、歸還給操作系統,如果持續占用,將會導致內存泄漏,也就是人們常說的“你的程序在吃內存”!

在堆上分配內存的其它方法

除了 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);

分配對齊內存

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 值。

aligned_alloc()函數
aligned_alloc()函數用于分配 size 個字節大小的內存空間, 返回指向該空間的指針。
函數參數和返回值含義如下:
alignment: 用于設置對齊字節大小, alignment 必須是 2 的冪次方(譬如 2^4、 2^5、 2^8 等)。
size: 設置分配的內存大小,以字節為單位。參數 size 必須是參數 alignment 的整數倍。
返回值: 成功將返回內存空間的指針,內存空間的起始地址是參數 alignment 的整數倍;失敗返回 NULL。

memalign()函數
memalign()與 aligned_alloc()參數是一樣的,它們之間的區別在于:對于參數 size 必須是參數 alignment的整數倍這個限制條件, memalign()并沒有這個限制條件。
Tips: memalign()函數已經過時了,并不提倡使用!

valloc()函數
valloc()分配 size 個字節大小的內存空間,返回指向該內存空間的指針, 內存空間的地址是頁大小(pagesize) 的倍數。
valloc()與 memalign()類似,只不過 valloc()函數內部實現中,使用了頁大小作為對齊的長度,在程序當中,可以通過系統調用 getpagesize()來獲取內存的頁大小。
Tips: valloc()函數已經過時了,并不提倡使用!

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:系統運行時間;

proc 文件系統的使用

proc 文件系統的使用就是去讀取/proc 目錄下的這些文件,獲取文件中記錄的信息,可以直接使用 cat 命令讀取,也可以在應用程序中調用 open()打開、然后再使用 read()函數讀取。在 Linux 系統下直接使用 cat 命令查看/proc 目錄下的虛擬文件,譬如"cat /proc/version"查看內核版本相關信息:
???????

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/14352.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/14352.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/14352.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

restTemplate返回報文亂碼問題

默認服務端使用UTF8編碼 排查1&#xff1a; 請求前手動設置UTF-8編碼解析報文 RestTemplate restTemplate new RestTemplate(); restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); ResponseEntity<String> excha…

三能一體運營體系助力政企支撐水平提升

生產力的發展是現代社會孜孜不倦的追求&#xff0c;由此產生了我們熟悉的“機械化、電子化、信息化”乃至現今正在發生的“智能化”四次工業革命。這些是由技術的突破性發展帶來的&#xff0c;但我們也注意到生產力發展的另一個助力&#xff0c;即生產效率的提升&#xff0c;19…

【MySQL數據庫】mysql日志管理、備份與恢復

mysql日志管理、備份與恢復 MySQL數據庫備份及日志一、數據庫備份分類&#xff1a;如何選擇邏輯備份策略 (頻率)完全備份與恢復備份恢復 增量備份與恢復實現增量備份 基于時間點與位置恢復 二.MySQL日志管理 MySQL數據庫備份及日志 在生產環境中&#xff0c;數據的安全性是至關…

在未來你將何去何從?

在數字化的浪潮中&#xff0c;信息技術行業無疑是推動全球經濟和社會發展的重要動力。隨著科技的不斷迭代與進步&#xff0c;云計算、大數據、人工智能&#xff08;AI&#xff09;、物聯網&#xff08;IoT&#xff09;、5G通信和區塊鏈等技術已經深入到我們生活的每一個角落&am…

鴻蒙原生應用元服務開發-鴻蒙真機運行項目實戰與注意事項

一、解壓項目注意項目包不能為中文 二、用數據線將裝好DevEco Studio的電腦與設置為開發者模式的鴻蒙手機相連接。 三、將項目包托進DevEco Studio 中 注意項目包文件不能有嵌套 四、查看設備運行 五、點擊項目結構 六、勾選紅色框圈部分 登錄開發者賬號 七、選擇好公司 八、等…

我是如何使用 Next.js14 + Tailwindcss 重構個人項目的

前言 去年在學習 React 和 Nest 的時候&#xff0c;參考了大佬 imsyy 的項目 DailyHot&#xff0c;以此項目的靈感基于 React 開發&#xff0c;完成之后就沒怎么在意。 后來發現這個項目還有點小流量&#xff0c;每天差不多 200-400 的 IP 訪問量&#xff1a; 我又抽時間優…

深度學習之基于Pytorch框架手寫數字識別

歡迎大家點贊、收藏、關注、評論啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代碼。 文章目錄 一項目簡介 二、功能三、系統四. 總結 一項目簡介 一、項目背景與意義 手寫數字識別是數字圖像處理領域的一個經典問題&#xff0c;也是深度學習技術的一個常用應用場…

AWS計算之Amazon Lightsail

Amazon Lightsail是亞馬遜提供的一種簡化的虛擬私有服務器(VPS)服務&#xff0c;旨在幫助開發人員快速、輕松地搭建和管理虛擬服務器。Lightsail提供了預配置地計算資源、網絡、存儲和數據傳輸選項&#xff0c;用戶可以通過簡單的界面選擇所需的配置&#xff0c;輕松部署應用程…

51匯編--數碼管時鐘

實現一個24小時制的電子鐘程序&#xff0c;在實驗箱的6個數碼管上顯示時分秒&#xff08;用定時器0中斷更新計時時間&#xff0c;時間值以壓縮BCD碼形式保存在內部RAM的30H31H和32H單元&#xff09;。 PC機可通過串行口發送要設置的時間給單片機&#xff08;發送的時間格式為壓…

java 重寫接口的default方法

在Java 8中&#xff0c;接口可以包含默認方法&#xff08;default methods&#xff09;&#xff0c;這些方法可以有默認實現。如果一個類實現了包含默認方法的接口&#xff0c;并且沒有提供這個方法的實現&#xff0c;則會使用接口中的默認實現。 如果需要重寫接口中的默認方法…

【MySQL精通之路】SQL優化(1)-查詢優化(11)-多范圍查詢優化

主博客&#xff1a; 【MySQL精通之路】SQL優化(1)-查詢優化-CSDN博客 上一篇&#xff1a; 【MySQL精通之路】SQL優化(1)-查詢優化(10)-外部聯接簡化-CSDN博客 下一篇&#xff1a; 當基表很大且未存儲在存儲引擎的緩存中時&#xff0c;使用輔助索引上的范圍掃描讀取行可能會…

uniappx 獲取設備唯一標識(OAID、AAID、AndroidID、IMEI等) Ba-IdCode-U

簡介&#xff08;下載地址&#xff09; Ba-IdCode-U 是一款可以獲取國內各大手機廠商 OAID&#xff08;開放匿名設備標識&#xff09;及海外手機平臺 AAID&#xff08;安卓廣告標識&#xff09;的uniapp插件。另外也支持獲取 IMEI/MEID、AndroidID、WidevineID、PseudoID、GUI…

Spring Cloud Alibaba-06-Sleuth鏈路追蹤

Lison <dreamlison163.com>, v1.0.0, 2024.4.03 Spring Cloud Alibaba-06-Sleuth鏈路追蹤 文章目錄 Spring Cloud Alibaba-06-Sleuth鏈路追蹤為什么使用鏈路追蹤常見鏈路追蹤解決方案Sleuth概述概述Sleuth術語 Sleuth Zipkin 原理Sleuth原理簡述Zipkin 原理簡述 Sleut…

Python庫之`lxml`的高級用法深度解析

Python庫之lxml的高級用法深度解析 簡介 lxml是一個功能強大的第三方庫&#xff0c;它提供了對XML和HTML文檔的高效處理能力。除了基本的解析和創建功能外&#xff0c;lxml還包含了一些高級用法&#xff0c;這些用法可以幫助開發者在處理復雜文檔時更加得心應手。 高級解析技…

代碼隨想錄——路徑總和(Leetcode113)需要回顧

題目鏈接 遞歸 本題遞歸需要遍歷整棵樹&#xff0c;所以遞歸沒有返回值 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* T…

蘋果M4性能分析:進步神速?還有多少空間?

2024年初&#xff0c;蘋果推出了M4處理器&#xff0c;令人意外的是&#xff0c;它的發布距離M3發布僅僅過去了半年時間。更讓人驚訝的是&#xff0c;M4首次亮相于iPad Pro。這一新處理器不僅僅是M3的簡單升級版本&#xff0c;而是一次全面的架構優化。本文將詳細分析M4處理器的…

Vue基礎(1)數據綁定

一. 文本插值 普通文本可以使用雙大括號 {{ }} &#xff0c;要想插入 HTML&#xff0c;需要使用 v-html 指令。 <template><h1>Message: {{ state.msg }}</h1><p>{{ state.count 1 }}</p><p>{{ state.rawHtml }}</p><p v-html…

【教學類-58-02】黑白三角拼圖02(3*3宮格)262144種

背景需求&#xff1a; 已知黑白三角拼圖2*2&#xff08;4個拼圖&#xff09;一共有256種排列方法 【教學類-58-01】黑白三角拼圖01&#xff08;2*2宮格&#xff09;256種-CSDN博客文章瀏覽閱讀142次&#xff0c;點贊5次&#xff0c;收藏12次。【教學類-58-01】黑白三角拼圖01…

深度學習之基于Matlab卷積神經網絡(CNN)手寫數字識別

歡迎大家點贊、收藏、關注、評論啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代碼。 文章目錄 一項目簡介 二、功能三、系統四. 總結 一項目簡介 一、項目背景與意義 手寫數字識別是計算機視覺領域的一個重要問題&#xff0c;也是深度學習應用的一個典型場景。卷…

什么是固態繼電器?

固態繼電器是不需要使用任何機械部件的開關繼電器。這通常使它們具有比普通機電繼電器壽命更長的優勢&#xff0c;然而&#xff0c;盡管固態繼電器速度快且耐用&#xff0c;但仍具有某些設計規定。 固態繼電器風靡全球&#xff0c;徹底改變了從農業自動化到航空航天等各個行業…