日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無(wú)先后,達(dá)者為師

網(wǎng)站首頁(yè) 編程語(yǔ)言 正文

UNIX環(huán)境高級(jí)編程筆記

作者:颯颯ai 更新時(shí)間: 2022-11-14 編程語(yǔ)言

一、UNIX基礎(chǔ)知識(shí)

1.1 Linux 主要特性

Linux 是一個(gè)基于文件的操作系統(tǒng)

操作系統(tǒng)需要和硬件進(jìn)行交互,對(duì)應(yīng) Linux 來(lái)說(shuō)這些硬件都是文件,比如:操作系統(tǒng)會(huì)將 硬盤(pán) , 鼠標(biāo) , 鍵盤(pán) , 顯示屏等抽象成一個(gè)設(shè)備文件來(lái)進(jìn)行管理。

Linux 操作系統(tǒng)是一種自由軟件,是免費(fèi)的,并且公開(kāi)源代碼。

可以同時(shí)登陸多個(gè)用戶,并且每個(gè)用戶可以同時(shí)運(yùn)行多個(gè)應(yīng)用程序。

提供了友好的圖形用戶界面,操作簡(jiǎn)單, 易于快速上手。

支持多平臺(tái)(這里指的是基于不同 CPU 架構(gòu)的平臺(tái),比如國(guó)產(chǎn) Linux 使用的龍芯等)

UNIX體系結(jié)構(gòu)
在這里插入圖片描述
內(nèi)核:控制計(jì)算機(jī)運(yùn)行資源,提供程序運(yùn)行環(huán)境

系統(tǒng)調(diào)用:內(nèi)核的接口

共用庫(kù)函數(shù):共用庫(kù)函數(shù)構(gòu)建在系統(tǒng)調(diào)用的接口上

shell:shell是一個(gè)特殊的應(yīng)用程序,為運(yùn)行其他的應(yīng)用程序提供接口

1.2 Linux 內(nèi)核

Linux 系統(tǒng)從應(yīng)用角度來(lái)看,分為內(nèi)核空間和用戶空間兩個(gè)部分。內(nèi)核空間是 Linux 操作系統(tǒng)的主要部分,但是僅有內(nèi)核的操作系統(tǒng)是不能完成用戶任務(wù)的。豐富并且功能強(qiáng)大的應(yīng)用程序包是一個(gè)操作系統(tǒng)成功的必要件。這個(gè)和武林秘籍一樣,不僅得有招式還得有內(nèi)功心法。

Linux 的內(nèi)核主要由 5 個(gè)子系統(tǒng)組成:進(jìn)程調(diào)度、內(nèi)存管理、虛擬文件系統(tǒng)、網(wǎng)絡(luò)接口、進(jìn)程間通信。下面將依次講解這 5 個(gè)子系統(tǒng)。

  1. 進(jìn)程調(diào)度 SCHED
  • SCHED_OTHER:分時(shí)調(diào)度策略(默認(rèn)),是用于針對(duì)普通進(jìn)程的時(shí)間片輪轉(zhuǎn)調(diào)度策略。

  • SCHED_FIFO:實(shí)時(shí)調(diào)度策略,是針對(duì)運(yùn)行的實(shí)時(shí)性要求比較高、運(yùn)行時(shí)間短的進(jìn)程調(diào)度策略

  • SCHED_RR:實(shí)時(shí)調(diào)度策略,是針對(duì)實(shí)時(shí)性要求比較高、運(yùn)行時(shí)間比較長(zhǎng)的進(jìn)程調(diào)度策略。

  1. 內(nèi)存管理 MMU
  • 內(nèi)存管理是多個(gè)進(jìn)程間的內(nèi)存共享策略。在 Linux 中,內(nèi)存管理主要說(shuō)的是虛擬內(nèi)存。

  • 虛擬內(nèi)存可以讓進(jìn)程擁有比實(shí)際物理內(nèi)存更大的內(nèi)存,可以是實(shí)際內(nèi)存的很多倍。

  • 每個(gè)進(jìn)程的虛擬內(nèi)存有不同的地址空間,多個(gè)進(jìn)程的虛擬內(nèi)存不會(huì)沖突。

  1. 虛擬文件系統(tǒng) VFS
  • 在 Linux 下支持多種文件系統(tǒng),如 ext、ext2、minix、umsdos、msdos、vfat、ntfs、proc、smb、ncp、iso9660、sysv、hpfs、affs 等。目前 Linux 下最常用的文件格式是 ext2 和 ext3。
  1. 網(wǎng)絡(luò)接口
  • Linux 是在 Internet 飛速發(fā)展的時(shí)期成長(zhǎng)起來(lái)的,所以 Linux 支持多種網(wǎng)絡(luò)接口和協(xié)議。網(wǎng)絡(luò)接口分為網(wǎng)絡(luò)協(xié)議和驅(qū)動(dòng)程序,網(wǎng)絡(luò)協(xié)議是一種網(wǎng)絡(luò)傳輸?shù)耐ㄐ艠?biāo)準(zhǔn),而網(wǎng)絡(luò)驅(qū)動(dòng)則是對(duì)硬件設(shè)備的驅(qū)動(dòng)程序。Linux 支持的網(wǎng)絡(luò)設(shè)備多種多樣,幾乎目前所有網(wǎng)絡(luò)設(shè)備都有驅(qū)動(dòng)程序。
  1. 進(jìn)程間通信
  • Linux 操作系統(tǒng)支持多進(jìn)程,進(jìn)程之間需要進(jìn)行數(shù)據(jù)的交流才能完成控制、協(xié)同工作等功能,Linux 的進(jìn)程間通信是從 UNIX 系統(tǒng)繼承過(guò)來(lái)的。Linux 下的進(jìn)程間的通信方式主要有管道、信號(hào)、消息隊(duì)列、共享內(nèi)存和套接字等方法。

1.3 Linux 目錄結(jié)構(gòu)

在這里插入圖片描述

在 linux 中根目錄的子目錄結(jié)構(gòu)相對(duì)是固定的 (名字固定), 不同的目錄功能是也是固定的
bin: binary, 二進(jìn)制文件目錄,存儲(chǔ)了可執(zhí)行程序,今天要將的命令對(duì)應(yīng)的可執(zhí)行程序都在這個(gè)目錄中
sbin: super binary, root 用戶使用的一些二進(jìn)制可執(zhí)行程序
etc: 配置文件目錄,系統(tǒng)的或者用戶自己安裝的應(yīng)用程序的配置文件都存儲(chǔ)在這個(gè)目錄中
lib: library, 存儲(chǔ)了一些動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù),給系統(tǒng)或者安裝的軟件使用
media: 掛載目錄,掛載外部設(shè)備,比如:光驅(qū),掃描儀
mnt: 臨時(shí)掛載目錄,比如我們可以將 U 盤(pán)臨時(shí)掛載到這個(gè)目錄下
proc: 內(nèi)存使用的一個(gè)映射目錄,給操作系統(tǒng)使用的
tmp: 臨時(shí)目錄,存放臨時(shí)數(shù)據(jù),重啟電腦數(shù)據(jù)就被自動(dòng)刪除了
boot: 存儲(chǔ)了開(kāi)機(jī)相關(guān)的設(shè)置
home: 存儲(chǔ)了普通用戶的家目錄,家目錄名和用戶名相同
root: root 用戶的家目錄
dev: device , 設(shè)備目錄,Linux 中一切皆文件,所有的硬件會(huì)抽象成文件存儲(chǔ)起來(lái),比如:鍵盤(pán), 鼠標(biāo)
lost+found: 一般時(shí)候是空的,電腦異常關(guān)閉 / 崩潰時(shí)用來(lái)存儲(chǔ)這些無(wú)家可歸的文件,用于用戶系統(tǒng)恢復(fù)
opt: 第三方軟件的安裝目錄
var: 存儲(chǔ)了系統(tǒng)使用的一些經(jīng)常會(huì)發(fā)生變化的文件, 比如:日志文件
usr: unix system resource, 系統(tǒng)的資源目錄
/usr/bin: 可執(zhí)行的二進(jìn)制應(yīng)用程序
/usr/games: 游戲目錄
/usr/include: 包含的標(biāo)準(zhǔn)頭文件目錄
/usr/local: 和 opt 目錄作用相同,安裝第三方軟件
環(huán)境變量表:PATH=/bin:/usr/bin:/usr/local/bin:.    PATH 變量包含了一張目錄表(稱(chēng)為路徑前綴),目錄之間用冒號(hào)(:)分隔。

