日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux系统信息与系统资源

發(fā)布時間:2023/12/10 linux 60 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux系统信息与系统资源 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

目錄

  • 系統(tǒng)信息
    • 系統(tǒng)標(biāo)識uname
    • sysinfo 函數(shù)
    • gethostname 函數(shù)
    • sysconf()函數(shù)
  • 時間、日期
    • GMT 時間
    • UTC 時間
    • UTC 時間格式
    • 時區(qū)
    • 實時時鐘RTC
    • 獲取時間time/gettimeofday
    • 時間轉(zhuǎn)換函數(shù)
    • 設(shè)置時間settimeofday
    • 總結(jié)
  • 進程時間
    • times 函數(shù)
    • clock 函數(shù)
  • 產(chǎn)生隨機數(shù)
  • 休眠(延時)
    • 秒級休眠: sleep
    • 微秒級休眠: usleep
    • 高精度休眠: nanosleep
  • 申請堆內(nèi)存
    • 在堆上分配內(nèi)存:malloc 和free
    • 調(diào)用free()還是不調(diào)用free()
    • 在堆上分配內(nèi)存的其它方法
    • 分配對齊內(nèi)存
  • proc 虛擬文件系統(tǒng)
    • proc 文件系統(tǒng)的使用

在應(yīng)用程序當(dāng)中,有時往往需要去獲取到一些系統(tǒng)相關(guān)的信息。

  • 時間、日期、以及其它一些系統(tǒng)相關(guān)信息,如何通過Linux 系統(tǒng)調(diào)用或C 庫函數(shù)獲取系統(tǒng)信息;
  • Linux 系統(tǒng)下的/proc 虛擬文件系統(tǒng),包括/proc 文件系統(tǒng)是什么以及如何從/proc 文件系統(tǒng)中讀取系統(tǒng)、進程有關(guān)信息;
  • 系統(tǒng)資源的使用,譬如系統(tǒng)內(nèi)存資源的申請與使用等。

系統(tǒng)信息

系統(tǒng)標(biāo)識uname

系統(tǒng)調(diào)用uname()用于獲取有關(guān)當(dāng)前操作系統(tǒng)內(nèi)核的名稱和信息,函數(shù)原型如下所示(可通過"man 2 uname"命令查看):

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

buf:struct utsname 結(jié)構(gòu)體類型指針,指向一個struct utsname 結(jié)構(gòu)體類型對象。
返回值:成功返回0;失敗將返回-1,并設(shè)置errno。

struct utsname 結(jié)構(gòu)體如下所示:

struct utsname {char sysname[]; /* 當(dāng)前操作系統(tǒng)的名稱*/char nodename[]; /* 網(wǎng)絡(luò)上的名稱(主機名)*/char release[]; /* 操作系統(tǒng)內(nèi)核版本*/char version[]; /* 操作系統(tǒng)發(fā)行版本*/char machine[]; /* 硬件架構(gòu)類型*/#ifdef _GNU_SOURCEchar domainname[];/* 當(dāng)前域名*/#endif };

可以看到,struct utsname 結(jié)構(gòu)體中的所有成員變量都是字符數(shù)組,所以獲取到的信息都是字符串。

測試

#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("操作系統(tǒng)名稱: %s\n", os_info.sysname);printf("主機名: %s\n", os_info.nodename);printf("內(nèi)核版本: %s\n", os_info.release);printf("發(fā)行版本: %s\n", os_info.version);printf("硬件架構(gòu): %s\n", os_info.machine);exit(0); }

運行結(jié)果:

sysinfo 函數(shù)

sysinfo 系統(tǒng)調(diào)用可用于獲取一些系統(tǒng)統(tǒng)計信息,其函數(shù)原型如下所示:

#include <sys/sysinfo.h>int sysinfo(struct sysinfo *info);

info:struct sysinfo 結(jié)構(gòu)體類型指針,指向一個struct sysinfo 結(jié)構(gòu)體類型對象。
返回值:成功返回0;失敗將返回-1,并設(shè)置errno。

struct sysinfo 結(jié)構(gòu)體如下所示:

struct sysinfo {long uptime; /* 自系統(tǒng)啟動之后所經(jīng)過的時間(以秒為單位)*/unsigned long loads[3]; /* 1, 5, and 15 minute load averages */unsigned long totalram; /* 總的可用內(nèi)存大小*/unsigned long freeram; /* 還未被使用的內(nèi)存大小*/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; /* 系統(tǒng)當(dāng)前進程數(shù)量*/unsigned long totalhigh; /* Total high memory size */unsigned long freehigh; /* Available high memory size */unsigned int mem_unit; /* 內(nèi)存單元大小(以字節(jié)為單位)*/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("自系統(tǒng)啟動之后所經(jīng)過的時間: %ld\n", sys_info.uptime);printf("總的可用內(nèi)存大小: %lu\n", sys_info.totalram);printf("還未被使用的內(nèi)存大小: %lu\n", sys_info.freeram);printf("系統(tǒng)當(dāng)前進程數(shù)量: %u\n", sys_info.procs);exit(0); }

運行結(jié)果:

gethostname 函數(shù)

用于單獨獲取Linux 系統(tǒng)主機名,與struct utsname 數(shù)據(jù)結(jié)構(gòu)體中的nodename 變量一樣:

#include <unistd.h>int gethostname(char *name, size_t len);

name:指向用于存放主機名字符串的緩沖區(qū)。
len:緩沖區(qū)長度。
返回值:成功返回0,;失敗將返回-1,并會設(shè)置errno。

測試

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

運行結(jié)果:

sysconf()函數(shù)

sysconf()函數(shù)是一個庫函數(shù),可在運行時獲取系統(tǒng)的一些配置信息,譬如頁大小(page size)、主機名的最大長度、進程可以打開的最大文件數(shù)、每個用戶ID 的最大并發(fā)進程數(shù)等。其函數(shù)原型如下所示:

#include <unistd.h>long sysconf(int name);

參數(shù)name 指定了要獲取哪個配置信息,參數(shù)name 可取以下任何一個值(都是宏定義,可通過man 手冊查詢):

? _SC_ARG_MAX:exec 族函數(shù)的參數(shù)的最大長度,exec 族函數(shù)后面會介紹,這里先不管!
? _SC_CHILD_MAX:每個用戶的最大并發(fā)進程數(shù),也就是同一個用戶可以同時運行的最大進程數(shù)。
? _SC_HOST_NAME_MAX:主機名的最大長度。
? _SC_LOGIN_NAME_MAX:登錄名的最大長度。
? _SC_CLK_TCK:每秒時鐘滴答數(shù),也就是系統(tǒng)節(jié)拍率。
? _SC_OPEN_MAX:一個進程可以打開的最大文件數(shù)。
? _SC_PAGESIZE:系統(tǒng)頁大小(page size)。
? _SC_TTY_NAME_MAX:終端設(shè)備名稱的最大長度。
? ……

若指定的參數(shù)name 為無效值,則sysconf()函數(shù)返回-1,并會將errno 設(shè)置為EINVAL。否則返回的值便是對應(yīng)的配置值。

使用示例

#include <stdio.h> #include <stdlib.h> #include <unistd.h>int main(void) {printf("每個用戶的最大并發(fā)進程數(shù): %ld\n", sysconf(_SC_CHILD_MAX));printf("系統(tǒng)節(jié)拍率: %ld\n", sysconf(_SC_CLK_TCK));printf("系統(tǒng)頁大小: %ld\n", sysconf(_SC_PAGESIZE));exit(0); }

運行結(jié)果:

時間、日期

GMT 時間

GMT(Greenwich Mean Time)中文全稱是格林威治標(biāo)準(zhǔn)時間,1884 年被確立,GMT 時間就是英國格林威治當(dāng)?shù)貢r間,也就是零時區(qū)(中時區(qū))所在時間,與我國的標(biāo)準(zhǔn)時間北京時間(東八區(qū))相差8 個小時,即早八個小時,所以GMT 12:00 對應(yīng)的北京時間是20:00。