1.4 登錄

1 登錄名

登錄名、加密口令、數(shù)字用戶ID、數(shù)字組、注釋字段、起始目錄、shell程序
dyc:x:205:105:trdycdyc:/home/dyc:/bin/dsh

2.shell

分為兩種: 1、和用戶交互的交互式shell。 2、 文件類(lèi)型的shell腳本

1.5 輸入和輸出

1. 文件描述符

文件描述符(fledescriptor)通常是一個(gè)小的非負(fù)整數(shù),內(nèi)核用以標(biāo)識(shí)一個(gè)特定進(jìn)程正在訪問(wèn)的文件。當(dāng)內(nèi)核打開(kāi)一個(gè)現(xiàn)有文件或創(chuàng)建一個(gè)新文件時(shí),它都返回一個(gè)文件描述符。在讀、寫(xiě)文件時(shí),可以使用這個(gè)文件描述符。

2. 標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤

每當(dāng)運(yùn)行一個(gè)新程序時(shí),所有的shell都為其打開(kāi)3個(gè)文件描述符,標(biāo)準(zhǔn)輸入、輸出、錯(cuò)誤,默認(rèn)情況下這三個(gè)文件描述符都會(huì)鏈接向終端,shell也提供了一種方法(重定向符”>“),是這三個(gè)文件描述符重新定向到某個(gè)文件,當(dāng)重定向的文件不存在時(shí),shell會(huì)創(chuàng)建它。

編寫(xiě)代碼可實(shí)現(xiàn)文件的復(fù)制

./a.out < infile > outfile

3. 不帶緩沖的IO

函數(shù)open、read、write、lseek以及close提供了不帶緩沖的IO

4. 標(biāo)準(zhǔn)I/O

標(biāo)準(zhǔn)IO為那些不帶緩沖的IO提供了一個(gè)帶緩沖的接口,這樣就無(wú)需選擇最佳的緩沖區(qū)大小

1.6 程序和進(jìn)程

1. 程序

程序是一個(gè)存儲(chǔ)在磁盤(pán)上某個(gè)目錄中的可執(zhí)行文件,內(nèi)核使用exec函數(shù)將程序讀入內(nèi)存,并執(zhí)行

2. 進(jìn)程和進(jìn)程ID

程序的執(zhí)行實(shí)例叫進(jìn)程,進(jìn)程ID唯一標(biāo)識(shí)了每個(gè)進(jìn)程

3. 進(jìn)程控制

主要用到三個(gè)函數(shù):fork、exec、waitpid

4. 線程和線程ID

一個(gè)進(jìn)程內(nèi)的所有線程共享同一地址空間、文件描述符、棧、以及與進(jìn)程相關(guān)的屬性

1.7 出錯(cuò)處理

在<errno.h>中定義了errno以及可以賦予它的各種常量,這些常量都是以字符E開(kāi)頭的

errno并不是簡(jiǎn)單的一個(gè)數(shù)據(jù)結(jié)構(gòu),也不是一個(gè)int類(lèi)型的變量,而是一個(gè)宏,下面是其實(shí)現(xiàn)

extern int *__errno_location(void);
#define errno (*__errno_location())

c標(biāo)準(zhǔn)提供了兩個(gè)函數(shù),用于打印出錯(cuò)消息

  • strerror函數(shù)將errnum(即errno)的值映射為一個(gè)出錯(cuò)的消息字符串,并返回該字符串的指針
#include<string.h>

char *strerror(int errnum);

perror函數(shù)基于errno當(dāng)前值,在標(biāo)準(zhǔn)錯(cuò)誤上產(chǎn)生一條出錯(cuò)消息字符串,并返回此字符串的指針

#include<stdio.h>

void perror(const char *msg)

**注意:**只有當(dāng)一個(gè)庫(kù)函數(shù)失敗時(shí),errno才會(huì)被設(shè)置。當(dāng)函數(shù)成功運(yùn)行時(shí),errno的值不會(huì)被修改。這意味著我們不能通過(guò)測(cè)試errno的值來(lái)判斷是否有錯(cuò)誤存在。反之,只有當(dāng)被調(diào)用的函數(shù)提示有錯(cuò)誤發(fā)生時(shí)檢查errno的值才有意義。

1.8 用戶標(biāo)識(shí)

用戶ID、組ID、附屬組ID

1.9 信號(hào)

信號(hào)用于通知進(jìn)程發(fā)生了某種情況,常見(jiàn)的信號(hào)處理方式有以下三種

  1. 忽略信號(hào)
  2. 按系統(tǒng)默認(rèn)方式處理
  3. 提供一個(gè)函數(shù)。即信號(hào)發(fā)生時(shí),調(diào)用該函數(shù),這個(gè)過(guò)程也叫做捕捉該信號(hào)

終端鍵盤(pán)提供了產(chǎn)生兩種信號(hào)的方法:中斷鍵(ctrl + c)和退出鍵(ctrl + \)

另一種時(shí)調(diào)用kill產(chǎn)生信號(hào),例如在一個(gè)進(jìn)程中調(diào)用此函數(shù)就可以向另一個(gè)進(jìn)程發(fā)送信號(hào)

1.10 時(shí)間值

歷史上,共有兩種時(shí)間:

  1. 日歷時(shí)間 :保存在time_t中
  2. 進(jìn)程時(shí)間 :保存在clock_t中

度量一個(gè)進(jìn)程的執(zhí)行時(shí)間,用三個(gè)時(shí)間值:

  • 時(shí)鐘時(shí)間 進(jìn)程運(yùn)行的時(shí)間總量
  • 用戶CPU時(shí)間 執(zhí)行用戶指令的時(shí)間
  • 系統(tǒng)CPU時(shí)間 執(zhí)行系統(tǒng)調(diào)用的時(shí)間

1.11 系統(tǒng)調(diào)用和庫(kù)函數(shù)

二、UNIX標(biāo)準(zhǔn)及實(shí)現(xiàn)

2.1 ISO C

#include<assert.h>                         //驗(yàn)證程序斷言
#include<errno.h>						   //出錯(cuò)碼
#include<stdio.h>                          //標(biāo)準(zhǔn)I/O庫(kù)
#include<stdlib.h>                         //使用函數(shù)
#include<string.h>						   //字符串操作
#include<time.h>                           //時(shí)間和日期
#include<wchar.h>                          //寬字符類(lèi)型
#include<dirent.h>                         //目錄項(xiàng)
#include<fcntl.h>                          //文件控制
#include<netdb.h>						   //網(wǎng)絡(luò)數(shù)據(jù)庫(kù)操作
#include<pthread.h> 					   //線程
#include<unistd.h>						   //符號(hào)常量
#include<arpa/inet.h>	                   //因特網(wǎng)定義
#include<net/if.h> 						   //套接字本地接口
#include<sys/select.h>					   //select函數(shù)
#include<sys/socket.h>					   //套接字接口
#include<sys/stat.h>					   //文件狀態(tài)
#include<sys/types.h>					   //基本系統(tǒng)數(shù)據(jù)類(lèi)型
#include<sys/un.h> 						   //UNIX域套接字定義

2.2 函數(shù)sysconf、pathconf、和fpathconf

#include<unistd.h>
long sysconf(int name);
long pathconf(const char *pathname, int name);
long fpathconf(int fd, int name);

這些函數(shù)用來(lái)修改一些系統(tǒng)配置,例如進(jìn)程最大打開(kāi)文件數(shù)、進(jìn)程最大信號(hào)量等

2.3 ISO C和IEEE POSIX

POSIX是一個(gè)最初由IEEE制定的標(biāo)準(zhǔn)族,指的是可以指操作系統(tǒng)接口(Portable Operating System Interface),該標(biāo)準(zhǔn)以UNIX為基礎(chǔ),但是并不限于UNIX類(lèi)系統(tǒng)。

三、文件I/O

3.1 文件描述符

對(duì)于內(nèi)核而言,所有打開(kāi)的文件都用文件描述符引用。文件描述符是一個(gè)非負(fù)整數(shù)。

按照習(xí)慣,UNIX系統(tǒng)shell把文件描述符0與進(jìn)程的標(biāo)準(zhǔn)輸入關(guān)聯(lián),文件描述符1與進(jìn)程的標(biāo)輸出關(guān)聯(lián),文件描述符2與進(jìn)程的標(biāo)準(zhǔn)錯(cuò)誤關(guān)聯(lián),在喲應(yīng)用程序中,我們應(yīng)該將他們替換為STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO以提高程序可讀性。這些常量定義在<unistd.h>中,文件描述符,最多打開(kāi)63個(gè)。

3.2 函數(shù)open和openat

#include<fcntl.h>
int open(const char *path, int oflag, ...)
int openat(int fd, const char *path, int oflag,...)
													兩個(gè)函數(shù)的返回值:若成功,返回文件描述符;若出錯(cuò),返回-1
  • path參數(shù)是要打開(kāi)或創(chuàng)建文件的名字。
  • oflag參數(shù)可用來(lái)說(shuō)明此函數(shù)的多個(gè)選項(xiàng),該參數(shù)包括:
    • O_RDONLY 只讀打開(kāi)
    • O_WRONLY 只寫(xiě)打開(kāi)
    • O_RDWR 讀、寫(xiě)打開(kāi)
    • O_EXEC 只執(zhí)行打開(kāi)
    • O_SEARCH 只搜索打開(kāi)(應(yīng)用于目錄)
    • O_APPEND 寫(xiě)時(shí)追加到文件的尾端
    • O_CREAT 文件不存在時(shí)創(chuàng)建它,使用此選項(xiàng)還需要指定第三個(gè)參數(shù)mode,來(lái)指定其權(quán)限。
    • O_TRUNC 如果此文件存在,而且為只寫(xiě)或讀-寫(xiě)成功打開(kāi),則其長(zhǎng)度截?cái)酁?。
    • O_NOFOLLOW 若path引用的是一個(gè)符號(hào)鏈接,則出錯(cuò)
  • 如果path指定絕對(duì)路徑,則兩個(gè)函數(shù)完全相同。

由open和openat函數(shù)返回的文件描述符一定是最小的未用的描述符,

3.3 函數(shù)creat

#include<fcntl.h>
int creat(const car *path, mode_t mode)

此函數(shù)可以被open函數(shù)完全替代,這里不作介紹。

3.4 函數(shù)close

#include<ubnnistd.h>
int close(int fd);

當(dāng)一個(gè)進(jìn)程終止時(shí),內(nèi)核自動(dòng)關(guān)閉它所有打開(kāi)的文件,關(guān)閉文件時(shí)還會(huì)釋放該進(jìn)程加上去的所有記錄鎖。

3.5 函數(shù)lseek

每打開(kāi)文件,內(nèi)核都會(huì)維護(hù)一個(gè)與其相關(guān)聯(lián)的“當(dāng)前文件偏移量”,通常讀、寫(xiě)操作都是從當(dāng)前文件偏移量處開(kāi)始,并使偏移量增加所讀寫(xiě)的字節(jié)數(shù)。可以調(diào)用lseek顯示地為一個(gè)文件設(shè)置偏移量。

#include<unistd.h>
off_t lseek(int fd, off_t offset, int whence);
			//返回值:若成功,返回新的文件偏移量,若出錯(cuò),返回-1		

參數(shù)whence:

  • 若whence是SEEK_SET,則將該文件的偏移量設(shè)置為據(jù)文件開(kāi)始處offset個(gè)字節(jié)。
  • 若whence是SEEK_CUR,則將該文件的偏移量設(shè)置為當(dāng)前值加offset個(gè)字節(jié),offset可正可負(fù)。
  • 若whence是SEEK_END,則將該文件的偏移量設(shè)置為文件長(zhǎng)度加offset個(gè)字節(jié)。

通常文件的當(dāng)前偏移量應(yīng)該是一個(gè)非負(fù)整數(shù),但是某些設(shè)備也允許福德偏移量,因此,在比較lseek的返回值時(shí)應(yīng)該謹(jǐn)慎,不要測(cè)試它是否小于0,而要測(cè)試是否等于-1。

文件偏移量可以大于文件的當(dāng)前長(zhǎng)度,在這種情況下,對(duì)該文件的下一次寫(xiě)將加長(zhǎng)該文件,并在文件中構(gòu)成一個(gè)空洞,這一點(diǎn)是允許的,在文件中但沒(méi)有被寫(xiě)過(guò)的字節(jié)都被讀為0. 空洞并不要求在磁盤(pán)上占用存儲(chǔ)區(qū)。

3.6 函數(shù)read/write

#include<unistd.h>
/*不帶緩沖的IO*/
ssoze_t read(int fd, void *buf, size_t nbytes);
ssize_t write(int fd, const void *buf, size_t nbytes);	
			//返回值:讀到的字節(jié)數(shù),若以到文件尾,返回0;若出錯(cuò),返回-1

3.7 I/O的效率

系統(tǒng)CPU時(shí)間的幾個(gè)最小值差不多出現(xiàn)在BUFFSIZE為4096以后,繼續(xù)增加緩沖區(qū)長(zhǎng)度對(duì)長(zhǎng)度幾乎沒(méi)有效應(yīng)影響。

3.8 文件共享

內(nèi)核使用三種數(shù)據(jù)結(jié)構(gòu)表示打開(kāi)的文件,

  1. 每個(gè)進(jìn)程在進(jìn)程表中都有一個(gè)記錄項(xiàng),記錄項(xiàng)中包含一張打開(kāi)的文件描述符表,可將其視為一個(gè)矢量,每個(gè)描述符占用一項(xiàng)。與每個(gè)文件描述符相關(guān)聯(lián)的是:

? a. 文件描述符標(biāo)志(close_on_exec)

? b.指向一個(gè)文件表項(xiàng)的指針。

  1. 內(nèi)核為所有打開(kāi)的文件維持一張文件表,每個(gè)文件表包含:

? a. 文件狀態(tài)標(biāo)志(讀、寫(xiě)、添加、同步和非阻塞等)

? b.當(dāng)前文件偏移量;

? c.指向該文件v結(jié)點(diǎn)表項(xiàng)的指針

  1. 每個(gè)打開(kāi)文件都有一個(gè)v節(jié)點(diǎn)結(jié)構(gòu)。v節(jié)點(diǎn)包含了文件類(lèi)型和對(duì)文件進(jìn)行各種操作函數(shù)的指針。對(duì)于大多數(shù)文件,v節(jié)點(diǎn)還包含了該文件的i節(jié)點(diǎn)。
    在這里插入圖片描述

  2. 對(duì)于多個(gè)打開(kāi)的文件

    • 在每次完成write操作后,當(dāng)前表項(xiàng)的中當(dāng)前文件的文件偏移量即增加的字節(jié)數(shù),如果超出了當(dāng)前文件長(zhǎng)度,則i節(jié)點(diǎn)中的當(dāng)前文件長(zhǎng)度也會(huì)同步更新。

    • lseek函數(shù)只修改文件表項(xiàng)中的當(dāng)前文件偏移量,不進(jìn)行任何I/O操作