UTC 時間

UTC(Coordinated Universal Time)指的是世界協(xié)調(diào)時間(又稱世界標(biāo)準(zhǔn)時間、世界統(tǒng)一時間),是經(jīng)過平均太陽時(以格林威治時間GMT 為準(zhǔn))、地軸運動修正后的新時標(biāo)以及以「秒」為單位的國際原子時所綜合精算而成的時間,計算過程相當(dāng)嚴(yán)謹(jǐn)精密,因此若以「世界標(biāo)準(zhǔn)時間」的角度來說,UTC 比GMT 來得更加精準(zhǔn)。

在Ubuntu 系統(tǒng)下,可以使用"date -u"命令查看到當(dāng)前的UTC 時間,如下所示:

UTC 時間格式

根據(jù) ISO 8601《數(shù)據(jù)存儲和交換形式·信息交換·日期和時間的表示方法》,UTC時間,也就是國際統(tǒng)一時間/國際協(xié)調(diào)時,表示方法如下:
YYYYMMDD T HHMMSS Z(或者時區(qū)標(biāo)識)。

例如,20100607T152000Z,表示2010年6月7號15點20分0秒,Z表示是標(biāo)準(zhǔn)時間

如果表示北京時間,那么就是:
20100607T152000+08,其中 “+08” 表示東八區(qū)。

時區(qū)

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

東十二區(qū)和西十二區(qū)其實是一個時區(qū),就是十二區(qū),東十二區(qū)與西十二區(qū)各橫跨經(jīng)度7.5 度,以180 度經(jīng)線作為分界線。每個時區(qū)的中央經(jīng)線上的時間就是這個時區(qū)內(nèi)統(tǒng)一采用的時間,稱為區(qū)時。相鄰兩個時區(qū)的時間相差1 小時。例如,我國東8 區(qū)的時間總比泰國東7 區(qū)的時間早1 小時,而比日本東9 區(qū)的時間晚1小時。因此,出國旅行的人,必須隨時調(diào)整自己的手表,才能和當(dāng)?shù)貢r間相一致。凡向西走,每過一個時區(qū),就要把表向前撥1 小時(比如2 點撥到1 點);凡向東走,每過一個時區(qū),就要把表向后撥1 小時(比如1 點撥到2 點)。

實際上,世界上不少國家和地區(qū)都不嚴(yán)格按時區(qū)來計算時間。為了在全國范圍內(nèi)采用統(tǒng)一的時間,一般都把某一個時區(qū)的時間作為全國統(tǒng)一采用的時間。例如,我國把首都北京所在的東8 區(qū)的時間作為全國統(tǒng)一的時間,稱為北京時間,北京時間就作為我國使用的本地時間,譬如我們電腦上顯示的時間就是北京時間,我國國土面積廣大,由東到西橫跨了5 個時區(qū),也就意味著我國最東邊的地區(qū)與最西邊的地區(qū)實際上相差了4、5 個小時。又例如,英國、法國、荷蘭和比利時等國,雖地處中時區(qū),但為了和歐洲大多數(shù)國家時間相一致,則采用東1 區(qū)的時間。

譬如在Ubuntu 系統(tǒng)下,可以使用date 命令查看系統(tǒng)當(dāng)前的本地時間,如下所示:

可以看到顯示出來的字符串后面有一個"CST"字樣,CST 在這里其實指的是China Standard Time(中國標(biāo)準(zhǔn)時間)的縮寫,表示當(dāng)前查看到的時間是中國標(biāo)準(zhǔn)時間,也就是我國所使用的標(biāo)準(zhǔn)時間–北京時間,一般在安裝Ubuntu 系統(tǒng)的時候會提示用戶設(shè)置所在城市,那么系統(tǒng)便會根據(jù)你所設(shè)置的城市來確定系統(tǒng)的本地時間對應(yīng)的時區(qū),譬如設(shè)置的城市為上海,那么系統(tǒng)的本地時間就是北京時間,因為我國統(tǒng)一使用北京時間作為本國的標(biāo)準(zhǔn)時間。

在Ubuntu 系統(tǒng)下,時區(qū)信息通常以標(biāo)準(zhǔn)格式保存在一些文件當(dāng)中,這些文件通常位于/usr/share/zoneinfo目錄下,該目錄下的每一個文件(包括子目錄下的文件)都包含了一個特定國家或地區(qū)內(nèi)時區(qū)制度的相關(guān)信息,且往往根據(jù)其所描述的城市或地區(qū)縮寫來加以命名,譬如EST(美國東部標(biāo)準(zhǔn)時間)、CET(歐洲中部時間)、UTC(世界標(biāo)準(zhǔn)時間)、Hongkong、Iran、Japan(日本標(biāo)準(zhǔn)時間)等,也把這些文件稱為時區(qū)配置文件,如下圖所示:

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

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

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


接下來再使用date 命令查看下系統(tǒng)當(dāng)前的時間,如下所示:

可以發(fā)現(xiàn)后面的標(biāo)識變成了EST,也就意味著當(dāng)前系統(tǒng)的本地時間變成了EST 時間(美國東部標(biāo)準(zhǔn)時間)。

實時時鐘RTC

操作系統(tǒng)中一般會有兩個時鐘,一個系統(tǒng)時鐘(system clock),一個實時時鐘(Real time clock),也叫RTC;系統(tǒng)時鐘由系統(tǒng)啟動之后由內(nèi)核來維護,譬如使用date 命令查看到的就是系統(tǒng)時鐘,所以在系統(tǒng)關(guān)機情況下是不存在的;而實時時鐘一般由RTC 時鐘芯片提供,RTC 芯片有相應(yīng)的電池為其供電,以保證系統(tǒng)在關(guān)機情況下RTC 能夠繼續(xù)工作、繼續(xù)計時。

Linux 系統(tǒng)如何記錄時間
Linux 系統(tǒng)在開機啟動之后首先會讀取RTC 硬件獲取實時時鐘作為系統(tǒng)時鐘的初始值,之后內(nèi)核便開始維護自己的系統(tǒng)時鐘。所以由此可知,RTC 硬件只有在系統(tǒng)開機啟動時會讀取一次,目的是用于對系統(tǒng)時鐘進行初始化操作,之后的運行過程中便不會再對其進行讀取操作了。
而在系統(tǒng)關(guān)機時,內(nèi)核會將系統(tǒng)時鐘寫入到RTC 硬件、以進行同步操作。