3.9 原子操作

當(dāng)多個(gè)進(jìn)程同時(shí)寫(xiě)一個(gè)文件時(shí),邏輯操作“先定位到文件尾端,然后寫(xiě)”的邏輯操作會(huì)使后一個(gè)寫(xiě)的覆蓋掉前一個(gè)寫(xiě)的內(nèi)容,解決問(wèn)題的方法就是將定位到文件尾端+寫(xiě)操作 對(duì)于其他進(jìn)程來(lái)說(shuō)為一個(gè)原子操作。在打開(kāi)文件時(shí)設(shè)置O_APPEND標(biāo)志,在每次寫(xiě)時(shí),內(nèi)核都會(huì)先將當(dāng)前文件偏移量設(shè)置到該文件的尾端,這樣就不用調(diào)用lseek函數(shù)了。

  • 函數(shù)pread和pwrite
#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
											返回值:讀到的字節(jié)數(shù),若已到文件尾,返回0;若出錯(cuò),返回-1
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
返回值:若成功,返回已寫(xiě)的字節(jié)數(shù);若出錯(cuò),返回-1

針對(duì)這兩個(gè)函數(shù),需要注意的是:

  • 調(diào)用pread時(shí),無(wú)法中斷其定位和讀操作
  • 不更新當(dāng)前文件偏移量。(read/write函數(shù)都會(huì)更改當(dāng)前文件偏移量)
  • pwrite也有類(lèi)似的區(qū)別

3.10 函數(shù)dup和dup2

#include<unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);   
			//兩個(gè)函數(shù)的返回值:若成功,返回新的文件描述符;若出錯(cuò),返回-1

? dup和dup2函數(shù)都用來(lái)復(fù)制一個(gè)指定的文件描述符,dup2函數(shù)可以指定返回的文件描述符fd2,如果fd2已經(jīng)打開(kāi),則要想將其關(guān)閉。復(fù)制文件描述符的另一種方法是使用fcntl函數(shù),這個(gè)將在后面介紹。

3.11 函數(shù)sync、fsync和fdatasync

傳統(tǒng)的UNIX系統(tǒng)實(shí)現(xiàn)在內(nèi)核中設(shè)有緩沖區(qū)高速緩存或頁(yè)高速緩存,大多數(shù)磁盤(pán)I/O都通過(guò)緩沖區(qū)進(jìn)行。當(dāng)我們像文件寫(xiě)入數(shù)據(jù)時(shí),通常都先寫(xiě)入高速緩存(為了效率),然后再排隊(duì),晚些再寫(xiě)入磁盤(pán)。這種方式稱(chēng)為延遲寫(xiě)。

內(nèi)核需要重用緩存區(qū)時(shí),他會(huì)把所有延遲寫(xiě) 數(shù)據(jù)塊寫(xiě)入磁盤(pán)。為了保證磁盤(pán)實(shí)際數(shù)據(jù)和緩存區(qū)的內(nèi)容一致性,UNIX系統(tǒng)提供了sync、fsync和fdatasync三個(gè)函數(shù)

#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
																返回值:若成功,返回0;若失敗,返回-1
void sync(void)

sync只是將所有修改過(guò)的塊緩沖區(qū)排入寫(xiě)隊(duì)列,然后就返回,并不等待實(shí)際寫(xiě)磁盤(pán)結(jié)束。

通常,稱(chēng)為update的守護(hù)進(jìn)程周期性的調(diào)用sync函數(shù),這就保證了可以定時(shí)沖洗內(nèi)核的塊緩沖區(qū)。

fsync函數(shù)只對(duì)文件描述符fd指定的一個(gè)文件起作用,并等待寫(xiě)磁盤(pán)結(jié)束才返回,比如數(shù)據(jù)庫(kù)操作需要調(diào)用此函數(shù)。

3.12 函數(shù)fcntl

fcntl函數(shù)可以改變已經(jīng)打開(kāi)文件的屬性

#include<fcntl.h>

int fcntl(int fd, int cmd, .../*int arg*/);
		      //返回值:若成功,則依賴(lài)于cmd;若出錯(cuò),返回-1

fcntl的返回值與命令有關(guān)。如果出錯(cuò),所有命令有返回-1,成功則返回值其他值。

3.13 函數(shù)ioctl

ioctl函數(shù)是I/O操作的雜物箱。

#include<unistd.h>
#include<sys/ioctl.h>

int ioctl(int fd, int request,...);
						//返回值:若出錯(cuò),返回-1;若成功,返回其他值

3.14 /dev/fd

系統(tǒng)都會(huì)提供/dev/fd的目錄,打開(kāi)文件dev/fd/n等效于復(fù)制文件描述符n

例如fd = open("dev/fd/0",等效于fd = dup(0);值得注意的是/dev/fd在linux中是指向底層物理文件的,操作不當(dāng)可能會(huì)造成底層截?cái)唷?/p>

例如,命令filter file2 | cat file1 - file3 | lprfilter file2 | cat file1 /deb/fd/0 file3 | lpr相比,缺少了文件名參數(shù)的一致性。

該命令的解釋?zhuān)?code>cat讀file1,接著讀其標(biāo)準(zhǔn)輸入(也就是filter file2命令的輸出),然后讀file3文件

四、文件和目錄

4.1 函數(shù)stat、fstat、fstatat和lstat

#include<sys/stat.h>

int stat(const char *restrict pathname, struct stat *restrict buf);
int fstat(int, struct stat *buf);
int lstat(const char *restrict pathname, struct stat *restrict buf);
int fstat(int fd, const char *restrict pathname, struct sat *restrict buf, int flag);

4.2 文件類(lèi)型

文件類(lèi)型包括以下幾種:

  1. 普通文件
  2. 目錄文件
  3. 塊特殊文件
  4. 字符特殊文件
  5. FIFO
  6. 套接字
  7. 符號(hào)鏈接

4.3 設(shè)置用戶ID和設(shè)置組ID

4.4 新文件和目錄的所有權(quán)

新文件的用戶ID設(shè)置為進(jìn)程的有效用戶ID。關(guān)于組ID,用戶可以選擇:1. 新文件的組ID可以是進(jìn)程的有效組ID.2.新文件的組ID可以是他所在的目錄的組ID。

4.7 函數(shù)access和faccessat

#include<unistd.h>

int access(const char *pathname, int mode);
int faccessat(int fd, const char *pathname, int mode, int flag);
				//兩個(gè)函數(shù)返回值:若成功,返回0;若出錯(cuò),返回-1

4.8 函數(shù)umask

#include<sys/stat.h> 

mode_t umask(mode_t cmask);
							//返回值:之前的文件模式創(chuàng)建屏蔽字

在這里插入圖片描述

參數(shù)cmask是由圖4-6中列出的9個(gè)常量中的若干位按照“或”構(gòu)成的

在進(jìn)程創(chuàng)建一個(gè)新文件或新目錄時(shí),就一定會(huì)使用文件模式創(chuàng)建屏蔽字,

下面程序創(chuàng)建兩個(gè)文件,創(chuàng)建第一個(gè)時(shí),umask為0.創(chuàng)建第二個(gè)時(shí),umask值禁止所有組合和其他用戶的訪問(wèn)權(quán)限。

#include<unistd.h>
#include<stdio.h>
#include<fcntl.h>

#define RWRWRW (S_IRUSR|S_IWUSR|S_IWGRP|S_IRGRP|S_IROTH|S_IWOTH)

int main(void)
{
    umask(0);
    if(creat("foo",RWRWRW) < 0)
        err_sys("creat error for foo");
    uamsk(S_IWGRP|S_IRGRP|S_IROTH|S_IWOTH);
    if(creat("bar",RWRWRW) < 0)
        err_sys("creat error for bar");
    exit(0);
}

4.9 函數(shù)chmod、fchmod和fchmodat

這三個(gè)函數(shù)是我們可以更改現(xiàn)有文件的訪問(wèn)權(quán)限

#include<sys/stat.h>

int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
int fchmodat(int fd, const char *pathname, mode_t mode, int flag);
							//	成功返回0;出錯(cuò),返回-1

chmod函數(shù)在指定文件上進(jìn)行操作,而fchmod函數(shù)則是對(duì)已經(jīng)打開(kāi)的文件進(jìn)行操作。

4.10 函數(shù)chown、fchown、fchownat和lchown

下面這幾個(gè)chown函數(shù)可用于更改文件的用戶ID和組ID。

#include <unistd.h>
int chown (const char *pathname, uid_t owner, gid_t group) ;
int fchown(int fd, uid_t owner, gid_t group) ;
int fchownat(int fd, const char *pathname, uid_t owner, gid_t group, int flag) ;
int lchown (const char *pathname, uid_t owner, gid_t group) ;
			//4個(gè)函數(shù)的返回值:成功,返回0;失敗,返回-1

4.11 文件長(zhǎng)度

stat結(jié)構(gòu)成員st_size表示億字節(jié)為單位的文件長(zhǎng)度。此字段只對(duì)普通文件、目錄文件和符號(hào)鏈接有意義。

  • 對(duì)于普通文件,得到的是文件的實(shí)際長(zhǎng)度
  • 對(duì)于目錄文件,得到的是一個(gè)數(shù)(16或者512)的整數(shù)倍.
  • 對(duì)于符號(hào)鏈接,文件長(zhǎng)度是文件名中的實(shí)際字節(jié)數(shù)

當(dāng)文件中存在空洞時(shí),實(shí)際長(zhǎng)度可能和ls -l所得到的不相同,這是正?,F(xiàn)象,當(dāng)復(fù)制一個(gè)文件時(shí),所有的空洞都會(huì)按實(shí)際字節(jié)被填充為0

4.12 文件截?cái)?/h2>

有時(shí)我們需要在文件尾端出截取一些數(shù)據(jù)以縮短數(shù)據(jù)。將一個(gè)文件的長(zhǎng)度截?cái)酁?是一個(gè)特例,再打開(kāi)文件使用O_TRUNC標(biāo)志可以做到這一點(diǎn),為了截?cái)辔募梢哉{(diào)用函數(shù)truncate和ftruncate。

#include<unistd.h>

int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);
				//兩個(gè)函數(shù)返回值:若成功,返回0;若出錯(cuò),返回-1

4.13 文件系統(tǒng)

磁盤(pán)、分區(qū)和文件系統(tǒng)

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-uNmKp1dS-1668346492367)(D:\file\課程\md\image-20221106172800189.png)]

如果更仔細(xì)的觀察一個(gè)柱面組的i節(jié)點(diǎn)和數(shù)據(jù)塊部分,則可以看出如下圖所示情況。

在這里插入圖片描述

  • 在圖中有兩個(gè)目錄項(xiàng)指向同一個(gè)i節(jié)點(diǎn)。每個(gè)i節(jié)點(diǎn)都有一個(gè)鏈接計(jì)數(shù),其值時(shí)指向該i節(jié)點(diǎn)的目錄項(xiàng)數(shù)。只有當(dāng)鏈接計(jì)數(shù)減至0時(shí),才可以刪除文件。這也是為什么刪除一個(gè)目錄項(xiàng)的函數(shù)稱(chēng)之為unlink的而不是delete的原因,該鏈接稱(chēng)為硬鏈接。
  • 另外一種鏈接類(lèi)型稱(chēng)為符號(hào)鏈接。任何一個(gè)葉目錄的鏈接計(jì)數(shù)總是2,數(shù)值2來(lái)源于命名該目錄的目錄項(xiàng)..以及在該目錄中的.項(xiàng)。如果文件有一個(gè)子目錄,則數(shù)值應(yīng)該為3,另一個(gè)為其子目錄中的..。注意,每個(gè)子目錄又會(huì)使父目錄的引用計(jì)數(shù)增加1。

4.14 函數(shù)link、linkat、unlink、unlinkat和remove

#include<unistd.h>
int link(const char *existingpath, const char *newpath);
int linkat(int efd, const char *existingpath, int nfd, const char *newpath, int flag);
				//兩個(gè)函數(shù)的返回值:若成功,返回0;若出錯(cuò),返回-1

這兩個(gè)函數(shù)都用來(lái)創(chuàng)建一個(gè)新目錄想newpath,他引用現(xiàn)有文件existingpath。

為了刪除一個(gè)現(xiàn)有的目錄項(xiàng),可以調(diào)用unlink函數(shù)

#include<unistd.h>
int link(const char *pathname);
int linkat(int efd, const char *pathname, int flag);
			//兩個(gè)函數(shù)的返回值:若成功,返回0;若出錯(cuò),返回-1

unlink的這種特性經(jīng)常被程序用來(lái)確保即使在程序崩潰時(shí),她所創(chuàng)建的臨時(shí)文件也不會(huì)遺留下來(lái)。進(jìn)程用open或creat創(chuàng)建一個(gè)文件,然后立即調(diào)用unlink,引文該文件仍然是打開(kāi)的,所以不會(huì)將其刪除,只有當(dāng)進(jìn)程終止時(shí)(內(nèi)核會(huì)關(guān)閉打開(kāi)的所有文件描述符),該文件才會(huì)刪除。

#include<stdio.h>

int remove(const char *pathname);
							//返回值:若成功,返回0;出錯(cuò),返回-1

4.15 函數(shù)rename和renameat

文件或者目錄可以用rename函數(shù)或者renameat函數(shù)重命名

#include<stdio.h>
int rename(const char *pathname);
int renameat(int oldfd, const char *oldname, int newflag,const char *newname);
					//兩個(gè)函數(shù)的返回值:若成功,返回0;若出錯(cuò),返回-1

4.16 符號(hào)鏈接

4.17 創(chuàng)建和讀取符號(hào)鏈接

4.18 文件時(shí)間

4.19 函數(shù)futimens、utimensat和utimes

4.20 函數(shù)mkdir、mkdirat和rmdir

用mkdir和mkdirat函數(shù)創(chuàng)建目錄,用rmdir函數(shù)刪除目錄

#include<sys/stat.h>
int mkdir(const char *pathname, mode_t mode);
int mkdirat(int fd, const char *pathname, mode_t mode);
					//	兩個(gè)函數(shù)的返回值:若成功,返回0;若出錯(cuò),返回-1

兩個(gè)函數(shù)創(chuàng)建一個(gè)新的空目錄。其中...自動(dòng)創(chuàng)建。訪問(wèn)權(quán)限由mode指定,對(duì)于牡蠣,通常至少要設(shè)置一個(gè)執(zhí)行權(quán)限位,以允許訪問(wèn)該目錄中的文件。

#include<unistd.h>
int rmdir(const char *pathname);
						//函數(shù)的返回值:若成功,返回0;若出錯(cuò),返回-1

4.21 讀目錄

4.22 函數(shù)chdir、fchdir、getcwd

4.23 設(shè)備特殊文件

st_dev和st__rdev這兩個(gè)字段經(jīng)常引起混淆。

五、標(biāo)準(zhǔn)I/O庫(kù)

5.1 流和FILE對(duì)象

在第三章,所有I/O函數(shù)都圍繞文件描述符的,而對(duì)于標(biāo)準(zhǔn)I/O庫(kù),他們操作都是圍繞進(jìn)行的,當(dāng)標(biāo)準(zhǔn)I/O庫(kù)打開(kāi)或創(chuàng)建一個(gè)文件時(shí),我們已使一個(gè)流與一個(gè)文件相關(guān)聯(lián)。