jiffies 的引入
jiffies 是內(nèi)核中定義的一個全局變量,內(nèi)核使用jiffies 來記錄系統(tǒng)從啟動以來的系統(tǒng)節(jié)拍數(shù),所以這個變量用來記錄以系統(tǒng)節(jié)拍時間為單位的時間長度,Linux 內(nèi)核在編譯配置時定義了一個節(jié)拍時間,使用節(jié)拍率(一秒鐘多少個節(jié)拍數(shù))來表示,譬如常用的節(jié)拍率為100Hz(一秒鐘100 個節(jié)拍數(shù),節(jié)拍時間為1s /100)、200Hz(一秒鐘200 個節(jié)拍,節(jié)拍時間為1s / 200)、250Hz(一秒鐘250 個節(jié)拍,節(jié)拍時間為1s /250)、300Hz(一秒鐘300 個節(jié)拍,節(jié)拍時間為1s / 300)、500Hz(一秒鐘500 個節(jié)拍,節(jié)拍時間為1s /500)等。由此可以發(fā)現(xiàn)配置的節(jié)拍率越低,每一個系統(tǒng)節(jié)拍的時間就越短,也就意味著jiffies 記錄的時間精度越高,當(dāng)然,高節(jié)拍率會導(dǎo)致系統(tǒng)中斷的產(chǎn)生更加頻繁,頻繁的中斷會加劇系統(tǒng)的負擔(dān),一般默認情況下都是采用100Hz 作為系統(tǒng)節(jié)拍率。

內(nèi)核其實通過jiffies 來維護系統(tǒng)時鐘,全局變量jiffies 在系統(tǒng)開機啟動時會設(shè)置一個初始值,RTC 實時時鐘會在系統(tǒng)開機啟動時讀取一次,目的是用于對系統(tǒng)時鐘進行初始化,這里說的初始化其實指的就是對內(nèi)核的jiffies 變量進行初始化操作,具體如何將讀取到的實時時鐘換算成jiffies 數(shù)值。

所以由此可知,操作系統(tǒng)使用jiffies 這個全局變量來記錄當(dāng)前時間,當(dāng)我們需要獲取到系統(tǒng)當(dāng)前時間點時,就可以使用jiffies 變量去計算,當(dāng)然并不需要我們手動去計算,Linux 系統(tǒng)提供了相應(yīng)的系統(tǒng)調(diào)用或C庫函數(shù)用于獲取當(dāng)前時間,譬如系統(tǒng)調(diào)用time()、gettimeofday(),其實質(zhì)上就是通過jiffies 變量換算得到。

獲取時間time/gettimeofday

(1)time 函數(shù)
系統(tǒng)調(diào)用time()用于獲取當(dāng)前時間,以秒為單位,返回得到的值是自1970-01-01 00:00:00 +0000 (UTC)以來的秒數(shù),函數(shù)原型如下所示:

#include <time.h>time_t time(time_t *tloc);

tloc:如果tloc 參數(shù)不是NULL,則返回值也存儲在tloc 指向的內(nèi)存中。
返回值:成功則返回自1970-01-01 00:00:00 +0000 (UTC)以來的時間值(以秒為單位);失敗則返回-1,并會設(shè)置errno。

time 函數(shù)獲取得到的是一個時間段,也就是從1970-01-01 00:00:00 +0000 (UTC)到現(xiàn)在這段時間所經(jīng)過的秒數(shù),所以你要計算現(xiàn)在這個時間點,只需要使用time()得到的秒數(shù)加1970-01-01 00:00:00即可!當(dāng)然,這并不需要我們手動去計算,可以直接使用相關(guān)系統(tǒng)調(diào)用或C 庫函數(shù)來得到當(dāng)前時間,后面介紹。

自1970-01-01 00:00:00 +0000 (UTC)以來經(jīng)過的總秒數(shù),我們把這個稱之為日歷時間或time_t 時間。

測試
使用系統(tǒng)調(diào)用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); }

運行結(jié)果:

(2)gettimeofday 函數(shù)
time()獲取到的時間只能精確到秒,gettimeofday()函數(shù)提供微秒級時間精度,函數(shù)原型如下:

#include <sys/time.h>int gettimeofday(struct timeval *tv, struct timezone *tz);

tv:參數(shù)tv 是一個struct timeval 結(jié)構(gòu)體指針變量,struct timeval 結(jié)構(gòu)體在前面章節(jié)內(nèi)容中已經(jīng)給大家介紹過,具體參考示例代碼5.6.3。

tz:參數(shù)tz 是個歷史產(chǎn)物,早期實現(xiàn)用其來獲取系統(tǒng)的時區(qū)信息,目前已遭廢棄,在調(diào)用gettimeofday()函數(shù)時應(yīng)將參數(shù)tz 設(shè)置為NULL。

返回值:成功返回0;失敗將返回-1,并設(shè)置errno。

獲取得到的時間值存儲在參數(shù)tv 所指向的struct timeval 結(jié)構(gòu)體變量中,該結(jié)構(gòu)體包含了兩個成員變量tv_sec 和tv_usec,分別用于表示秒和微秒,所以獲取得到的時間值就是tv_sec(秒)+tv_usec(微秒),同樣獲取得到的秒數(shù)與time()函數(shù)一樣,也是自1970-01-01 00:00:00 +0000 (UTC)到現(xiàn)在這段時間所經(jīng)過的秒數(shù),也就是日歷時間,所以由此可知time()返回得到的值和函數(shù)gettimeofday()所返回的tv 參數(shù)中tv_sec 字段的數(shù)值相同。

測試

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

運行結(jié)果:

時間轉(zhuǎn)換函數(shù)

通過time()或gettimeofday()函數(shù)可以獲取到當(dāng)前時間點相對于1970-01-01 00:00:00 +0000 (UTC)這個時間點所經(jīng)過時間(日歷時間),所以獲取得到的是一個時間段的長度,但是這并不利于我們查看當(dāng)前時間,這個結(jié)果對于我們來說非常不友好,那么本小節(jié)將向大家介紹一些系統(tǒng)調(diào)用或C 庫函數(shù),通過這些API 可以將time()或gettimeofday()函數(shù)獲取到的秒數(shù)轉(zhuǎn)換為利于查看和理解的形式。

(1)ctime 函數(shù)
ctime()是一個C 庫函數(shù),可以將日歷時間轉(zhuǎn)換為可打印輸出的字符串形式,ctime()函數(shù)原型如下所示:

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

timep:time_t 時間變量指針。
返回值:成功將返回一個char *類型指針,指向轉(zhuǎn)換后得到的字符串;失敗將返回NULL。

所以由此可知,使用ctime 函數(shù)非常簡單,只需將time_t 時間變量的指針傳入即可,調(diào)用成功便可返回字符串指針,拿到字符串指針之后,可以使用printf 將其打印輸出。但是ctime()是一個不可重入函數(shù),存在一些安全上面的隱患,ctime_r()是ctime()的可重入版本,一般推薦大家使用可重入函數(shù)ctime_r(),可重入函數(shù)ctime_r()多了一個參數(shù)buf,也就是緩沖區(qū)首地址,所以ctime_r()函數(shù)需要調(diào)用者提供用于存放字符串的緩沖區(qū)。