我們稱(chēng)指向FILE對(duì)象的指針(FILE *)為文件指針。

這里有必要科普一下流和文件描述符的區(qū)別:

  • 任何操作系統(tǒng),在程序訪問(wèn)(讀寫(xiě))時(shí),都需要建立程序與文件之間的通道,這一過(guò)程稱(chēng)之為打開(kāi)文件。UNIX提供了兩種機(jī)制,分別為:(1)文件描述符。(2)流。

  • 兩者的相同點(diǎn):

    • 都是作為程序與文件之間的通道。
    • 都包含了一大類(lèi)的I/O庫(kù)函數(shù)
  • 兩者不同點(diǎn):

    • 文件描述符使用int類(lèi)型的變量來(lái)表示打開(kāi)的文件,而流使用FILE*文件指針來(lái)進(jìn)行標(biāo)識(shí)
    • 如果需要對(duì)特定設(shè)備進(jìn)行控制操作,必須使用文件描述符。
    • 如果需要使用特殊的方式進(jìn)行I/O操作(例如非阻塞等),必須使用文件描述符的方式。
    • 在執(zhí)行實(shí)際的輸入輸出時(shí),流提供操作接口更加靈活、強(qiáng)大。
    • 文件描述符只提供簡(jiǎn)答的穿過(guò)送字符塊的函數(shù),而流函數(shù)提供格式化I/O,字符I/O,面向行的I/O的大量函數(shù)。
    • 流函數(shù)更利于程序的移植,任何基于ANSI C的系統(tǒng)都支持流。
  • 兩者的關(guān)系:

    流為用戶提供了一些更高一級(jí)的I/O接口,它處在文件描述符的上層,也就是說(shuō)流是通過(guò)文件描述符來(lái)實(shí)現(xiàn)的。

5.2 標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤

對(duì)一個(gè)進(jìn)程預(yù)定義了3個(gè)流,并且這3個(gè)流可以自動(dòng)被進(jìn)程使用。這3個(gè)標(biāo)準(zhǔn)I/O流通過(guò)預(yù)定義文件指針stdin、stdout和stderr加以引用,這三個(gè)文件指針定義在頭文件<stdio.h>中。

5.3 緩沖

標(biāo)準(zhǔn)I/O庫(kù)提供緩沖的目的是盡可能減少使用read和write調(diào)用的次數(shù)。他也對(duì)每個(gè)I/O流自動(dòng)地進(jìn)行緩沖管理,從而避免不必要的麻煩。遺憾的是,標(biāo)準(zhǔn)I/O庫(kù)最令人迷惑的也是他的緩沖。

標(biāo)準(zhǔn)I/O提供了以下3種類(lèi)型的緩沖。

  • 全緩沖。在填滿標(biāo)準(zhǔn)I/O緩沖區(qū)后才進(jìn)行實(shí)際I/O操作。對(duì)于駐留在磁盤(pán)上的文件通常是由I/O庫(kù)實(shí)施全緩沖的。術(shù)語(yǔ)沖洗說(shuō)明標(biāo)準(zhǔn)I/O緩沖區(qū)的寫(xiě)操作,緩沖區(qū)也可由標(biāo)準(zhǔn)i/o例程子自動(dòng)沖洗,或者可以調(diào)用函數(shù)fflush沖洗一個(gè)流。
  • 行緩沖。這種情況下,當(dāng)輸入和輸出中遇到換行符時(shí),才會(huì)執(zhí)行I/O操作。這允許我們一次輸入一個(gè)字符(啊函數(shù)fputc),但只有在寫(xiě)了一行之后,才會(huì)進(jìn)行實(shí)際的I/O操作。
  • 不帶緩沖。標(biāo)準(zhǔn)I/O庫(kù)不對(duì)字符進(jìn)行緩沖存儲(chǔ)。

5.4 打開(kāi)流

#include<stdio.h>
FILE *fopen(const char *restrict pathname, const char *restrict type);
FILE *freopen(const char *restrict pathname, const char *restrict type, FILE *restrict fp);
FILE *fdopen(in//三個(gè)函數(shù)的返回值:若成功,則返回0;若出錯(cuò),則返回-1

調(diào)用fclose函數(shù)關(guān)閉流

#include<stdio.h>

int fclose(FILE *fp);
					//返回值:若成功,則返回0;若出錯(cuò),則返回-1

再關(guān)閉文件之前,沖洗緩沖中的輸出數(shù)據(jù)。緩沖區(qū)中的任何輸入數(shù)據(jù)則被丟棄。如果標(biāo)準(zhǔn)I/O庫(kù)以為該流自動(dòng)分配了一個(gè)緩沖區(qū),則釋放此緩沖區(qū)。當(dāng)一個(gè)程序正常終止時(shí),所有的流都會(huì)被正確關(guān)閉。

5.5 讀和寫(xiě)流

  1. 輸入函數(shù)
#include<stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
								//若成功,返回c;若出錯(cuò),返回EOF
  1. 輸出函數(shù)
#include<stdio.h>
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);
					       	//若成功,返回c;若出錯(cuò),返回EOF

5.6 每次一行I/O

#include<stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);
		    //若成功,返回buf;若已到達(dá)文件尾端或者出錯(cuò),返回NULL

gets函數(shù)從標(biāo)準(zhǔn)輸入讀,而fgets函數(shù)從指定的流讀。gets有些不安全,是不建議使用的

#include<stdio.h>
int *fputs(char *restrict str, FILE *restrict fp);
int *puts(char *str);
				//若成功,返回buf;若已到達(dá)文件尾端或者出錯(cuò),返回NULL

5.7 二進(jìn)制I/O

5.6和5.7節(jié)中的函數(shù),因?yàn)楦髯缘奶攸c(diǎn)均不適合讀寫(xiě)二進(jìn)制I/O函數(shù),因此特地提供以下兩個(gè)函數(shù)進(jìn)行二進(jìn)制I/O

#include<stdio.h>

size_t fread(void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp);
size_t fwrite(const void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp);
							           //返回值:讀寫(xiě)的對(duì)象數(shù)

此函數(shù)常見(jiàn)的兩種用法。

  1. 讀或者寫(xiě)一個(gè)二進(jìn)制數(shù)組。下面例程將數(shù)組中2~5個(gè)元素寫(xiě)至一文件中。
float data[10];
if(fwrite(&data[2], sizeof(float), 4, fp) != 4)
	perror("fwrite error");
  1. 讀寫(xiě)一個(gè)結(jié)構(gòu)。
struct {
	short count;
	long total;
	char name[NAMESIZE];
}item;
if(fwrite(&item, sizeof(item), 1, fp) != 1)
    perror("fwrite error");

使用二進(jìn)制I/O的基本問(wèn)題是,它只能用于都在同一系統(tǒng)上已寫(xiě)的數(shù)據(jù)。具體表現(xiàn)為,在一個(gè)系統(tǒng)上寫(xiě)的數(shù)據(jù),在另一個(gè)系統(tǒng)上進(jìn)行處理,此時(shí)會(huì)出問(wèn)題,不能正常工作,其原因包括:

  1. 在一個(gè)結(jié)構(gòu)中,同一個(gè)成員的偏移量可以隨編譯程序和系統(tǒng)的不同而不同(由于不同的對(duì)要求)
  2. 用來(lái)存儲(chǔ)多字節(jié)整數(shù)和浮點(diǎn)值的二進(jìn)制格式在不同的系統(tǒng)結(jié)構(gòu)間也可能不同。

5.8 定位流

5.9 格式化I/O

  1. 格式化輸出

格式化輸出由5個(gè)printf函數(shù)來(lái)處理的。

#include<stdio.h>

int printf(const char *restrict format, ...);
int fprintf(FILE *restrict fd, const char *restrict format, ...);
int dprintf(int fd, const char *restrict format, ...);
									//3個(gè)函數(shù)的返回值:成功,返回輸出字符數(shù),若輸出錯(cuò)誤,返回負(fù)值
int sprintf(char *restrict buf, const char *restrict format, ...);
								   //返回值:成功,返回存入數(shù)組的字符數(shù),若編碼錯(cuò)誤,返回負(fù)值
int snprintf(char *restrict buf, size_t n, const char *restrict format, ...);
								   //返回值:若緩沖區(qū)足夠大,返回將要存入數(shù)組的字符數(shù),若編碼錯(cuò)誤,返回負(fù)值

printf函數(shù)將格式化數(shù)據(jù)寫(xiě)到標(biāo)準(zhǔn)輸出,fprintf寫(xiě)至指定的流,dprintf寫(xiě)至指定的文件描述符,sprintf將格式化的數(shù)據(jù)寫(xiě)入數(shù)組,同時(shí)在該數(shù)組的尾端自動(dòng)加一個(gè)null字節(jié),但該字符不包括到返回值中。snprintf函數(shù)顯式的提供了緩沖區(qū)的長(zhǎng)度,以防止緩沖區(qū)溢出的隱患,但是此函數(shù)不會(huì)再數(shù)組最后加NULL字節(jié)。

格式說(shuō)明控制其余參數(shù)如何編寫(xiě),以后如何顯示,轉(zhuǎn)換說(shuō)明是以%開(kāi)始的。一個(gè)轉(zhuǎn)換說(shuō)明有4個(gè)可選擇的部分。具體說(shuō)明和示例見(jiàn)來(lái)鏈接:https://blog.csdn.net/weixin_44567318/article/details/115441167。

  1. 格式化輸入
#include<stdio.h>

int scanf(const char *restrict format, ...);
int fscanf(FILE *restrict fp, const char *restrict format, ...);
int sscanf(const char *restrict buf, const char  *restrict format, ...);

5.10 實(shí)現(xiàn)細(xì)節(jié)

#include<stdio.h>

int fileno(FILE *fp);
										//返回值:與文件相關(guān)聯(lián)的文件描述符

5.11 臨時(shí)文件

5.12 內(nèi)存流

5.13 標(biāo)準(zhǔn)I/O的替代軟件

六、系統(tǒng)數(shù)據(jù)文件和信息

6.1 口令文件

6.2 陰影文件

6.3 組文件

6.4 附屬組ID

6.5 實(shí)現(xiàn)區(qū)別

6.6 其他數(shù)據(jù)文件

6.7 登陸賬戶記錄

6.8 系統(tǒng)標(biāo)識(shí)

6.9 時(shí)間和日期例程

有UNIX提供的基本時(shí)間服務(wù)是自1970年00:00:00這一特定時(shí)間以來(lái)提供的秒數(shù)。

time函數(shù)返回當(dāng)前的時(shí)間和日期

#include<time.h>
time_t time(time_t *calptr);
//返回值:成功,返回時(shí)間數(shù),若參數(shù)列表非空,返回值也會(huì)保存到參數(shù)列表的指針中;失敗,返回-1

兩個(gè)函數(shù)localtime和gmtime將日歷時(shí)間轉(zhuǎn)換為分解后的時(shí)間,并將其存放在一個(gè)tm的時(shí)間結(jié)構(gòu)中。

struct tm{
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year
/**還包括夏令時(shí)間**/
...
};
#include<time.h>
struct tm *gmtime(const time_t *calptr);
struct tm *localtime(const time_t *calptr);

七、進(jìn)程環(huán)境

7.1 main函數(shù)

C程序總是從main函數(shù)開(kāi)始的。main函數(shù)的原型是:

int main(int argc, char *argv[]);

argc 是命令行的參數(shù),argv是指向參數(shù)的哥哥指針多構(gòu)成的數(shù)組。

當(dāng)內(nèi)核執(zhí)行一個(gè)C程序時(shí)(使用exec函數(shù)),調(diào)用main前先調(diào)用一個(gè)特殊的啟動(dòng)例程??蓤?zhí)行文件將此啟動(dòng)例程指定為程序的起始地址。啟動(dòng)例程從內(nèi)核取得命令行參數(shù)和環(huán)境變量值,然后為按照上述方式調(diào)用main函數(shù)做好安排。

7.2 進(jìn)程終止

有8種方式是進(jìn)程終止,其中5種為正常終止,他們是:

  • 從main返回、調(diào)用exit、調(diào)用 _exit 或 _Exit、最后一個(gè)線程從啟動(dòng)例程中返回、從最后一個(gè)線程調(diào)用pthread_exit。

異常終止方式包括三種:

  • 調(diào)用abort、接到一個(gè)信號(hào)、最后一個(gè)線程對(duì)取消請(qǐng)求做出響應(yīng)。

啟動(dòng)例程是這樣編寫(xiě)的:使得從main函數(shù)返回后立即調(diào)用exit函數(shù)。該例程通常使用匯編語(yǔ)言編寫(xiě)的

  1. 退出函數(shù)

    3個(gè)函數(shù)用于正常終止一個(gè)程序:_exit和 _Exit立即進(jìn)入內(nèi)核,exit則先執(zhí)行一些清理處理,然后返回內(nèi)核。

    #include<stdlib.h>
    void exit(int status);
    void _Exit(int status);
    #include<uistd.h>
    void _exit(int status);
    

    exit函數(shù)總是會(huì)先執(zhí)行一個(gè)標(biāo)準(zhǔn)I/O庫(kù)的清理關(guān)閉操作。

    3個(gè)退出函數(shù)都會(huì)帶一個(gè)整形參數(shù),稱(chēng)為終止?fàn)顟B(tài)。大多數(shù)UNIX系統(tǒng)都提供了檢查進(jìn)程終止?fàn)顟B(tài)的方法。

  2. 函數(shù)atexit

按照ISO C一個(gè)程序最多可以登記32個(gè)函數(shù),這些函數(shù)有exit自動(dòng)調(diào)用,我們稱(chēng)這些函數(shù)為終止處理函數(shù)。并調(diào)用atexit來(lái)登記這些函數(shù)。

#include<stdlib.h>
int atexit(void (*func)(void));
						//返回值:成功,返回0,若出錯(cuò),返回-1

其中,atexit函數(shù)的參數(shù)是一個(gè)函數(shù)地址,exit函數(shù)調(diào)用這些函數(shù)的順序預(yù)登記時(shí)順序相反,同意函數(shù)如果登記多次,那么也會(huì)調(diào)用多次。

根據(jù)ISO C和POSIX.1,exit首先誰(shuí)調(diào)用各種終止處理程序,然后關(guān)閉所有打開(kāi)流,此外若程序調(diào)用exec函數(shù)族,則將清除所有已安裝的終止處理函數(shù)。

注意,內(nèi)核使程序執(zhí)行的唯一方法是調(diào)用exec函數(shù)。進(jìn)程終止的唯一方法是顯式或隱式的調(diào)用_exit或 _Exi。進(jìn)程也可以通過(guò)信號(hào)的方式非自愿終止。

7.3 命令行參數(shù)

當(dāng)執(zhí)行一個(gè)程序時(shí),調(diào)用exec的進(jìn)程可將命令行參數(shù)傳遞給新程序。

ISO C和POSIC.1 都要求argv[argc]是一個(gè)空指針。這就使得我們可以將參數(shù)處理循環(huán)寫(xiě)為:

for(i = 0; argv[i] != NULL; i++)

7.4 環(huán)境表