Tips:關(guān)于可重入函數(shù)與不可重入函數(shù)將會在后面章節(jié)內(nèi)容中進行介紹,這里暫時先不去管這個問題,在Linux 系統(tǒng)中,有一些系統(tǒng)調(diào)用或C庫函數(shù)提供了可重入版本與不可重入版本的函數(shù)接口,可重入版本函數(shù)所對應(yīng)的函數(shù)名一般都會有一個" _r "后綴來表明它是一個可重入函數(shù)。
ctime (或ctime_r)轉(zhuǎn)換得到的時間是計算機所在地對應(yīng)的本地時間(譬如在中國對應(yīng)的便是北京時間),并不是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);}/* 將時間轉(zhuǎn)換為字符串形式*/ctime_r(&tm, tm_str);/* 打印輸出*/printf("當(dāng)前時間: %s", tm_str);exit(0); }

運行結(jié)果:

從圖中可知,打印出來的時間為"Mon Feb 22 17:10:46 2021",Mon 表示星期一,這是一個英文單詞的縮寫,Feb 表示二月份,這也是一個英文單詞的縮寫,22 表示22 日,所以整個打印信息顯示的時間就是2021年2 月22 日星期一17 點10 分46 秒。

(2)localtime 函數(shù)
localtime()函數(shù)可以把time()或gettimeofday()得到的秒數(shù)(time_t 時間或日歷時間)變成一個struct tm結(jié)構(gòu)體所表示的時間,該時間對應(yīng)的是本地時間。localtime 函數(shù)原型如下:

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

函數(shù)參數(shù)和返回值含義如下:
timep:需要進行轉(zhuǎn)換的time_t 時間變量對應(yīng)的指針,可通過time()或gettimeofday()獲取得到。
result:是一個struct tm 結(jié)構(gòu)體類型指針,稍后給大家介紹struct tm 結(jié)構(gòu)體,參數(shù)result 是可重入函數(shù)
localtime_r()需要額外提供的參數(shù)。
返回值:對于不可重入版本localtime()來說,成功則返回一個有效的struct tm 結(jié)構(gòu)體指針,而對于可重入版本localtime_r()來說,成功執(zhí)行情況下,返回值將會等于參數(shù)result;失敗則返回NULL。

使用不可重入函數(shù)localtime()并不需要調(diào)用者提供struct tm 變量,而是它會直接返回出來一個struct tm結(jié)構(gòu)體指針,然后直接通過該指針訪問里邊的成員變量即可!雖然很方便,但是存在一些安全隱患,所以一般不推薦使用不可重入版本。

使用可重入版本localtime_r()調(diào)用者需要自己定義struct tm 結(jié)構(gòu)體變量、并將該變量指針賦值給參數(shù)result,在函數(shù)內(nèi)部會對該結(jié)構(gòu)體變量進行賦值操作。
struct tm 結(jié)構(gòu)體如下所示:

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 年到現(xiàn)在經(jīng)過的年數(shù)) */int tm_wday; /* 星期(0-6, 星期日Sunday = 0、星期一=1…) */int tm_yday; /* 一年里的第幾天(0-365, 1 Jan = 0) */int tm_isdst; /* 夏令時*/ };

從struct tm 結(jié)構(gòu)體內(nèi)容可知,該結(jié)構(gòu)體中包含了年月日時分秒星期等信息,使用localtime/localtime_r()便可以將time_t 時間總秒數(shù)分解成了各個獨立的時間信息,易于我們查看和理解。

測試

#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);}/* 轉(zhuǎn)換得到本地時間*/localtime_r(&sec, &t);/* 打印輸出*/printf("當(dāng)前時間: %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); }

運行結(jié)果:

(3)gmtime 函數(shù)
gmtime()函數(shù)也可以把time_t 時間變成一個struct tm 結(jié)構(gòu)體所表示的時間,與localtime()所不同的是,gmtime()函數(shù)所得到的是UTC 國際標(biāo)準(zhǔn)時間,并不是計算機的本地時間,這是它們之間的唯一區(qū)別。gmtime()函數(shù)原型如下所示:

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

gmtime_r()是gmtime()的可重入版本,同樣也是推薦大家使用可重入版本函數(shù)gmtime_r。關(guān)于該函數(shù)的參數(shù)和返回值,這里便不再介紹,與localtime()是一樣的。

測試
使用localtime 獲取本地時間、使用gmtime 獲取UTC 國際標(biāo)準(zhǔn)時間,并進行對比:

#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);}/* 轉(zhuǎn)換得到本地時間*/localtime_r(&sec, &local_t);/* 轉(zhuǎn)換得到國際標(biāo)準(zhǔn)時間*/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); }

運行結(jié)果:

從打印結(jié)果可知,本地時間與UTC 時間(國際標(biāo)準(zhǔn)時間)相差8 個小時,因為筆者使用的計算機其對應(yīng)的本地時間指的便是北京時間,而北京時間要早于國際標(biāo)準(zhǔn)時間8 個小時(東八區(qū))。
(4)mktime 函數(shù)
mktime()函數(shù)與localtime()函數(shù)相反,mktime()可以將使用struct tm 結(jié)構(gòu)體表示的分解時間轉(zhuǎn)換為time_t時間(日歷時間),同樣這也是一個C 庫函數(shù),其函數(shù)原型如下所示:

#include <time.h>time_t mktime(struct tm *tm);

tm:需要進行轉(zhuǎn)換的struct tm 結(jié)構(gòu)體變量對應(yīng)的指針。
返回值:成功返回轉(zhuǎn)換得到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("獲取得到的秒數(shù): %ld\n", sec);localtime_r(&sec, &local_t);printf("轉(zhuǎn)換得到的秒數(shù): %ld\n", mktime(&local_t));exit(0); }

運行結(jié)果:

(5)asctime 函數(shù)
asctime()函數(shù)與ctime()函數(shù)的作用一樣,也可將時間轉(zhuǎn)換為可打印輸出的字符串形式,與ctime()函數(shù)的區(qū)別在于,ctime()是將time_t 時間轉(zhuǎn)換為固定格式字符串、而asctime()則是將struct tm 表示的分解時間轉(zhuǎn)換為固定格式的字符串。asctime()函數(shù)原型如下所示:

#include <time.h>char *asctime(const struct tm *tm); char *asctime_r(const struct tm *tm, char *buf);

tm:需要進行轉(zhuǎn)換的struct tm 表示的時間。
buf:可重入版本函數(shù)asctime_r 需要額外提供的參數(shù)buf,指向一個緩沖區(qū),用于存放轉(zhuǎn)換得到的字符串。
返回值:轉(zhuǎn)換失敗將返回NULL;成功將返回一個char *類型指針,指向轉(zhuǎn)換后得到的時間字符串,對于asctime_r 函數(shù)來說,返回值就等于參數(shù)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); }

運行結(jié)果:

(6)strftime 函數(shù)

除了asctime()函數(shù)之外,這里再給大家介紹一個C 庫函數(shù)strftime(),此函數(shù)也可以將一個struct tm 變量表示的分解時間轉(zhuǎn)換為為格式化字符串,并且在功能上比asctime()和ctime()更加強大,它可以根據(jù)自己的喜好自定義時間的顯示格式,而asctime()和ctime()轉(zhuǎn)換得到的字符串時間格式的固定的。

strftime()函數(shù)原型如下所示:

#include <time.h>size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);

s:指向一個緩存區(qū)的指針,該緩沖區(qū)用于存放生成的字符串。
max:字符串的最大字節(jié)數(shù)。
format:這是一個用字符串表示的字段,包含了普通字符和特殊格式說明符,可以是這兩種字符的任意組合。特殊格式說明符將會被替換為struct tm 結(jié)構(gòu)體對象所指時間的相應(yīng)值,這些特殊格式說明符如下:



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

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

tm:指向struct tm 結(jié)構(gòu)體對象的指針。
返回值:如果轉(zhuǎn)換得到的目標(biāo)字符串不超過最大字節(jié)數(shù)(也就是max),則返回放置到s 數(shù)組中的字節(jié)數(shù);如果超過了最大字節(jié)數(shù),則返回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); }

運行結(jié)果:

設(shè)置時間settimeofday

使用settimeofday()函數(shù)可以設(shè)置時間,也就是設(shè)置系統(tǒng)的本地時間,函數(shù)原型如下所示:

#include <sys/time.h>int settimeofday(const struct timeval *tv, const struct timezone *tz);

tv:參數(shù)tv 是一個struct timeval 結(jié)構(gòu)體指針變量,struct timeval 結(jié)構(gòu)體在前面章節(jié)內(nèi)容中已經(jīng)給大家介紹了,需要設(shè)置的時間便通過參數(shù)tv 指向的struct timeval 結(jié)構(gòu)體變量傳遞進去。
tz:參數(shù)tz 是個歷史產(chǎn)物,早期實現(xiàn)用其來設(shè)置系統(tǒng)的時區(qū)信息,目前已遭廢棄,在調(diào)用settimeofday()函數(shù)時應(yīng)將參數(shù)tz 設(shè)置為NULL。
返回值:成功返回0;失敗將返回-1,并設(shè)置errno。

使用settimeofday 設(shè)置系統(tǒng)時間時內(nèi)核會進行權(quán)限檢查,只有超級用戶(root)才可以設(shè)置系統(tǒng)時間,普通用戶將無操作權(quán)限。

總結(jié)

本小節(jié)給大家介紹了時間相關(guān)的基本概念,譬如GMT 時間、UTC 時間以及全球24 個時區(qū)的劃分等,并且給大家介紹了Linux 系統(tǒng)下常用的時間相關(guān)的系統(tǒng)調(diào)用和庫函數(shù),主要有9 個:time/ctime/localtime/gmtime/mktime/asctime/strftime/gettimeofday/settimeofday,對這些函數(shù)的功能、作用總結(jié)如下:

進程時間

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

times 函數(shù)

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

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

buf:times()會將當(dāng)前進程時間信息存在一個struct tms 結(jié)構(gòu)體數(shù)據(jù)中,所以我們需要提供struct tms 變量,使用參數(shù)buf 指向該變量,關(guān)于struct tms 結(jié)構(gòu)體稍后給大家介紹。

返回值:返回值類型為clock_t(實質(zhì)是long 類型),調(diào)用成功情況下,將返回從過去任意的一個時間點(譬如系統(tǒng)啟動時間)所經(jīng)過的時鐘滴答數(shù)(其實就是系統(tǒng)節(jié)拍數(shù)),將(節(jié)拍數(shù)/ 節(jié)拍率)便可得到秒數(shù),返回值可能會超過clock_t 所能表示的范圍(溢出);調(diào)用失敗返回-1,并設(shè)置errno。

如果我們想查看程序運行到某一個位置時的進程時間,或者計算出程序中的某一段代碼執(zhí)行過程所花費的進程時間,都可以使用times()函數(shù)來實現(xiàn)。

struct tms 結(jié)構(gòu)體內(nèi)容如下所示:

struct tms {clock_t tms_utime; /* user time, 進程的用戶CPU 時間, tms_utime 個系統(tǒng)節(jié)拍數(shù)*/clock_t tms_stime; /* system time, 進程的系統(tǒng)CPU 時間, tms_stime 個系統(tǒng)節(jié)拍數(shù)*/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()來計算程序中某一段代碼執(zhí)行所耗費的進程時間和總的時間,測試程序如下所示:

#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;/* 獲取系統(tǒng)的節(jié)拍率*/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************** *//* 結(jié)束時間*/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("系統(tǒng)CPU 時間: %f 秒\n", (t_buf_end.tms_stime - t_buf_start.tms_stime) / (double)tck);exit(0); }

首先,筆者先對測試程序做一個簡單地介紹,程序中使用sysconf(_SC_CLK_TCK)獲取到系統(tǒng)節(jié)拍率,程序還使用了一個庫函數(shù)sleep(),該函數(shù)也是本章將要向大家介紹的函數(shù),具體參考7.5.1 小節(jié)中的介紹。

示例代碼7.3.2 中對如下代碼段進行了測試:

for (i = 0; i < 20000; i++)for (j = 0; j < 20000; j++); sleep(1); //休眠掛起

接下來編譯運行,測試結(jié)果如下:

可以看到用戶CPU 時間為1.9 秒,系統(tǒng)CPU 時間為0 秒,也就是說測試的這段代碼并沒有進入內(nèi)核態(tài)運行,所以總的進程時間= 用戶CPU 時間+ 系統(tǒng)CPU 時間= 1.9 秒。

圖7.3.1 中顯示的時間總和并不是總的進程時間,前面也給大家解釋過,這個時間總和指的是從起點到終點鎖經(jīng)過的時間,并不是進程時間,這里大家要理解。時間總和包括了進程處于休眠狀態(tài)時消耗的時間(sleep 等會讓進程掛起、進入休眠狀態(tài)),可以發(fā)現(xiàn)時間總和比進程時間多1 秒,其實這一秒就是進程處于休眠狀態(tài)的時間。

clock 函數(shù)

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

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

無參數(shù)。
返回值:返回值是到目前為止程序的進程時間,為clock_t 類型,注意clock()的返回值并不是系統(tǒng)節(jié)拍數(shù),如果想要獲得秒數(shù),請除以CLOCKS_PER_SEC(這是一個宏)。如果返回的進程時間不可用或其值無法表示,則該返回值是-1。
clock()函數(shù)雖然可以很方便的獲取總的進程時間,但并不能獲取到單獨的用戶CPU 時間和系統(tǒng)CPU 時間,在實際編程當(dāng)中,根據(jù)自己的需要選擇。

測試
對示例代碼7.3.2 進行簡單地修改,使用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************** *//* 結(jié)束時間*/t_end = clock();if (-1 == t_end)exit(-1);/* 打印時間*/printf("總的CPU 時間: %f\n", (t_end - t_start) / (double)CLOCKS_PER_SEC);exit(0); }

運行結(jié)果:

產(chǎn)生隨機數(shù)

隨機數(shù)與偽隨機數(shù)
隨機數(shù)是隨機出現(xiàn),沒有任何規(guī)律的一組數(shù)列。在我們編程當(dāng)中,是沒有辦法獲得真正意義上的隨機數(shù)列的,這是一種理想的情況,在我們的程序當(dāng)中想要使用隨機數(shù)列,只能通過算法得到一個偽隨機數(shù)序列,那在編程當(dāng)中說到的隨機數(shù),基本都是指偽隨機數(shù)。

C 語言函數(shù)庫中提供了很多函數(shù)用于產(chǎn)生偽隨機數(shù),其中最常用的是通過rand()和srand()產(chǎn)生隨機數(shù),本小節(jié)就以這兩個函數(shù)為例向大家介紹如何在我們的程序中獲得隨機數(shù)列。

rand 函數(shù)
rand()函數(shù)用于獲取隨機數(shù),多次調(diào)用rand()可得到一組隨機數(shù)序列,其函數(shù)原型如下:

#include <stdlib.h>int rand(void);

返回值:返回一個介于0 到RAND_MAX(包含)之間的值,也就是數(shù)學(xué)上的[0, RAND_MAX]。
程度當(dāng)中調(diào)用rand()可以得到[0, RAND_MAX]之間的偽隨機數(shù),多次調(diào)用rand()便可以生成一組偽隨機樹序列,但是這里有個問題,就是每一次運行程序所得到的隨機數(shù)序列都是相同的,那如何使得每一次啟動應(yīng)用程序所得到的隨機數(shù)序列是不一樣的呢?那就通過設(shè)置不同的隨機數(shù)種子,可通過srand()設(shè)置隨機數(shù)種子。
如果沒有調(diào)用srand()設(shè)置隨機數(shù)種子的情況下,rand()會將1 作為隨機數(shù)種子,如果隨機數(shù)種子相同,那么每一次啟動應(yīng)用程序所得到的隨機數(shù)序列就是一樣的,所以每次啟動應(yīng)用程序需要設(shè)置不同的隨機數(shù)種子,這樣就可以使得程序每次運行所得到隨機數(shù)序列不同。

srand 函數(shù)
使用srand()函數(shù)為rand()設(shè)置隨機數(shù)種子,其函數(shù)原型如下所示:

#include <stdlib.h>void srand(unsigned int seed);

seed:指定一個隨機數(shù)中,int 類型的數(shù)據(jù),一般嘗嘗將當(dāng)前時間作為隨機數(shù)種子賦值給參數(shù)seed,譬如time(NULL),因為每次啟動應(yīng)用程序時間上是一樣的,所以就能夠使得程序中設(shè)置的隨機數(shù)種子在每次啟動程序時是不一樣的。
返回值:void
常用的用法srand(time(NULL));

測試
使用rand()和srand()產(chǎn)生一組偽隨機數(shù),數(shù)值范圍為[0~100],將其打印出來:

#include <stdio.h> #include <stdlib.h> #include <time.h>int main(int argc, char *argv[]) {int random_number_arr[8];int count;/* 設(shè)置隨機數(shù)種子*/srand(time(NULL));/* 生成偽隨機數(shù)*/for (count = 0; count < 8; count++)random_number_arr[count] = rand() % 100;/* 打印隨機數(shù)數(shù)組*/printf("[");for (count = 0; count < 8; count++) {printf("%d", random_number_arr[count]);if (count != 8 - 1)printf(", ");}printf("]\n");exit(0); }

運行結(jié)果:

從圖中可以發(fā)現(xiàn),每一次得到的[0~100]之間的隨機數(shù)數(shù)組都是不同的(數(shù)組不同,不是產(chǎn)生的隨機數(shù)不同),因為程序中將rand()的隨機數(shù)種子設(shè)置為srand(time(NULL)),直接等于time_t 時間值,意味著每次啟動種子都不一樣,所以能夠產(chǎn)生不同的隨機數(shù)數(shù)組。

本小節(jié)關(guān)于在Linux 下使用隨機數(shù)就給大家介紹這么多,產(chǎn)生隨機數(shù)的API 函數(shù)并不僅僅只有這些,除此之外,譬如還有random()、srandom()、initstate()、setstate()等,這里便不再給大家一一介紹了,在我們使用man 手冊查看系統(tǒng)調(diào)用或C 庫函數(shù)幫助信息時,在幫助信息頁面SEE ALSO 欄會列舉出與本函數(shù)有關(guān)聯(lián)的一些命令、系統(tǒng)調(diào)用或C 庫函數(shù)等,如下所示(譬如執(zhí)行man 3 srand 查看):

休眠(延時)

有時需要將進程暫停或休眠一段時間,進入休眠狀態(tài)之后,程序?qū)和_\行,直到休眠結(jié)束。常用的系統(tǒng)調(diào)用和C 庫函數(shù)有sleep()、usleep()以及nanosleep(),這些函數(shù)在應(yīng)用程序當(dāng)中通常作為延時使用,譬如延時1 秒鐘,本小節(jié)將一一介紹。

秒級休眠: sleep

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

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

seconds:休眠時長,以秒為單位。
返回值:如果休眠時長為參數(shù)seconds 所指定的秒數(shù),則返回0;若被信號中斷則返回剩余的秒數(shù)。
sleep()是一個秒級別休眠函數(shù),程序在休眠過程中,是可以被其它信號所打斷的,關(guān)于信號這些內(nèi)容,將會在后面章節(jié)向大家介紹。

測試
編寫一個簡單地程序,調(diào)用sleep()函數(shù)讓程序暫停運行(休眠)3 秒鐘。

#include <stdio.h> #include <stdlib.h> #include <unistd.h>int main(void) {puts("Sleep Start!");/* 讓程序休眠3 秒鐘*/sleep(3);puts("Sleep End!");exit(0); }

運行結(jié)果:

微秒級休眠: usleep

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

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

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

測試
使用usleep()函數(shù)讓程序休眠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); }

運行結(jié)果:

高精度休眠: nanosleep

nanosleep()與sleep()以及usleep()類似,都用于程序休眠,但nanosleep()具有更高精度來設(shè)置休眠時間長度,支持納秒級時長設(shè)置。與sleep()、usleep()不同的是,nanosleep()是一個Linux 系統(tǒng)調(diào)用,其函數(shù)原型如下所示:

#include <time.h>int nanosleep(const struct timespec *req, struct timespec *rem);

req:一個struct timespec 結(jié)構(gòu)體指針,指向一個struct timespec 變量,用于設(shè)置休眠時間長度,可精確到納秒級別。
rem:也是一個struct timespec 結(jié)構(gòu)體指針,指向一個struct timespec 變量,也可設(shè)置NULL。
返回值:在成功休眠達到請求的時間間隔后,nanosleep()返回0;如果中途被信號中斷或遇到錯誤,則返回-1,并將剩余時間記錄在參數(shù)rem 指向的struct timespec 結(jié)構(gòu)體變量中(參數(shù)rem 不為NULL 的情況下,如果為NULL 表示不接收剩余時間),還會設(shè)置errno 標(biāo)識錯誤類型。
在5.2.3 小節(jié)中介紹了struct timespec 結(jié)構(gòu)體,該結(jié)構(gòu)體包含了兩個成員變量,秒(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); }

運行結(jié)果:

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

申請堆內(nèi)存

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

在堆上分配內(nèi)存:malloc 和free

Linux C 程序當(dāng)中一般使用malloc()函數(shù)為程序分配一段堆內(nèi)存,而使用free()函數(shù)來釋放這段內(nèi)存,先來看下malloc()函數(shù)原型,如下所示:

#include <stdlib.h>void *malloc(size_t size);