每個(gè)程序都接收到一張環(huán)境表,環(huán)境表也是一個(gè)字符指針數(shù)組,全局變量environ則包含了該指針數(shù)組的地址:extern char **environ;每個(gè)指針包含了一個(gè)以null結(jié)尾的C字符串的地址。
在這里插入圖片描述
通常用getenv和putenv來(lái)訪問(wèn)指定的環(huán)境變量。

7.5 C程序的存儲(chǔ)空間布局

C程序一直由以下幾部分組成:

  • 正文段。這是由CPU執(zhí)行的機(jī)器指令部分。通常正文段是可共享的,所以即使是頻繁執(zhí)行的程序(如文本編輯器、C編譯器和shell等)在存儲(chǔ)其中也只需要一個(gè)副本,另外正文段常常是只讀。
  • 初始化數(shù)據(jù)段。通常將此段稱(chēng)為數(shù)據(jù)段(data段),他包含了程序中需明確地賦初值的變量。
  • 未初始化數(shù)據(jù)段。通常將此段稱(chēng)為bss段,在程序開(kāi)始 執(zhí)行前,內(nèi)核將此段中的數(shù)據(jù)初始化為0或者空指針。
  • 棧。自動(dòng)變量以及每次函數(shù)調(diào)用時(shí)所保存的信息都存放在此段中。
  • 堆。通常在堆中進(jìn)行動(dòng)態(tài)存儲(chǔ)分配。
  • 命令行參數(shù)和環(huán)境變量

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-3oRsQuIS-1668346492369)(D:\file\課程\md\3.png)]

size命令報(bào)告了正文段、數(shù)據(jù)段和bss段的長(zhǎng)度(以字節(jié)為短)。

這里需要補(bǔ)充幾點(diǎn):

  • 程序是由進(jìn)程使用exec函數(shù)執(zhí)行的,而進(jìn)程號(hào)是由內(nèi)核指定的。C程序在編譯過(guò)后,就會(huì)將正文段(txt段)和初始化的數(shù)據(jù)data段的數(shù)據(jù)寫(xiě)入到可執(zhí)行文件中,有進(jìn)程加載磁盤(pán)來(lái)讀取這些數(shù)據(jù)。
  • bss和data段的區(qū)別:
    • bss段存放全局變量和靜態(tài)變量(聲明但未初始化),具體表現(xiàn)為一個(gè)占位符;data段存放:全局變量、靜態(tài)變量(聲明且初始化)、常量。
    • bss段不占用磁盤(pán)的存儲(chǔ)空間,其內(nèi)容由操作系統(tǒng)初始化(清零)。而data段則占用存儲(chǔ)空間。
    • bss段和data段在c語(yǔ)言程序內(nèi)存分區(qū)模型中對(duì)應(yīng)為全局/靜態(tài)變量區(qū),事實(shí)上常量也存放在data區(qū)。
    • 進(jìn)程中堆棧的大?。簂inux中棧區(qū)默認(rèn)為10M,而在Windows中默認(rèn)為1M,默認(rèn)一個(gè)線程的棧大小為1M;linux中堆區(qū)默認(rèn)大小為3G(假設(shè)內(nèi)存共4G),而在windows中為2G,高位空間留給內(nèi)核。
    • 如果函數(shù)中存在未釋放的內(nèi)存,則可能會(huì)使得進(jìn)程分配的堆區(qū)空間不斷增大,最終導(dǎo)致過(guò)度得換頁(yè)開(kāi)銷(xiāo),造成性能下降甚至程序崩潰。

7.6 共享庫(kù)

共享庫(kù)使得可執(zhí)行文件不再需要包含公共的庫(kù)函數(shù),而只需要在所有進(jìn)程都可引用的存儲(chǔ)區(qū)中保持這種庫(kù)例程的一個(gè)副本。程序第一次執(zhí)行或者調(diào)用某個(gè)庫(kù)函數(shù)時(shí),用動(dòng)態(tài)鏈接方法將程序與共享 庫(kù)函數(shù)相鏈接。這減少每個(gè)可執(zhí)行文件的長(zhǎng)度,但增加了一些運(yùn)行時(shí)間開(kāi)銷(xiāo)。例如編譯如下程序:

$ gcc -static hello1.c 阻止gcc使用共享庫(kù)

a.out -> 979443

$ gcc hello1.c gcc使用共享庫(kù)

a.out -> 8378

可以看出,使用共享庫(kù)編譯此程序,可執(zhí)行文件的正文和數(shù)據(jù)段的長(zhǎng)度都顯著減??;

7.7 存儲(chǔ)空間分配

ISO C說(shuō)明了三個(gè)用于存儲(chǔ)空間動(dòng)態(tài)分配的函數(shù)

  1. malloc,分配指定字節(jié)數(shù)的存儲(chǔ)區(qū),此存儲(chǔ)區(qū)中的初始值不缺定。
  2. calloc,為指定數(shù)量指定長(zhǎng)度的對(duì)象分配存儲(chǔ)空間,每一位bit都初始化為0.
  3. realloc,增加或者減少以前分配區(qū)的長(zhǎng)度
#include<stdlib.h>
void *malloc(size_t size);
void *calloc(size_t nobj, size_t size);
void *realloc(void *ptr, size_t newsize);
										//成功返回非空指針,若出錯(cuò),返回NULL
void free(void *ptr);

因?yàn)檫@三個(gè)alloc函數(shù)都返回void*, 所以若果程序中包含了#include<stdlib.h>,那么當(dāng)我們i將這些返回賦予一些其他類(lèi)型時(shí),就不需要顯式的強(qiáng)制類(lèi)型轉(zhuǎn)換了。

大多數(shù)實(shí)現(xiàn)所分配的存儲(chǔ)空間比所要求的要稍大一些,額外的空間用來(lái)記錄管理信息——分配塊的長(zhǎng)度、指向下一個(gè)分配塊的指針等,這就意味著,如果超過(guò)一個(gè)已分配區(qū)的尾端或者起始位置首端,則會(huì)造成在災(zāi)難性問(wèn)題。

其他可能產(chǎn)生致命性錯(cuò)誤的是:釋放一個(gè)已經(jīng)釋放的塊;調(diào)用free時(shí)使用的指針不是alloc函數(shù)返回值等。弱國(guó)一個(gè)進(jìn)程調(diào)用malloc函數(shù),但是忘了調(diào)用free函數(shù),那么該進(jìn)程占用的存儲(chǔ)空間就會(huì)連續(xù)增大,這被稱(chēng)為泄露。

7.8 環(huán)境變量

ISO C定義使用getenc函數(shù)來(lái)獲取環(huán)境變量得值,注意,此函數(shù)返回一個(gè)指針,指向name=value中得value,而且獲取得是一個(gè)副本,而不是直接訪問(wèn)environ的。

#include<stdlib.h>
char *getenv(const char *name);
								//返回值:指向name對(duì)應(yīng)value的指針,若未找到,返回NULL

此外,還可以修改環(huán)境變量的值:

#include<stdlib.h>
int putenv(char *str);
int setenv(const char *name, const char *value, int rewrite);
int unsetenv(const char *name);

7.9 函數(shù)setjmo和longjmp

在C中,goto語(yǔ)句不能跨越函數(shù)的,而執(zhí)行這種跳轉(zhuǎn)功能的函數(shù)時(shí)setjmp和longjmp。

#include<setjmp.h>
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);

7.10 函數(shù)getrlimit和setrlimit

每個(gè)進(jìn)程都有一組資源限制,其中一些可用以下兩個(gè)函數(shù)執(zhí)行

#include<sys/resource.h>
int getgetrlimit(int resource, struct rlimit *rlptr);
int setrlimit(int resource, const struct rlimit *rlptr);

八、進(jìn)程控制

8.2 進(jìn)程標(biāo)識(shí)

原文鏈接:https://blog.csdn.net/qq_55537010/article/details/127837953

欄目分類(lèi)
最近更新