size:需要分配的內(nèi)存大小,以字節(jié)為單位。
返回值:返回值為void *類型,如果申請分配內(nèi)存成功,將返回一個指向該段內(nèi)存的指針,void *并不是說沒有返回值或者返回空指針,而是返回的指針類型未知,所以在調(diào)用malloc()時通常需要進行強制類型轉(zhuǎn)換,將void *指針類型轉(zhuǎn)換成我們希望的類型;如果分配內(nèi)存失敗(譬如系統(tǒng)堆內(nèi)存不足)將返回NULL,如果參數(shù)size 為0,返回值也是NULL。

malloc()在堆區(qū)分配一塊指定大小的內(nèi)存空間,用來存放數(shù)據(jù)。這塊內(nèi)存空間在函數(shù)執(zhí)行完成后不會被初始化,它們的值是未知的,所以通常需要程序員對malloc()分配的堆內(nèi)存進行初始化操作。
在堆上分配的內(nèi)存,需要開發(fā)者自己手動釋放掉,通常使用free()函數(shù)釋放堆內(nèi)存,free()函數(shù)原型如下所示:

#include <stdlib.h>void free(void *ptr);

ptr:指向需要被釋放的堆內(nèi)存對應(yīng)的指針。
返回值:無返回值。
測試

#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;/* 申請堆內(nèi)存*/base = (char *)malloc(MALLOC_MEM_SIZE);if (NULL == base) {printf("malloc error\n");exit(-1);}/* 初始化申請到的堆內(nèi)存*/memset(base, 0x0, MALLOC_MEM_SIZE);/* 使用內(nèi)存*//* ...... *//* 釋放內(nèi)存*/free(base);exit(0); }

調(diào)用free()還是不調(diào)用free()

在學(xué)習(xí)文件IO 基礎(chǔ)章節(jié)內(nèi)容時曾向大家介紹過,Linux 系統(tǒng)中,當(dāng)一個進程終止時,內(nèi)核會自動關(guān)閉它沒有關(guān)閉的所有文件(該進程打開的文件,但是在進程終止時未調(diào)用close()關(guān)閉它)。同樣,對于內(nèi)存來說,也是如此!當(dāng)進程終止時,內(nèi)核會將其占用的所有內(nèi)存都返還給操作系統(tǒng),這包括在堆內(nèi)存中由malloc()函數(shù)所分配的內(nèi)存空間。基于內(nèi)存的這一自動釋放機制,很多應(yīng)用程序通常會省略對free()函數(shù)的調(diào)用。

這在程序中分配了多塊內(nèi)存的情況下可能會特別有用,因為加入多次對free()的調(diào)用不但會消耗品大量的CPU 時間,而且可能會使代碼趨于復(fù)雜。

雖然依靠終止進程來自動釋放內(nèi)存對大多數(shù)程序來說是可以接受的,但最好能夠在程序中顯式調(diào)用free()釋放內(nèi)存:

  • 首先其一,顯式調(diào)用free()能使程序具有更好的可讀性和可維護性;
  • 其二,對于很多程序來說,申請的內(nèi)存并不是在程序的生命周期中一直需要,大多數(shù)情況下,都是根據(jù)代碼需求動態(tài)申請、釋放的,如果申請的內(nèi)存對程序來說已經(jīng)不再需要了,那么就已經(jīng)把它釋放、歸還給操作系統(tǒng),如果持續(xù)占用,將會導(dǎo)致內(nèi)存泄漏,也就是人們常說的“你的程序在吃內(nèi)存”

在堆上分配內(nèi)存的其它方法

除了malloc()外,C 函數(shù)庫中還提供了一系列在堆上分配內(nèi)存的其它函數(shù),本小節(jié)將逐一介紹。

用calloc()分配內(nèi)存
calloc()函數(shù)用來動態(tài)地分配內(nèi)存空間并初始化為0,其函數(shù)原型如下所示:

#include <stdlib.h>void *calloc(size_t nmemb, size_t size);

calloc()在堆中動態(tài)地分配nmemb 個長度為size 的連續(xù)空間,并將每一個字節(jié)都初始化為0。所以它的結(jié)果是分配了nmemb * size 個字節(jié)長度的內(nèi)存空間,并且每個字節(jié)的值都是0。
返回值:分配成功返回指向該內(nèi)存的地址,失敗則返回NULL。
calloc()與malloc()的一個重要區(qū)別是:calloc()在動態(tài)分配完內(nèi)存后,自動初始化該內(nèi)存空間為零,而malloc()不初始化,里邊數(shù)據(jù)是未知的垃圾數(shù)據(jù)。下面的兩種寫法是等價的:

// calloc()分配內(nèi)存空間并初始化 char *buf1 = (char *)calloc(10, 2); // malloc()分配內(nèi)存空間并用memset()初始化 char *buf2 = (char *)malloc(10 * 2); memset(buf2, 0, 20);

測試
編寫測試代碼,將用戶輸入的一組數(shù)字存放到堆內(nèi)存中,并打印出來。

#include <stdio.h> #include <stdlib.h>int main(int argc, char *argv[]) {int *base = NULL;int i;/* 校驗傳參*/if (2 > argc)exit(-1);/* 使用calloc 申請內(nèi)存*/base = (int *)calloc(argc - 1, sizeof(int));if (NULL == base) {printf("calloc error\n");exit(-1);}/* 將字符串轉(zhuǎn)為int 型數(shù)據(jù)存放在base 指向的內(nèi)存中*/for (i = 0; i < argc - 1; i++)base[i] = atoi(argv[i+1]);/* 打印base 數(shù)組中的數(shù)據(jù)*/printf("你輸入的數(shù)據(jù)是: ");for (i = 0; i < argc - 1; i++)printf("%d ", base[i]);putchar('\n');/* 釋放內(nèi)存*/free(base);exit(0); }

運行結(jié)果:

分配對齊內(nèi)存

C 函數(shù)庫中還提供了一系列在堆上分配對齊內(nèi)存的函數(shù),對齊內(nèi)存在某些應(yīng)用場合非常有必要,常用于分配對其內(nèi)存的庫函數(shù)有:posix_memalign()、aligned_alloc()、memalign()、valloc()、pvalloc(),它們的函數(shù)原型如下所示:

#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()這三個函數(shù)時需要包含頭文件<stdlib.h>,而使用memalign()、pvalloc()這兩個函數(shù)時需要包含頭文件<malloc.h>。前面介紹的malloc()、calloc()分配內(nèi)存返回的地址其實也是對齊的,但是它倆的對齊都是固定的,并且對其的字節(jié)邊界比較小,譬如在32 位系統(tǒng)中,通常是以8 字節(jié)為邊界進行對其,在64 位系統(tǒng)中是以16 字節(jié)進行對其。如果想實現(xiàn)更大字節(jié)的對齊,則需要使用本小節(jié)介紹的函數(shù)。

posix_memalign()函數(shù)

posix_memalign()函數(shù)用于在堆上分配size 個字節(jié)大小的對齊內(nèi)存空間,將 * memptr 指向分配的空間,分配的內(nèi)存地址將是參數(shù)alignment 的整數(shù)倍。參數(shù) alignment 表示對齊字節(jié)數(shù),alignment 必須是2 的冪次方(譬如2 ^ 4、2 ^ 5、2 ^ 8 等),同時也要是sizeof( void * )的整數(shù)倍,對于32 位系統(tǒng)來說,sizeof(void *)等于
4,如果是64 位系統(tǒng)sizeof(void *)等于8。

函數(shù)參數(shù)和返回值含義如下:
memptr:void ** 類型的指針,內(nèi)存申請成功后會將分配的內(nèi)存地址存放在* memptr 中。
alignment:設(shè)置內(nèi)存對其的字節(jié)數(shù),alignment 必須是2 的冪次方(譬如2^ 4、2 ^ 5、2 ^ 8 等),同時也要是 sizeof( void *)的整數(shù)倍。
size:設(shè)置分配的內(nèi)存大小,以字節(jié)為單位,如果參數(shù)size 等于0,那么 * memptr 中的值是NULL。
返回值:成功將返回0;失敗返回非0 值。
示例代碼

#include <stdio.h> #include <stdlib.h>int main(int argc, char *argv[]) {int *base = NULL;int ret;/* 申請內(nèi)存: 256 字節(jié)對齊*/ret = posix_memalign((void **)&base, 256, 1024);if (0 != ret) {printf("posix_memalign error\n");exit(-1);}/* 使用內(nèi)存*/// base[0] = 0;// base[1] = 1;// base[2] = 2;// base[3] = 3;/* 釋放內(nèi)存*/free(base);exit(0); }

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

#include <stdio.h> #include <stdlib.h>int main(int argc, char *argv[]) {int *base = NULL;/* 申請內(nèi)存: 256 字節(jié)對齊*/base = (int *)aligned_alloc(256, 256 * 4);if (base == NULL) {printf("aligned_alloc error\n");exit(-1);}/* 使用內(nèi)存*/// base[0] = 0;// base[1] = 1;// base[2] = 2;// base[3] = 3;/* 釋放內(nèi)存*/free(base);exit(0); }

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

#include <stdio.h> #include <stdlib.h> #include <malloc.h>int main(int argc, char *argv[]) {int *base = NULL;/* 申請內(nèi)存: 256 字節(jié)對齊*/base = (int *)memalign(256, 1024);if (base == NULL) {printf("memalign error\n");exit(-1);}/* 使用內(nèi)存*/// base[0] = 0;// base[1] = 1;// base[2] = 2;// base[3] = 3;/* 釋放內(nèi)存*/free(base);exit(0); }

valloc()函數(shù)

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

使用示例

#include <stdio.h> #include <stdlib.h>int main(int argc, char *argv[]) {int *base = NULL;/* 申請內(nèi)存: 1024 個字節(jié)*/base = (int *)valloc(1024);if (base == NULL) {printf("valloc error\n");exit(-1);}/* 使用內(nèi)存*/// base[0] = 0;// base[1] = 1;// base[2] = 2;// base[3] = 3;/* 釋放內(nèi)存*/free(base);exit(0); }

proc 虛擬文件系統(tǒng)

proc文件系統(tǒng)是一個虛擬文件系統(tǒng),它以文件系統(tǒng)的方式為應(yīng)用層訪問系統(tǒng)內(nèi)核數(shù)據(jù)提供了接口,用戶和應(yīng)用程序可以通過proc文件系統(tǒng)得到系統(tǒng)信息和進程相關(guān)信息,對proc文件系統(tǒng)的讀寫作為與內(nèi)核進行通信的一種手段。但是與普通文件不同的是,proc文件系統(tǒng)是動態(tài)創(chuàng)建的,文件本身并不存在于磁盤當(dāng)中、只存在于內(nèi)存當(dāng)中,與devfs一樣,都被稱為虛擬文件系統(tǒng)

最初構(gòu)建proc文件系統(tǒng)是為了提供有關(guān)系統(tǒng)中進程相關(guān)的信息,但是由于這個文件系統(tǒng)非常有用,因此內(nèi)核中的很多信息也開始使用它來報告,或啟用動態(tài)運行時配置。內(nèi)核構(gòu)建proc虛擬文件系統(tǒng),它會將內(nèi)核運行時的一些關(guān)鍵數(shù)據(jù)信息以文件的方式呈現(xiàn)在proc文件系統(tǒng)下的一些特定文件中,這樣相當(dāng)于將一些不可見的內(nèi)核中的數(shù)據(jù)結(jié)構(gòu)以可視化的方式呈現(xiàn)給應(yīng)用層。

proc文件系統(tǒng)掛載在系統(tǒng)的/proc目錄下,對于內(nèi)核開發(fā)者(譬如驅(qū)動開發(fā)工程師)來說,proc文件系統(tǒng)給了開發(fā)者一種調(diào)試內(nèi)核的方法:通過查看/proc/xxx文件來獲取到內(nèi)核特定數(shù)據(jù)結(jié)構(gòu)的值,在添加了新功能前后進行對比,就可以判斷此功能所產(chǎn)生的影響是否合理。

/proc目錄下中包含了一些目錄和虛擬文件,如下所示:

可以看到/proc 目錄下有很多以數(shù)字命名的文件夾,譬如100038、2299、98560,這些數(shù)字對應(yīng)的其實就是一個一個的進程PID 號,每一個進程在內(nèi)核中都會存在一個編號。

所以這些以數(shù)字命名的文件夾中記錄了這些進程相關(guān)的信息,不同的信息通過不同的虛擬文件呈現(xiàn)出來,關(guān)于這些信息將會在后面章節(jié)內(nèi)容中向大家介紹。

/proc 目錄下除了文件夾之外,還有很多的虛擬文件,譬如buddyinfo、cgroups、cmdline、version 等等,不同的文件記錄了不同信息,關(guān)于這些文件記錄的信息和意思如下:

? cmdline:內(nèi)核啟動參數(shù);
? cpuinfo:CPU 相關(guān)信息;
? iomem:IO 設(shè)備的內(nèi)存使用情況;
? interrupts:顯示被占用的中斷號和占用者相關(guān)的信息;
? ioports:IO 端口的使用情況;
? kcore:系統(tǒng)物理內(nèi)存映像,不可讀取;
? loadavg:系統(tǒng)平均負載;
? meminfo:物理內(nèi)存和交換分區(qū)使用情況;
? modules:加載的模塊列表;
? mounts:掛載的文件系統(tǒng)列表;
? partitions:系統(tǒng)識別的分區(qū)表;
? swaps:交換分區(qū)的利用情況;
? version:內(nèi)核版本信息;
? uptime:系統(tǒng)運行時間;

proc 文件系統(tǒng)的使用

proc 文件系統(tǒng)的使用就是去讀取/proc 目錄下的這些文件,獲取文件中記錄的信息,可以直接使用cat 命令讀取,也可以在應(yīng)用程序中調(diào)用open()打開、然后再使用read()函數(shù)讀取。

使用cat 命令讀取
在Linux 系統(tǒng)下直接使用cat 命令查看/proc 目錄下的虛擬文件,譬如"cat /proc/version"查看內(nèi)核版本相關(guān)信息:

使用read()函數(shù)讀取
編寫一個簡單地程序,使用read()函數(shù)讀取/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);/* 關(guān)閉文件*/close(fd);exit(0); }

運行結(jié)果:

總結(jié)

以上是生活随笔為你收集整理的Linux系统信息与系统资源的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。