偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

深入Linux系統(tǒng)調(diào)用:原理、機制與實戰(zhàn)全解析

系統(tǒng) Linux
從本質(zhì)上看,系統(tǒng)調(diào)用是內(nèi)核提供的接口,常以 C 函數(shù)形式呈現(xiàn),方便開發(fā)者調(diào)用系統(tǒng)功能。在 Linux 系統(tǒng)里,它構(gòu)建了用戶程序安全訪問硬件資源的通道。

在 Linux 操作系統(tǒng)中,系統(tǒng)調(diào)用是連接用戶程序與內(nèi)核的關(guān)鍵橋梁。當(dāng)我們在終端輸入命令,或是運行各類應(yīng)用程序時,背后都離不開系統(tǒng)調(diào)用的支撐。它不僅是內(nèi)核為用戶空間提供服務(wù)的核心接口,更是理解 Linux 運行機制的重要鑰匙。若將操作系統(tǒng)比作精密的工廠,內(nèi)核便是掌控全局的中樞,而系統(tǒng)調(diào)用則是連接生產(chǎn)線(用戶程序)與中樞的通道。用戶程序通過它向內(nèi)核請求服務(wù),無論是文件操作、進程管理,還是內(nèi)存分配等任務(wù),系統(tǒng)調(diào)用都發(fā)揮著不可或缺的作用。

從本質(zhì)上看,系統(tǒng)調(diào)用是內(nèi)核提供的接口,常以 C 函數(shù)形式呈現(xiàn),方便開發(fā)者調(diào)用系統(tǒng)功能。在 Linux 系統(tǒng)里,它構(gòu)建了用戶程序安全訪問硬件資源的通道。由于硬件資源由內(nèi)核底層操作,若用戶程序直接訪問,易引發(fā)系統(tǒng)不穩(wěn)定甚至崩潰,而系統(tǒng)調(diào)用就像門衛(wèi),保障了系統(tǒng)的安全與穩(wěn)定。在多任務(wù)管理方面,系統(tǒng)調(diào)用如同工廠調(diào)度員。內(nèi)核借助它合理調(diào)度 CPU 時間,讓多個進程協(xié)同工作,顯著提升系統(tǒng)并發(fā)處理能力,確保系統(tǒng)流暢運行。

對開發(fā)者和系統(tǒng)管理員而言,掌握系統(tǒng)調(diào)用至關(guān)重要。開發(fā)者編寫應(yīng)用程序時,可通過 open、read 等系統(tǒng)調(diào)用實現(xiàn)文件操作,用 fork、exec 進行進程管理;系統(tǒng)管理員則能借助 sysinfo 獲取系統(tǒng)性能信息,使用 ioctl 配置管理設(shè)備。掌握系統(tǒng)調(diào)用,就能更好地與 Linux 交互,釋放系統(tǒng)潛能。接下來,就讓我們一起深入探索 Linux 系統(tǒng)調(diào)用的奇妙世界,層層揭開它神秘的面紗,探尋它的原理與實現(xiàn)機制,看看這個強大的工具是如何在幕后掌控整個操作系統(tǒng)的運行,為我們的日常操作和開發(fā)工作保駕護航的。

一、Linux 系統(tǒng)調(diào)用是什么?

1.1 系統(tǒng)調(diào)用的定義

系統(tǒng)調(diào)用,顧名思義,說的是操作系統(tǒng)提供給用戶程序調(diào)用的一組“特殊”接口。用戶程序可以通過這組“特殊”接口來獲得操作系統(tǒng)內(nèi)核提供的服務(wù),比如用戶可以通過文件系統(tǒng)相關(guān)的調(diào)用請求系統(tǒng)打開文件、關(guān)閉文件或讀寫文件,可以通過時鐘相關(guān)的系統(tǒng)調(diào)用獲得系統(tǒng)時間或設(shè)置定時器等。

從邏輯上來說,系統(tǒng)調(diào)用可被看成是一個內(nèi)核與用戶空間程序交互的接口——它好比一個中間人,把用戶進程的請求傳達給內(nèi)核,待內(nèi)核把請求處理完畢后再將處理結(jié)果送回給用戶空間。系統(tǒng)服務(wù)之所以需要通過系統(tǒng)調(diào)用來提供給用戶空間的根本原因是為了對系統(tǒng)進行“保護”,因為我們知道Linux的運行空間分為內(nèi)核空間與用戶空間,它們各自運行在不同的級別中,邏輯上相互隔離。所以用戶進程在通常情況下不允許訪問內(nèi)核數(shù)據(jù),也無法使用內(nèi)核函數(shù),它們只能在用戶空間操作用戶數(shù)據(jù),調(diào)用用戶空間函數(shù)。

比如我們熟悉的“hello world”程序(執(zhí)行時)就是標準的用戶空間進程,它使用的打印函數(shù)printf就屬于用戶空間函數(shù),打印的字符“hello word”字符串也屬于用戶空間數(shù)據(jù)。但是很多情況下,用戶進程需要獲得系統(tǒng)服務(wù)(調(diào)用系統(tǒng)程序),這時就必須利用系統(tǒng)提供給用戶的“特殊接口”——系統(tǒng)調(diào)用了,它的特殊性主要在于規(guī)定了用戶進程進入內(nèi)核的具體位置;換句話說,用戶訪問內(nèi)核的路徑是事先規(guī)定好的,只能從規(guī)定位置進入內(nèi)核,而不準許肆意跳入內(nèi)核。有了這樣的陷入內(nèi)核的統(tǒng)一訪問路徑限制才能保證內(nèi)核安全無虞。

我們可以形象地描述這種機制:作為一個游客,你可以買票要求進入野生動物園,但你必須老老實實地坐在觀光車上,按照規(guī)定的路線觀光游覽。當(dāng)然,不準下車,因為那樣太危險,不是讓你丟掉小命,就是讓你嚇壞了野生動物。

1.2 存在的必要性

系統(tǒng)調(diào)用的存在,有著諸多至關(guān)重要的原因,每一點都與操作系統(tǒng)的穩(wěn)定、安全和高效運行息息相關(guān)。

(1)保護系統(tǒng)資源

操作系統(tǒng)的資源,無論是硬件資源,如 CPU、內(nèi)存、磁盤等,還是軟件資源,像文件、進程等,都是極其珍貴且需要嚴格保護的。內(nèi)核如同一位嚴謹?shù)墓芗?,牢牢掌控著這些資源。如果用戶程序能夠隨意直接訪問這些資源,就好比未經(jīng)授權(quán)的人隨意進出管家的核心管理區(qū)域,必然會引發(fā)混亂,導(dǎo)致系統(tǒng)的安全性和穩(wěn)定性遭受嚴重威脅。

而系統(tǒng)調(diào)用就像是管家設(shè)置的一扇安全門,用戶程序必須通過系統(tǒng)調(diào)用這一正規(guī)途徑向內(nèi)核提出資源訪問請求,內(nèi)核會依據(jù)一系列嚴格的規(guī)則,如用戶權(quán)限、資源使用狀態(tài)等,對請求進行細致的審查和合理的裁決,只有符合條件的請求才會被批準,從而有效避免了用戶程序?qū)ο到y(tǒng)資源的非法或不當(dāng)訪問,保障了系統(tǒng)的穩(wěn)定和安全。

(2)提供統(tǒng)一接口

在計算機系統(tǒng)中,硬件設(shè)備的種類繁雜多樣,不同的硬件設(shè)備有著各自獨特的操作方式和特性。這就好比一個大型工廠里有著各種不同類型的機器設(shè)備,每臺設(shè)備的操作方法都不一樣。如果沒有一個統(tǒng)一的接口,用戶程序在訪問不同硬件設(shè)備時,就需要針對每種設(shè)備編寫復(fù)雜且差異巨大的代碼,這無疑會極大地增加編程的難度和復(fù)雜性,降低開發(fā)效率。而系統(tǒng)調(diào)用就像是工廠里的統(tǒng)一操作指南,為用戶空間提供了一種簡潔、統(tǒng)一的硬件抽象接口。

無論用戶程序需要訪問何種硬件設(shè)備,都只需通過相應(yīng)的系統(tǒng)調(diào)用,而無需深入了解硬件設(shè)備的底層細節(jié),這使得編程變得更加簡單、高效,也提高了應(yīng)用程序的可移植性,就像按照統(tǒng)一操作指南,工人可以輕松操作不同的設(shè)備,而無需為每種設(shè)備單獨學(xué)習(xí)復(fù)雜的操作方法。

(3)實現(xiàn)多任務(wù)和虛擬內(nèi)存管理

在現(xiàn)代操作系統(tǒng)中,多任務(wù)處理和虛擬內(nèi)存管理是至關(guān)重要的功能。多任務(wù)處理就像一位優(yōu)秀的調(diào)度員同時安排多個任務(wù)并行執(zhí)行,讓多個進程能夠在同一時間內(nèi)有條不紊地運行,充分利用系統(tǒng)資源,提高系統(tǒng)的并發(fā)處理能力。虛擬內(nèi)存管理則如同一個智能的內(nèi)存分配助手,為每個進程分配獨立的虛擬地址空間,使得進程在運行時仿佛擁有了自己獨立的內(nèi)存空間,互不干擾。而這些功能的實現(xiàn),都離不開系統(tǒng)調(diào)用的支持。系統(tǒng)調(diào)用能夠讓內(nèi)核清晰地了解每個進程的運行狀態(tài)和資源需求,從而合理地調(diào)度 CPU 時間,精準地分配內(nèi)存資源。

當(dāng)一個進程通過系統(tǒng)調(diào)用請求創(chuàng)建新進程時,內(nèi)核會根據(jù)系統(tǒng)的資源狀況和調(diào)度策略,為新進程分配必要的資源,并將其納入多任務(wù)管理的范疇;在內(nèi)存管理方面,進程通過系統(tǒng)調(diào)用請求內(nèi)存分配時,內(nèi)核會依據(jù)虛擬內(nèi)存管理機制,為進程分配合適的虛擬內(nèi)存空間,并負責(zé)管理虛擬內(nèi)存與物理內(nèi)存之間的映射關(guān)系,確保進程能夠正常訪問內(nèi)存資源,就像調(diào)度員根據(jù)任務(wù)需求合理安排工作時間,內(nèi)存分配助手根據(jù)進程需要分配和管理內(nèi)存空間,保障了系統(tǒng)多任務(wù)和虛擬內(nèi)存管理功能的順利實現(xiàn)。

1.3為什么需要系統(tǒng)調(diào)用

linux內(nèi)核中設(shè)置了一組用于實現(xiàn)系統(tǒng)功能的子程序,稱為系統(tǒng)調(diào)用。系統(tǒng)調(diào)用和普通庫函數(shù)調(diào)用非常相似,只是系統(tǒng)調(diào)用由操作系統(tǒng)核心提供,運行于內(nèi)核態(tài),而普通的函數(shù)調(diào)用由函數(shù)庫或用戶自己提供,運行于用戶態(tài)。

一般的,進程是不能訪問內(nèi)核的。它不能訪問內(nèi)核所占內(nèi)存空間也不能調(diào)用內(nèi)核函數(shù)。CPU硬件決定了這些(這就是為什么它被稱作“保護模式”)。

為了和用戶空間上運行的進程進行交互,內(nèi)核提供了一組接口。透過該接口,應(yīng)用程序可以訪問硬件設(shè)備和其他操作系統(tǒng)資源。這組接口在應(yīng)用程序和內(nèi)核之間扮演了使者的角色,應(yīng)用程序發(fā)送各種請求,而內(nèi)核負責(zé)滿足這些請求(或者讓應(yīng)用程序暫時擱置)。實際上提供這組接口主要是為了保證系統(tǒng)穩(wěn)定可靠,避免應(yīng)用程序肆意妄行,惹出大麻煩。

系統(tǒng)調(diào)用在用戶空間進程和硬件設(shè)備之間添加了一個中間層,該層主要作用有三個:

  1. 它為用戶空間提供了一種統(tǒng)一的硬件的抽象接口。比如當(dāng)需要讀些文件的時候,應(yīng)用程序就可以不去管磁盤類型和介質(zhì),甚至不用去管文件所在的文件系統(tǒng)到底是哪種類型。
  2. 系統(tǒng)調(diào)用保證了系統(tǒng)的穩(wěn)定和安全。作為硬件設(shè)備和應(yīng)用程序之間的中間人,內(nèi)核可以基于權(quán)限和其他一些規(guī)則對需要進行的訪問進行裁決。舉例來說,這樣可以避免應(yīng)用程序不正確地使用硬件設(shè)備,竊取其他進程的資源,或做出其他什么危害系統(tǒng)的事情。
  3. 每個進程都運行在虛擬系統(tǒng)中,而在用戶空間和系統(tǒng)的其余部分提供這樣一層公共接口,也是出于這種考慮。如果應(yīng)用程序可以隨意訪問硬件而內(nèi)核又對此一無所知的話,幾乎就沒法實現(xiàn)多任務(wù)和虛擬內(nèi)存,當(dāng)然也不可能實現(xiàn)良好的穩(wěn)定性和安全性。在Linux中,系統(tǒng)調(diào)用是用戶空間訪問內(nèi)核的惟一手段;除異常和中斷外,它們是內(nèi)核惟一的合法入口。

1.4 API/POSIX/C庫的區(qū)別與聯(lián)系

一般情況下,應(yīng)用程序通過應(yīng)用編程接口(API)而不是直接通過系統(tǒng)調(diào)用來編程。這點很重要,因為應(yīng)用程序使用的這種編程接口實際上并不需要和內(nèi)核提供的系統(tǒng)調(diào)用一一對應(yīng)。

一個API定義了一組應(yīng)用程序使用的編程接口。它們可以實現(xiàn)成一個系統(tǒng)調(diào)用,也可以通過調(diào)用多個系統(tǒng)調(diào)用來實現(xiàn),而完全不使用任何系統(tǒng)調(diào)用也不存在問題。實際上,API可以在各種不同的操作系統(tǒng)上實現(xiàn),給應(yīng)用程序提供完全相同的接口,而它們本身在這些系統(tǒng)上的實現(xiàn)卻可能迥異。

在Unix世界中,最流行的應(yīng)用編程接口是基于POSIX標準的,其目標是提供一套大體上基于Unix的可移植操作系統(tǒng)標準。POSIX是說明API和系統(tǒng)調(diào)用之間關(guān)系的一個極好例子。在大多數(shù)Unix系統(tǒng)上,根據(jù)POSIX而定義的API函數(shù)和系統(tǒng)調(diào)用之間有著直接關(guān)系。

Linux的系統(tǒng)調(diào)用像大多數(shù)Unix系統(tǒng)一樣,作為C庫的一部分提供如下圖所示。C庫實現(xiàn)了 Unix系統(tǒng)的主要API,包括標準C庫函數(shù)和系統(tǒng)調(diào)用。所有的C程序都可以使用C庫,而由于C語言本身的特點,其他語言也可以很方便地把它們封裝起來使用。

從程序員的角度看,系統(tǒng)調(diào)用無關(guān)緊要,他們只需要跟API打交道就可以了。相反,內(nèi)核只跟系統(tǒng)調(diào)用打交道;庫函數(shù)及應(yīng)用程序是怎么使用系統(tǒng)調(diào)用不是內(nèi)核所關(guān)心的。

關(guān)于Unix的界面設(shè)計有一句通用的格言“提供機制而不是策略”。換句話說,Unix的系統(tǒng)調(diào)用抽象出了用于完成某種確定目的的函數(shù)。至干這些函數(shù)怎么用完全不需要內(nèi)核去關(guān)心。區(qū)別對待機制(mechanism)和策略(policy)是Unix設(shè)計中的一大亮點。大部分的編程問題都可以被切割成兩個部分:“需要提供什么功能”(機制)和“怎樣實現(xiàn)這些功能”(策略)。

區(qū)別

api是函數(shù)的定義,規(guī)定了這個函數(shù)的功能,跟內(nèi)核無直接關(guān)系。而系統(tǒng)調(diào)用是通過中斷向內(nèi)核發(fā)請求,實現(xiàn)內(nèi)核提供的某些服務(wù)。

聯(lián)系

  • 一個api可能會需要一個或多個系統(tǒng)調(diào)用來完成特定功能。通俗點說就是如果這個api需要跟內(nèi)核打交道就需要系統(tǒng)調(diào)用,否則不需要。
  • 程序員調(diào)用的是API(API函數(shù)),然后通過與系統(tǒng)調(diào)用共同完成函數(shù)的功能。因此,API是一個提供給應(yīng)用程序的接口,一組函數(shù),是與程序員進行直接交互的。系統(tǒng)調(diào)用則不與程序員進行交互的,它根據(jù)API函數(shù),通過一個軟中斷機制向內(nèi)核提交請求,以獲取內(nèi)核服務(wù)的接口。
  • 并不是所有的API函數(shù)都一一對應(yīng)一個系統(tǒng)調(diào)用,有時,一個API函數(shù)會需要幾個系統(tǒng)調(diào)用來共同完成函數(shù)的功能,甚至還有一些API函數(shù)不需要調(diào)用相應(yīng)的系統(tǒng)調(diào)用(因此它所完成的不是內(nèi)核提供的服務(wù))

二、系統(tǒng)調(diào)用與用戶程序的交互

系統(tǒng)調(diào)用(system calls),Linux內(nèi)核, GNU C庫(glibc)

在電腦中,系統(tǒng)調(diào)用(英語:system call),指運行在用戶空間的程序向操作系統(tǒng)內(nèi)核請求需要更高權(quán)限運行的服務(wù)。系統(tǒng)調(diào)用提供用戶程序與操作系統(tǒng)之間的接口。大多數(shù)系統(tǒng)交互式操作需求在內(nèi)核態(tài)執(zhí)行。如設(shè)備IO操作或者進程間通信。

用戶空間(用戶態(tài))和內(nèi)核空間(內(nèi)核態(tài))

操作系統(tǒng)的進程空間可分為用戶空間和內(nèi)核空間,它們需要不同的執(zhí)行權(quán)限。其中系統(tǒng)調(diào)用運行在內(nèi)核空間。

庫函數(shù)

系統(tǒng)調(diào)用和普通庫函數(shù)調(diào)用非常相似,只是系統(tǒng)調(diào)用由操作系統(tǒng)內(nèi)核提供,運行于內(nèi)核核心態(tài),而普通的庫函數(shù)調(diào)用由函數(shù)庫或用戶自己提供,運行于用戶態(tài)。

典型實現(xiàn)(Linux)

Linux 在x86上的系統(tǒng)調(diào)用通過 int 80h 實現(xiàn),用系統(tǒng)調(diào)用號來區(qū)分入口函數(shù)。操作系統(tǒng)實現(xiàn)系統(tǒng)調(diào)用的基本過程是:

  1. 應(yīng)用程序調(diào)用庫函數(shù)(API);
  2. API 將系統(tǒng)調(diào)用號存入 EAX,然后通過中斷調(diào)用使系統(tǒng)進入內(nèi)核態(tài);
  3. 內(nèi)核中的中斷處理函數(shù)根據(jù)系統(tǒng)調(diào)用號,調(diào)用對應(yīng)的內(nèi)核函數(shù)(系統(tǒng)調(diào)用);
  4. 系統(tǒng)調(diào)用完成相應(yīng)功能,將返回值存入 EAX,返回到中斷處理函數(shù);
  5. 中斷處理函數(shù)返回到 API 中;
  6. API 將 EAX 返回給應(yīng)用程序。

應(yīng)用程序調(diào)用系統(tǒng)調(diào)用的過程是:

  1. 把系統(tǒng)調(diào)用的編號存入 EAX;
  2. 把函數(shù)參數(shù)存入其它通用寄存器;
  3. 觸發(fā) 0x80 號中斷(int 0x80)。

查看系統(tǒng)調(diào)用號

  1. 使用命令cat /usr/include/asm/unistd_32.h來打開32位系統(tǒng)調(diào)用表
  2. 使用命令cat /usr/include/asm/unistd_64.h來打開32位系統(tǒng)調(diào)用表

簡介幾種系統(tǒng)調(diào)用函數(shù):write、read、open、close、ioctl

在 Linux 中,一切(或幾乎一切)都是文件,因此,文件操作在 Linux 中是十分重要的,為此,Linux 系統(tǒng)直接提供了一些函數(shù)用于對文件和設(shè)備進行訪問和控制,這些函數(shù)被稱為系統(tǒng)調(diào)用(syscall),它們也是通向操作系統(tǒng)本身的接口。

系統(tǒng)調(diào)用工作在內(nèi)核態(tài),實際上,系統(tǒng)調(diào)用是用戶空間訪問內(nèi)核空間的唯一手段(除異常和陷入外,它們是內(nèi)核唯一的合法入口)。系統(tǒng)調(diào)用的主要作用如下:

  • 1)系統(tǒng)調(diào)用為用戶空間提供了一種硬件的抽象接口,這樣,當(dāng)需要讀寫文件時,應(yīng)用程序就可以不用管磁盤類型和介質(zhì),甚至不用去管文件所在的文件系統(tǒng)到底是哪種類型;
  • 2)系統(tǒng)調(diào)用保證了系統(tǒng)的穩(wěn)定和安全。作為硬件設(shè)備和應(yīng)用程序之間的中間人,內(nèi)核可以基于權(quán)限、用戶類型和其他一些規(guī)則對需要進行的訪問進行判斷;
  • 3)系統(tǒng)調(diào)用是實現(xiàn)多任務(wù)和虛擬內(nèi)存的前提

要訪問系統(tǒng)調(diào)用,通常通過 C 庫中定義的函數(shù)調(diào)用來進行。它們通常都需要定義零個、一個或幾個參數(shù)(輸入),而且可能產(chǎn)生一些副作用(會使系統(tǒng)的狀態(tài)發(fā)生某種變化)。系統(tǒng)調(diào)用還會通過一個 long 類型的返回值來表示成功或者錯誤。通常,用一個負的值來表明錯誤,0表示成功。系統(tǒng)調(diào)用出現(xiàn)錯誤時,C 庫會把錯誤碼寫入 errno 全局變量,通過調(diào)用 perror() 庫函數(shù),可以把該變量翻譯成用戶可理解的錯誤字符串。

2.1 用戶程序發(fā)起調(diào)用的方式

(1)write 系統(tǒng)調(diào)用

在 Linux 系統(tǒng)中,用戶程序發(fā)起系統(tǒng)調(diào)用主要有兩種常見方式。一種是通過應(yīng)用程序編程接口(API),這些 API 通常是對系統(tǒng)調(diào)用的封裝,以更友好、易用的形式呈現(xiàn)給開發(fā)者。另一種則是在一些特定場景下,直接使用 syscall 函數(shù)。

以 write 函數(shù)為例,它是一個用于向文件描述符寫入數(shù)據(jù)的系統(tǒng)調(diào)用封裝函數(shù),在 C 語言中被廣泛應(yīng)用。當(dāng)我們需要將數(shù)據(jù)寫入文件時,便可以使用 write 函數(shù)。以下是一個簡單的 C 語言示例代碼,展示了如何使用 write 函數(shù)將字符串寫入標準輸出(通常是終端屏幕):

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

int main() {
    char *message = "Hello, World!\n";
    int len = 15;  // 包括空終止符的長度

    // 使用write函數(shù)將字符串寫入stdout
    if (write(1, message, len) != len) {
        perror("write error");
        return 1;
    }

    return 0;
}

在這段代碼中,首先定義了一個字符串 message,即我們想要輸出的內(nèi)容,以及它的長度 len。然后,調(diào)用 write 函數(shù),其第一個參數(shù) 1 代表標準輸出的文件描述符,在 Linux 系統(tǒng)中,文件描述符是一個非負整數(shù),用于標識打開的文件或設(shè)備,標準輸出的文件描述符通常為 1;第二個參數(shù) message 是指向要寫入數(shù)據(jù)緩沖區(qū)的指針,也就是我們定義的字符串;第三個參數(shù) len 則表示要寫入數(shù)據(jù)的字節(jié)數(shù)。如果 write 函數(shù)返回值不等于請求寫入的字節(jié)數(shù) len,說明寫入過程出現(xiàn)了錯誤,此時通過 perror 函數(shù)輸出錯誤信息,并返回 1 表示程序異常結(jié)束。如果寫入成功,則正常返回 0 。

在這個例子中,write 函數(shù)雖然看似只是一個普通的函數(shù)調(diào)用,但實際上它是對底層系統(tǒng)調(diào)用的封裝。當(dāng)程序執(zhí)行 write 函數(shù)時,會進一步觸發(fā)系統(tǒng)調(diào)用機制,實現(xiàn)從用戶態(tài)到內(nèi)核態(tài)的切換,進而完成實際的寫入操作。這種通過 API 封裝系統(tǒng)調(diào)用的方式,極大地簡化了開發(fā)者的工作,使得我們無需深入了解底層系統(tǒng)調(diào)用的復(fù)雜細節(jié),就能輕松實現(xiàn)文件寫入等功能 。

(2)read 系統(tǒng)調(diào)用

系統(tǒng)調(diào)用 read 的作用是:從文件描述符 fildes 相關(guān)聯(lián)的文件里讀入 nbytes 個字節(jié)的數(shù)據(jù),并把它們放到數(shù)據(jù)區(qū) buf 中。它返回實際讀入的字節(jié)數(shù),這可能會小于請求的字節(jié)數(shù)。如果 read 調(diào)用返回 0,就表示沒有讀入任何數(shù)據(jù),已到達了文件尾;如果返回 -1,則表示 read 調(diào)用出現(xiàn)了錯誤。read 系統(tǒng)調(diào)用的原型如下:

#include <unistd.h>
size_t read(int fildes,void *buf,size_t nbytes);

用一段代碼演示一下用法:

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

int main()
{
        char buffer[30];
        size_t x = read(0,buffer,30);
        write(1,buffer,x);

        exit(0);
}

/* 輸出結(jié)果:
hello ,my name is tongye!
hello ,my name is tongye!
*/

這段代碼使用 read 系統(tǒng)調(diào)用函數(shù)從標準輸入讀取 30 個字節(jié)到緩沖區(qū) buffer 中去(輸出結(jié)果中的第一行是從標準輸入鍵入的),然后使用 write 系統(tǒng)調(diào)用函數(shù)將 buffer 中的字節(jié)寫到標準輸出中去。

(3)open 系統(tǒng)調(diào)用

系統(tǒng)調(diào)用 open 用于創(chuàng)建一個新的文件描述符。

#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int open(const char *path,int oflags);
int open(const char *path,int oflags,mode_t mode);  // oflags 標志為 O_CREAT 時,使用這種格式

open 建立了一條到文件或設(shè)備的訪問路徑。如果調(diào)用成功,它將返回一個可以被 read、write 和其他系統(tǒng)調(diào)用使用的文件描述符。這個文件描述符是唯一的,不會與任何其他運行中的進程共享。在調(diào)用失敗時,將返回 -1 并設(shè)置全局變量 errno 來指明失敗的原因。

使用 open 系統(tǒng)調(diào)用時,準備打開的文件或設(shè)備的名字作為參數(shù) path 傳遞給函數(shù),oflags 參數(shù)用于指定打開文件所采取的動作。oflags 參數(shù)是通過命令文件訪問模式與其他可選模式相結(jié)合的方式來指定的,open 調(diào)用必須指定以下文件訪問模式之一:

  • 1)O_RDONLY:以只讀方式打開;
  • 2)O_WRONLY:以只寫方式打開;
  • 3)O_RDWR :以讀寫方式打開。

另外,還有以下幾種可選模式的組合( 用按位或 || 來操作 ):

  • 4)O_APPEND:把寫入數(shù)據(jù)追加在文件的末尾;
  • 5)O_TRUNC:把文件長度設(shè)置為零,丟棄已有的內(nèi)容;
  • 6)O_CREAT:如果需要,就按照參數(shù) mode 中給出的訪問模式創(chuàng)建文件;
  • 7)O_EXCL:與 O_CREAT 一起使用,確保調(diào)用者創(chuàng)建出文件。使用這個模式可以防止兩個程序同時創(chuàng)建同一個文件,如果文件已經(jīng)存在,open 調(diào)用將失敗。

當(dāng)使用 O_CREAT 標志的 open 調(diào)用來創(chuàng)建文件時,需要使用有 3 個參數(shù)格式的 open 調(diào)用。其中,第三個參數(shù) mode 是幾個標志按位或后得到的,這些標志在頭文件 sys/stat.h 中定義,如下:

標志

說明

S_IRUSR

文件屬主可讀

S_IWUSR

文件屬主可寫

S_IXUSR

文件屬主可執(zhí)行

S_IRGRP

文件所在組可讀

S_IWGRP

文件所在組可寫

S_IWOTH

文件所在組可執(zhí)行

S_IROTH

其他用戶可讀

S_IWOTH

其他用戶可寫

S_IWOTH

其他用戶可執(zhí)行

用一個例子說明一下:

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

int main()
{
        open("file",O_CREAT,S_IRUSR | S_IWGRP);

        exit(0);
}

執(zhí)行這段代碼將在當(dāng)前目錄下創(chuàng)建一個名為 file 的文件,該文件對文件屬主可讀,對文件所在組可寫,用 ls -l 命令查看如下:

[tongye@Iocalhost OpenSysCaII]$ Is -I
total 20
-r---W---- 1 tongye tongye 0 0ct 24 00:55 file
-rw-rw-r-- 1 tongye tongye 96 0ct 24 00:54 Makefile
-rwxrwxr-x 1 tongye tongye 8496 0ct 24 00:57
-rw-rw-r-- 1 tongye tongye 127 0ct 24 00:57 open_sys_calT.c

可以看到有一個名為 file 的文件,該文件就是使用 open 系統(tǒng)調(diào)用創(chuàng)建的,文件的權(quán)限為文件屬主可讀,文件所在組可寫。

(4)close 系統(tǒng)調(diào)用

系統(tǒng)調(diào)用 close 可以用來終止文件描述符 fildes 與其對應(yīng)文件之間的關(guān)聯(lián)。當(dāng) close 系統(tǒng)調(diào)用成功時,返回 0,文件描述符被釋放并能夠重新使用;調(diào)用出錯,則返回 -1。

#include <unistd.h>
int close(int fildes);

(5)ioctl 系統(tǒng)調(diào)用

系統(tǒng)調(diào)用 ioctl 提供了一個用于控制設(shè)備及其描述符行為和配置底層服務(wù)的接口。終端、文件描述符、套接字甚至磁帶機都可以有為它們定義的 ioctl。

#include <unistd.h>
int ioctl(int fildes,int cmd,...);

octl 對描述符 fildes 引用的對象執(zhí)行 cmd 參數(shù)中給出的操作。

2.2 調(diào)用過程中的狀態(tài)切換

當(dāng)用戶程序發(fā)起系統(tǒng)調(diào)用時,一個關(guān)鍵的環(huán)節(jié)便是從用戶態(tài)到內(nèi)核態(tài)的狀態(tài)切換。在 Linux 系統(tǒng)中,這種切換是通過軟中斷機制來實現(xiàn)的,而軟中斷又是借助中斷向量表來精準找到對應(yīng)的中斷處理程序。

在計算機系統(tǒng)中,用戶態(tài)和內(nèi)核態(tài)是兩種不同的執(zhí)行模式。用戶態(tài)下運行的程序權(quán)限較低,只能訪問用戶空間的內(nèi)存,并且不能直接操作硬件資源,這就像一個普通員工在公司的特定工作區(qū)域內(nèi)工作,權(quán)限有限,不能隨意進入核心管理區(qū)域。而內(nèi)核態(tài)則擁有對系統(tǒng)所有資源的完全控制權(quán)限,包括硬件訪問、內(nèi)存管理和進程調(diào)度等,類似于公司的核心管理層,擁有最高權(quán)限,可以掌控公司的一切資源。

為了實現(xiàn)從用戶態(tài)到內(nèi)核態(tài)的切換,Linux 使用軟中斷指令。以 x86 架構(gòu)為例,早期使用 int 0x80 指令來觸發(fā)系統(tǒng)調(diào)用軟中斷,后來隨著技術(shù)發(fā)展,引入了更高效的 syscall 指令。當(dāng)用戶程序執(zhí)行到發(fā)起系統(tǒng)調(diào)用的代碼時,如上述的 write 函數(shù)調(diào)用,會執(zhí)行相應(yīng)的軟中斷指令。這條指令就像一個特殊的 “通行證”,它會觸發(fā) CPU 產(chǎn)生一個軟件中斷信號。

CPU 接收到這個軟中斷信號后,會暫停當(dāng)前用戶態(tài)程序的執(zhí)行,就像一個正在專心工作的員工突然被緊急通知停下手中工作。接著,CPU 開始進行一系列復(fù)雜的操作來切換到內(nèi)核態(tài)。它首先會保存當(dāng)前用戶態(tài)程序的上下文信息,包括程序計數(shù)器(記錄下一條要執(zhí)行的指令地址)、通用寄存器的值等,這些信息就像是員工停下工作時記錄的工作進度和手頭的資料,以便后續(xù)能夠恢復(fù)工作。然后,CPU 根據(jù)中斷向量表來查找與該軟中斷對應(yīng)的中斷處理程序的入口地址。

中斷向量表是一個非常重要的數(shù)據(jù)結(jié)構(gòu),它就像一本詳細的 “指南手冊”,存儲了所有中斷處理程序的地址。每個中斷都被賦予一個唯一的中斷號,這個中斷號就像是手冊中的頁碼,通過它可以快速定位到相應(yīng)中斷處理程序的地址。在系統(tǒng)調(diào)用軟中斷的情況下,CPU 會根據(jù)軟中斷對應(yīng)的中斷號,從中斷向量表中找到系統(tǒng)調(diào)用處理程序的入口地址,進而跳轉(zhuǎn)到該地址開始執(zhí)行內(nèi)核態(tài)的代碼。

一旦進入內(nèi)核態(tài),系統(tǒng)調(diào)用處理程序就會根據(jù)用戶程序傳遞過來的系統(tǒng)調(diào)用號和參數(shù),執(zhí)行相應(yīng)的內(nèi)核服務(wù)例程。在 write 函數(shù)對應(yīng)的系統(tǒng)調(diào)用中,內(nèi)核會根據(jù)傳遞的文件描述符、數(shù)據(jù)緩沖區(qū)和數(shù)據(jù)長度等參數(shù),在內(nèi)核空間中進行實際的文件寫入操作,訪問底層的硬件設(shè)備(如磁盤)來完成數(shù)據(jù)的存儲。

當(dāng)內(nèi)核完成系統(tǒng)調(diào)用的處理后,會將結(jié)果返回給用戶程序。此時,CPU 會再次進行上下文切換,恢復(fù)之前保存的用戶態(tài)程序的上下文信息,就像員工重新拿起之前記錄的工作進度和資料,繼續(xù)之前被中斷的工作。然后,CPU 返回到用戶態(tài),繼續(xù)執(zhí)行用戶程序中系統(tǒng)調(diào)用之后的代碼 。

從用戶態(tài)到內(nèi)核態(tài)的切換過程是 Linux 系統(tǒng)調(diào)用實現(xiàn)的關(guān)鍵環(huán)節(jié),它涉及到 CPU、內(nèi)存、中斷向量表等多個組件的協(xié)同工作,通過軟中斷機制和中斷向量表的配合,實現(xiàn)了用戶程序與內(nèi)核之間安全、高效的通信,確保了系統(tǒng)的穩(wěn)定運行和資源的合理利用 。

三、系統(tǒng)調(diào)用的實現(xiàn)原理

3.1 系統(tǒng)調(diào)用號與系統(tǒng)調(diào)用表

在 Linux 系統(tǒng)調(diào)用的實現(xiàn)過程中,系統(tǒng)調(diào)用號與系統(tǒng)調(diào)用表扮演著不可或缺的關(guān)鍵角色。

系統(tǒng)調(diào)用號,簡單來說,是一個獨一無二的標識符,就像每個人都有一個獨特的身份證號碼一樣,每個系統(tǒng)調(diào)用都被賦予了一個唯一的系統(tǒng)調(diào)用號。在 x86 架構(gòu)中,系統(tǒng)調(diào)用號通常是通過 eax 寄存器傳遞給內(nèi)核的。在用戶空間執(zhí)行系統(tǒng)調(diào)用之前,會將對應(yīng)的系統(tǒng)調(diào)用號存入 eax 寄存器,這樣當(dāng)系統(tǒng)進入內(nèi)核態(tài)時,內(nèi)核就能依據(jù)這個系統(tǒng)調(diào)用號,精準地知曉用戶程序究竟請求的是哪一個系統(tǒng)調(diào)用。

以常見的文件操作相關(guān)系統(tǒng)調(diào)用為例,打開文件的系統(tǒng)調(diào)用 open,它擁有特定的系統(tǒng)調(diào)用號,當(dāng)用戶程序需要打開文件時,會將 open 系統(tǒng)調(diào)用對應(yīng)的系統(tǒng)調(diào)用號存入 eax 寄存器,再發(fā)起系統(tǒng)調(diào)用,內(nèi)核就能根據(jù)這個號碼識別出用戶的意圖是打開文件。這種通過唯一編號來標識系統(tǒng)調(diào)用的方式,極大地提高了系統(tǒng)調(diào)用處理的效率和準確性,避免了因名稱解析等復(fù)雜操作帶來的性能損耗 。

而系統(tǒng)調(diào)用表,則是一個存儲著系統(tǒng)調(diào)用函數(shù)指針的數(shù)組,它就像是一本精心編制的索引目錄,數(shù)組的每個元素都是一個指向特定系統(tǒng)調(diào)用處理函數(shù)的指針。在 x86 架構(gòu)下,系統(tǒng)調(diào)用表的定義和實現(xiàn)與具體的內(nèi)核版本和架構(gòu)相關(guān)。

在 64 位系統(tǒng)中,系統(tǒng)調(diào)用表定義在arch/x86/kernel/syscall_64.c文件中 ,其數(shù)組名為sys_call_table,該數(shù)組的大小為__NR_syscall_max + 1,其中__NR_syscall_max是一個宏,在 64 位模式下,它的值為 542 ,這個宏定義于include/generated/asm-offsets.h文件,該文件是在 Kbuild 編譯后生成的。系統(tǒng)調(diào)用表中的元素類型為sys_call_ptr_t,這是通過 typedef 定義的函數(shù)指針,它指向的是具體的系統(tǒng)調(diào)用處理函數(shù)。當(dāng)內(nèi)核接收到系統(tǒng)調(diào)用請求,并獲取到系統(tǒng)調(diào)用號后,就會以這個系統(tǒng)調(diào)用號作為索引,迅速在系統(tǒng)調(diào)用表中找到對應(yīng)的函數(shù)指針,進而調(diào)用相應(yīng)的系統(tǒng)調(diào)用處理函數(shù),執(zhí)行具體的系統(tǒng)調(diào)用操作 。

假設(shè)系統(tǒng)調(diào)用號為n,那么系統(tǒng)調(diào)用表sys_call_table中第n個元素sys_call_table[n]就指向了處理該系統(tǒng)調(diào)用的函數(shù)。如果系統(tǒng)調(diào)用號為 1,對應(yīng)sys_call_table[1],它指向的就是處理 write 系統(tǒng)調(diào)用的函數(shù),當(dāng)內(nèi)核根據(jù)系統(tǒng)調(diào)用號 1 在表中找到這個指針并調(diào)用相應(yīng)函數(shù)時,就能完成實際的文件寫入操作。系統(tǒng)調(diào)用號與系統(tǒng)調(diào)用表的緊密配合,構(gòu)成了 Linux 系統(tǒng)調(diào)用實現(xiàn)的重要基礎(chǔ),它們使得內(nèi)核能夠高效、準確地響應(yīng)用戶程序的各種系統(tǒng)調(diào)用請求,保障了系統(tǒng)的穩(wěn)定運行和高效工作 。

3.2 系統(tǒng)調(diào)用處理程序

系統(tǒng)調(diào)用處理程序是系統(tǒng)調(diào)用實現(xiàn)過程中的核心環(huán)節(jié),它負責(zé)處理用戶程序發(fā)起的系統(tǒng)調(diào)用請求,執(zhí)行相應(yīng)的內(nèi)核服務(wù)例程,并返回處理結(jié)果。當(dāng)用戶程序發(fā)起系統(tǒng)調(diào)用時,會觸發(fā)軟中斷,從而進入內(nèi)核態(tài),開始執(zhí)行系統(tǒng)調(diào)用處理程序。

系統(tǒng)調(diào)用處理程序的工作流程嚴謹而有序。當(dāng) CPU 響應(yīng)軟中斷進入內(nèi)核態(tài)后,首先會保存當(dāng)前用戶程序的寄存器狀態(tài)。這一步至關(guān)重要,因為寄存器中存儲著用戶程序當(dāng)前的執(zhí)行狀態(tài)和相關(guān)數(shù)據(jù),保存這些寄存器狀態(tài)就如同為用戶程序的執(zhí)行進度拍了一張 “快照”,以便在系統(tǒng)調(diào)用完成后能夠準確地恢復(fù)到調(diào)用前的狀態(tài),繼續(xù)執(zhí)行用戶程序。在 x86 架構(gòu)中,通常會將寄存器的值壓入到核心棧中,這些寄存器包括通用寄存器如 eax、ebx、ecx、edx 等,以及程序計數(shù)器(記錄下一條要執(zhí)行的指令地址)等關(guān)鍵寄存器。

保存完寄存器狀態(tài)后,系統(tǒng)調(diào)用處理程序會根據(jù)用戶程序傳遞過來的系統(tǒng)調(diào)用號,在系統(tǒng)調(diào)用表中查找對應(yīng)的系統(tǒng)調(diào)用處理函數(shù)。這個查找過程就像是在一本索引清晰的大字典中查找特定的詞條,系統(tǒng)調(diào)用號就是詞條的索引,通過它能夠快速定位到系統(tǒng)調(diào)用表中對應(yīng)的函數(shù)指針,進而找到真正執(zhí)行系統(tǒng)調(diào)用功能的處理函數(shù)。如果系統(tǒng)調(diào)用號為 5,表示打開文件的系統(tǒng)調(diào)用,處理程序就會根據(jù)這個 5 作為索引,在系統(tǒng)調(diào)用表中找到指向sys_open函數(shù)的指針,這個sys_open函數(shù)就是專門負責(zé)處理打開文件系統(tǒng)調(diào)用的函數(shù) 。

找到對應(yīng)的處理函數(shù)后,系統(tǒng)調(diào)用處理程序就會調(diào)用該函數(shù),執(zhí)行相應(yīng)的內(nèi)核服務(wù)例程。在執(zhí)行過程中,處理函數(shù)會根據(jù)系統(tǒng)調(diào)用的具體需求,訪問和操作內(nèi)核資源,完成用戶程序請求的任務(wù)。在執(zhí)行文件寫入的系統(tǒng)調(diào)用時,處理函數(shù)會根據(jù)傳遞過來的文件描述符、數(shù)據(jù)緩沖區(qū)和數(shù)據(jù)長度等參數(shù),在內(nèi)核空間中進行實際的文件寫入操作,訪問底層的磁盤設(shè)備,將數(shù)據(jù)存儲到指定的文件中 。

當(dāng)內(nèi)核服務(wù)例程執(zhí)行完畢后,系統(tǒng)調(diào)用處理程序會將執(zhí)行結(jié)果返回給用戶程序。在返回之前,會先恢復(fù)之前保存的用戶程序寄存器狀態(tài),就像把之前拍的 “快照” 重新還原,讓 CPU 回到系統(tǒng)調(diào)用前的狀態(tài)。然后,CPU 會從內(nèi)核態(tài)切換回用戶態(tài),繼續(xù)執(zhí)行用戶程序中系統(tǒng)調(diào)用之后的代碼,將系統(tǒng)調(diào)用的執(zhí)行結(jié)果傳遞給用戶程序,用戶程序就可以根據(jù)這個結(jié)果進行后續(xù)的處理 。

系統(tǒng)調(diào)用處理程序的工作流程確保了系統(tǒng)調(diào)用的安全、高效執(zhí)行,它在用戶程序與內(nèi)核之間搭建起了一座可靠的橋梁,使得用戶程序能夠在不直接訪問內(nèi)核資源的情況下,通過系統(tǒng)調(diào)用獲取內(nèi)核提供的各種服務(wù),保障了系統(tǒng)的穩(wěn)定性和安全性 。

3.3 參數(shù)傳遞與返回值處理

在系統(tǒng)調(diào)用過程中,參數(shù)傳遞和返回值處理是兩個關(guān)鍵環(huán)節(jié),它們確保了用戶程序與內(nèi)核之間能夠準確、有效地進行數(shù)據(jù)交互。

系統(tǒng)調(diào)用的參數(shù)傳遞方式與硬件架構(gòu)密切相關(guān)。以常見的 x86 架構(gòu)為例,在 32 位系統(tǒng)中,當(dāng)用戶程序發(fā)起系統(tǒng)調(diào)用時,參數(shù)通常通過寄存器來傳遞。具體來說,ebx、ecx、edx、esi 和 edi 這幾個寄存器按照順序存放前五個參數(shù)。如果系統(tǒng)調(diào)用需要傳遞六個或更多參數(shù),由于寄存器數(shù)量有限,此時會用一個單獨的寄存器(通常是 eax)存放指向所有這些參數(shù)在用戶空間地址的指針,然后通過內(nèi)存空間進行參數(shù)傳遞。在執(zhí)行一個需要傳遞多個參數(shù)的文件寫入系統(tǒng)調(diào)用時,前五個參數(shù)(如文件描述符、數(shù)據(jù)緩沖區(qū)指針、數(shù)據(jù)長度等)可能分別存放在 ebx、ecx、edx、esi 和 edi 寄存器中,如果還有其他參數(shù),就會將這些參數(shù)在用戶空間的地址存放在 eax 寄存器中,內(nèi)核可以根據(jù)這個地址從用戶空間獲取完整的參數(shù) 。

在 64 位的 x86 架構(gòu)系統(tǒng)中,參數(shù)傳遞規(guī)則有所不同。前 6 個整數(shù)或指針參數(shù)會在寄存器 RDI、RSI、RDX、RCX、R8、R9 中傳遞,對于嵌套函數(shù),R10 用作靜態(tài)鏈指針,其他參數(shù)則在堆棧上傳遞。這種參數(shù)傳遞方式充分利用了 64 位架構(gòu)下寄存器數(shù)量增加的優(yōu)勢,提高了參數(shù)傳遞的效率和靈活性 。

關(guān)于系統(tǒng)調(diào)用的返回值,也有著明確的約定。在 Linux 系統(tǒng)中,通常用一個負的返回值來表明系統(tǒng)調(diào)用執(zhí)行過程中出現(xiàn)了錯誤。返回值為 - 1 可能表示權(quán)限不足,-2 可能表示文件不存在等。不同的負值對應(yīng)著不同的錯誤類型,這些錯誤類型的定義可以在errno.h頭文件中找到。當(dāng)用戶程序接收到負的返回值時,可以通過查看errno變量的值來確定具體的錯誤原因,并且可以調(diào)用perror()庫函數(shù),將errno的值翻譯成用戶可以理解的錯誤字符串,以便進行錯誤處理 。

如果系統(tǒng)調(diào)用執(zhí)行成功,返回值通常為正值或 0。對于一些返回數(shù)據(jù)的系統(tǒng)調(diào)用,如讀取文件內(nèi)容的系統(tǒng)調(diào)用,返回值可能是實際讀取到的字節(jié)數(shù);而對于一些只執(zhí)行操作不返回具體數(shù)據(jù)的系統(tǒng)調(diào)用,成功時返回值可能為 0,表示操作順利完成。在執(zhí)行讀取文件系統(tǒng)調(diào)用時,如果成功讀取到數(shù)據(jù),返回值就是實際讀取的字節(jié)數(shù),用戶程序可以根據(jù)這個返回值來判斷讀取操作是否成功以及獲取到的數(shù)據(jù)量 。

參數(shù)傳遞和返回值處理機制是系統(tǒng)調(diào)用實現(xiàn)的重要組成部分,它們確保了用戶程序與內(nèi)核之間能夠準確地傳遞數(shù)據(jù)和信息,使得系統(tǒng)調(diào)用能夠按照預(yù)期的方式執(zhí)行,并將結(jié)果反饋給用戶程序,為應(yīng)用程序的正確運行提供了堅實的保障 。

四、不同架構(gòu)下的系統(tǒng)調(diào)用實現(xiàn)差異

4.1 x86架構(gòu)

x86 架構(gòu)下系統(tǒng)調(diào)用的實現(xiàn)方式隨著技術(shù)的發(fā)展不斷演進,經(jīng)歷了從 int 0x80 到 syscall 指令的重要轉(zhuǎn)變。

在早期,x86 架構(gòu)主要通過 int 0x80 指令來實現(xiàn)系統(tǒng)調(diào)用。當(dāng)用戶程序需要發(fā)起系統(tǒng)調(diào)用時,會執(zhí)行 int 0x80 這條軟中斷指令。這一指令就像是一個特殊的 “信號彈”,它會觸發(fā) CPU 產(chǎn)生一個軟件中斷信號。CPU 在接收到這個信號后,會暫停當(dāng)前用戶態(tài)程序的執(zhí)行,轉(zhuǎn)而執(zhí)行中斷處理程序。在這個過程中,系統(tǒng)調(diào)用號被存放在 eax 寄存器中,參數(shù)則通過 ebx、ecx、edx 等寄存器傳遞。例如,當(dāng)執(zhí)行一個打開文件的系統(tǒng)調(diào)用時,會將打開文件系統(tǒng)調(diào)用對應(yīng)的系統(tǒng)調(diào)用號存入 eax 寄存器,文件路徑等參數(shù)可能存放在 ebx 等寄存器中 。

與 int 0x80 指令緊密相關(guān)的是 entry_INT80_32 函數(shù),它在系統(tǒng)調(diào)用處理流程中扮演著關(guān)鍵角色。當(dāng) int 0x80 中斷發(fā)生后,CPU 會跳轉(zhuǎn)到 entry_INT80_32 函數(shù)執(zhí)行。這個函數(shù)主要負責(zé)保存用戶態(tài)的寄存器狀態(tài),因為這些寄存器中存儲著用戶程序當(dāng)前的執(zhí)行狀態(tài)和相關(guān)數(shù)據(jù),保存它們就如同為用戶程序的執(zhí)行進度拍了一張 “快照”,以便在系統(tǒng)調(diào)用完成后能夠準確地恢復(fù)到調(diào)用前的狀態(tài),繼續(xù)執(zhí)行用戶程序。在 entry_INT80_32 函數(shù)中,會將寄存器的值壓入到核心棧中,這些寄存器包括通用寄存器如 eax、ebx、ecx、edx 等,以及程序計數(shù)器(記錄下一條要執(zhí)行的指令地址)等關(guān)鍵寄存器 。

保存完寄存器狀態(tài)后,entry_INT80_32 函數(shù)會調(diào)用 do_syscall_32_irqs_on 函數(shù),這個函數(shù)才是真正處理系統(tǒng)調(diào)用的核心函數(shù)。它會根據(jù) eax 寄存器中保存的系統(tǒng)調(diào)用號,在系統(tǒng)調(diào)用表中查找對應(yīng)的系統(tǒng)調(diào)用處理函數(shù)。系統(tǒng)調(diào)用表就像是一本精心編制的索引目錄,數(shù)組的每個元素都是一個指向特定系統(tǒng)調(diào)用處理函數(shù)的指針。do_syscall_32_irqs_on 函數(shù)會以系統(tǒng)調(diào)用號作為索引,在系統(tǒng)調(diào)用表中找到對應(yīng)的函數(shù)指針,進而調(diào)用相應(yīng)的系統(tǒng)調(diào)用處理函數(shù),執(zhí)行具體的系統(tǒng)調(diào)用操作 。

隨著 x86 架構(gòu)的不斷發(fā)展,為了提高系統(tǒng)調(diào)用的性能,引入了 syscall 指令。syscall 指令相比 int 0x80 指令,減少了一些不必要的操作,使得系統(tǒng)調(diào)用的執(zhí)行更加高效。在使用 syscall 指令時,系統(tǒng)調(diào)用號被存放在 rax 寄存器中,參數(shù)則通過 rdi、rsi、rdx 等寄存器傳遞。這種方式在一定程度上簡化了參數(shù)傳遞過程,提高了系統(tǒng)調(diào)用的執(zhí)行效率 。

從 int 0x80 到 syscall 指令的演變,體現(xiàn)了 x86 架構(gòu)在系統(tǒng)調(diào)用實現(xiàn)上不斷追求性能優(yōu)化的過程。雖然具體的實現(xiàn)細節(jié)在不同的內(nèi)核版本和架構(gòu)下可能會有所差異,但總體上都是圍繞著如何更高效、更安全地實現(xiàn)用戶程序與內(nèi)核之間的通信這一核心目標展開的。無論是早期的 int 0x80 指令,還是后來的 syscall 指令,它們都在 x86 架構(gòu)的系統(tǒng)調(diào)用實現(xiàn)中發(fā)揮了重要作用,為 x86 架構(gòu)下的 Linux 系統(tǒng)提供了穩(wěn)定、高效的系統(tǒng)調(diào)用支持 。

4.2 ARM架構(gòu)

ARM 架構(gòu)下系統(tǒng)調(diào)用的實現(xiàn)與 x86 架構(gòu)有著顯著的不同,展現(xiàn)出自身獨特的特點。

在 ARM 架構(gòu)中,系統(tǒng)調(diào)用主要通過 SWI(Software Interrupt)指令來觸發(fā),在 Thumb 指令集下則使用 SVC(Supervisor Call)指令,它們的功能類似,都是用于實現(xiàn)從用戶態(tài)到內(nèi)核態(tài)的切換,以執(zhí)行系統(tǒng)調(diào)用。當(dāng)用戶程序需要發(fā)起系統(tǒng)調(diào)用時,會執(zhí)行 SWI 或 SVC 指令,這就如同按下了一個特殊的 “開關(guān)”,觸發(fā)系統(tǒng)進入內(nèi)核態(tài)進行系統(tǒng)調(diào)用的處理 。

與 x86 架構(gòu)不同,在 ARM 架構(gòu)中,系統(tǒng)調(diào)用號通常被存放在 r7 寄存器中。在發(fā)起系統(tǒng)調(diào)用之前,用戶程序會將對應(yīng)的系統(tǒng)調(diào)用號存入 r7 寄存器,同時,參數(shù)會被放入 r0 - r6 等寄存器中進行傳遞。在執(zhí)行一個讀取文件的系統(tǒng)調(diào)用時,會將讀取文件系統(tǒng)調(diào)用的系統(tǒng)調(diào)用號存入 r7 寄存器,文件描述符、數(shù)據(jù)緩沖區(qū)指針、數(shù)據(jù)長度等參數(shù)可能分別存放在 r0、r1、r2 等寄存器中 。

系統(tǒng)調(diào)用號的定義位置也與 x86 架構(gòu)不同。在 ARM 架構(gòu)中,系統(tǒng)調(diào)用號的定義通常位于arch/arm/include/asm/unistd.h文件中。在這個文件中,通過一系列的宏定義來為每個系統(tǒng)調(diào)用分配唯一的系統(tǒng)調(diào)用號。這些宏定義就像是一個編號分配表,明確地規(guī)定了每個系統(tǒng)調(diào)用對應(yīng)的編號,使得內(nèi)核能夠根據(jù)系統(tǒng)調(diào)用號準確地識別用戶程序請求的系統(tǒng)調(diào)用類型 。

在處理流程上,當(dāng) SWI 或 SVC 指令被執(zhí)行后,CPU 會跳轉(zhuǎn)到相應(yīng)的中斷處理程序。這個中斷處理程序會根據(jù) r7 寄存器中的系統(tǒng)調(diào)用號,在系統(tǒng)調(diào)用表中查找對應(yīng)的處理函數(shù)。與 x86 架構(gòu)類似,系統(tǒng)調(diào)用表中存儲著各個系統(tǒng)調(diào)用處理函數(shù)的指針,通過系統(tǒng)調(diào)用號作為索引,能夠快速找到對應(yīng)的處理函數(shù)并執(zhí)行。在處理函數(shù)執(zhí)行完畢后,會將結(jié)果返回給用戶程序,同時恢復(fù)用戶態(tài)的執(zhí)行環(huán)境,使程序繼續(xù)執(zhí)行 。

ARM 架構(gòu)下系統(tǒng)調(diào)用的實現(xiàn)方式是基于其自身的硬件特點和設(shè)計理念而形成的。通過 SWI 或 SVC 指令觸發(fā)系統(tǒng)調(diào)用,以及獨特的系統(tǒng)調(diào)用號定義和參數(shù)傳遞方式,使得 ARM 架構(gòu)在實現(xiàn)系統(tǒng)調(diào)用時,能夠充分發(fā)揮其低功耗、高性能的優(yōu)勢,滿足嵌入式系統(tǒng)等應(yīng)用場景對于系統(tǒng)調(diào)用高效、穩(wěn)定執(zhí)行的需求 。

五、Linux下系統(tǒng)調(diào)用的三種方法

5.1通過 glibc 提供的庫函數(shù)

glibc 是 Linux 下使用的開源的標準 C 庫,它是 GNU 發(fā)布的 libc 庫,即運行時庫。glibc 為程序員提供豐富的 API(Application Programming Interface),除了例如字符串處理、數(shù)學(xué)運算等用戶態(tài)服務(wù)之外,最重要的是封裝了操作系統(tǒng)提供的系統(tǒng)服務(wù),即系統(tǒng)調(diào)用的封裝。那么glibc提供的系統(tǒng)調(diào)用API與內(nèi)核特定的系統(tǒng)調(diào)用之間的關(guān)系是什么呢?

通常情況,每個特定的系統(tǒng)調(diào)用對應(yīng)了至少一個 glibc 封裝的庫函數(shù),如系統(tǒng)提供的打開文件系統(tǒng)調(diào)用 sys_open 對應(yīng)的是 glibc 中的 open 函數(shù);

其次,glibc 一個單獨的 API 可能調(diào)用多個系統(tǒng)調(diào)用,如 glibc 提供的 printf 函數(shù)就會調(diào)用如 sys_open、sys_mmap、sys_write、sys_close 等等系統(tǒng)調(diào)用;

另外,多個 API 也可能只對應(yīng)同一個系統(tǒng)調(diào)用,如glibc 下實現(xiàn)的 malloc、calloc、free 等函數(shù)用來分配和釋放內(nèi)存,都利用了內(nèi)核的 sys_brk 的系統(tǒng)調(diào)用。

舉例來說,我們通過 glibc 提供的chmod 函數(shù)來改變文件 etc/passwd 的屬性為 444:

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>

int main()
{
        int rc;

        rc = chmod("/etc/passwd", 0444);
        if (rc == -1)
                fprintf(stderr, "chmod failed, errno = %d\n", errno);
        else
                printf("chmod success!\n");
        return 0;
}

在普通用戶下編譯運用,輸出結(jié)果為:

chmod failed, errno = 1

上面系統(tǒng)調(diào)用返回的值為-1,說明系統(tǒng)調(diào)用失敗,錯誤碼為1,在 /usr/include/asm-generic/errno-base.h 文件中有如下錯誤代碼說明:

#define EPERM       1                /* Operation not permitted */

即無權(quán)限進行該操作,我們以普通用戶權(quán)限是無法修改 /etc/passwd 文件的屬性的,結(jié)果正確。

5.2使用 syscall 直接調(diào)用

使用上面的方法有很多好處,首先你無須知道更多的細節(jié),如 chmod 系統(tǒng)調(diào)用號,你只需了解 glibc 提供的 API 的原型;其次,該方法具有更好的移植性,你可以很輕松將該程序移植到其他平臺,或者將 glibc 庫換成其它庫,程序只需做少量改動。

但有點不足是,如果 glibc 沒有封裝某個內(nèi)核提供的系統(tǒng)調(diào)用時,我就沒辦法通過上面的方法來調(diào)用該系統(tǒng)調(diào)用。如我自己通過編譯內(nèi)核增加了一個系統(tǒng)調(diào)用,這時 glibc 不可能有你新增系統(tǒng)調(diào)用的封裝 API,此時我們可以利用 glibc 提供的syscall 函數(shù)直接調(diào)用。該函數(shù)定義在 unistd.h 頭文件中,函數(shù)原型如下:

long int syscall (long int sysno, ...)

sysno 是系統(tǒng)調(diào)用號,每個系統(tǒng)調(diào)用都有唯一的系統(tǒng)調(diào)用號來標識。在 sys/syscall.h 中有所有可能的系統(tǒng)調(diào)用號的宏定義。

... 為剩余可變長的參數(shù),為系統(tǒng)調(diào)用所帶的參數(shù),根據(jù)系統(tǒng)調(diào)用的不同,可帶0~5個不等的參數(shù),如果超過特定系統(tǒng)調(diào)用能帶的參數(shù),多余的參數(shù)被忽略。

返回值 該函數(shù)返回值為特定系統(tǒng)調(diào)用的返回值,在系統(tǒng)調(diào)用成功之后你可以將該返回值轉(zhuǎn)化為特定的類型,如果系統(tǒng)調(diào)用失敗則返回 -1,錯誤代碼存放在 errno 中。

還以上面修改 /etc/passwd 文件的屬性為例,這次使用 syscall 直接調(diào)用:

#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>

int main()
{
        int rc;
        rc = syscall(SYS_chmod, "/etc/passwd", 0444);

        if (rc == -1)
                fprintf(stderr, "chmod failed, errno = %d\n", errno);
        else
                printf("chmod succeess!\n");
        return 0;
}

在普通用戶下編譯執(zhí)行,輸出的結(jié)果與上例相同。

5.3通過 int 指令陷入

如果我們知道系統(tǒng)調(diào)用的整個過程的話,應(yīng)該就能知道用戶態(tài)程序通過軟中斷指令int 0x80 來陷入內(nèi)核態(tài)(在Intel Pentium II 又引入了sysenter指令),參數(shù)的傳遞是通過寄存器,eax 傳遞的是系統(tǒng)調(diào)用號,ebx、ecx、edx、esi和edi 來依次傳遞最多五個參數(shù),當(dāng)系統(tǒng)調(diào)用返回時,返回值存放在 eax 中。

仍然以上面的修改文件屬性為例,將調(diào)用系統(tǒng)調(diào)用那段寫成內(nèi)聯(lián)匯編代碼:

#include <stdio.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <errno.h>

int main()
{
        long rc;
        char *file_name = "/etc/passwd";
        unsigned short mode = 0444;

        asm(
                "int $0x80"
                : "=a" (rc)
                : "0" (SYS_chmod), "b" ((long)file_name), "c" ((long)mode)
        );

        if ((unsigned long)rc >= (unsigned long)-132) {
                errno = -rc;
                rc = -1;
        }

        if (rc == -1)
                fprintf(stderr, "chmode failed, errno = %d\n", errno);
        else
                printf("success!\n");

        return 0;
}

如果 eax 寄存器存放的返回值(存放在變量 rc 中)在 -1~-132 之間,就必須要解釋為出錯碼(在/usr/include/asm-generic/errno.h 文件中定義的最大出錯碼為 132),這時,將錯誤碼寫入 errno 中,置系統(tǒng)調(diào)用返回值為 -1;否則返回的是 eax 中的值。

上面程序在 32位Linux下以普通用戶權(quán)限編譯運行結(jié)果與前面兩個相同!

六、系統(tǒng)調(diào)用的應(yīng)用場景與實例分析

6.1Linux下系統(tǒng)調(diào)用的實現(xiàn)

Linux下的系統(tǒng)調(diào)用是通過0x80實現(xiàn)的,但是我們知道操作系統(tǒng)會有多個系統(tǒng)調(diào)用(Linux下有319個系統(tǒng)調(diào)用),而對于同一個中斷號是如何處理多個不同的系統(tǒng)調(diào)用的?最簡單的方式是對于不同的系統(tǒng)調(diào)用采用不同的中斷號,但是中斷號明顯是一種稀缺資源,Linux顯然不會這么做;還有一個問題就是系統(tǒng)調(diào)用是需要提供參數(shù),并且具有返回值的,這些參數(shù)又是怎么傳遞的?也就是說,對于系統(tǒng)調(diào)用我們要搞清楚兩點:

  • 1. 系統(tǒng)調(diào)用的函數(shù)名稱轉(zhuǎn)換。
  • 2. 系統(tǒng)調(diào)用的參數(shù)傳遞。

首先看第一個問題。實際上,Linux中處理系統(tǒng)調(diào)用的方式與中斷類似。每個系統(tǒng)調(diào)用都有相應(yīng)的系統(tǒng)調(diào)用號作為唯一的標識,內(nèi)核維護一張系統(tǒng)調(diào)用表,表中的元素是系統(tǒng)調(diào)用函數(shù)的起始地址,而系統(tǒng)調(diào)用號就是系統(tǒng)調(diào)用在調(diào)用表的偏移量。在進行系統(tǒng)調(diào)用是只要指定對應(yīng)的系統(tǒng)調(diào)用號,就可以明確的要調(diào)用哪個系統(tǒng)調(diào)用,這就完成了系統(tǒng)調(diào)用的函數(shù)名稱的轉(zhuǎn)換。舉例來說,Linux中fork的調(diào)用號是2(具體定義,在我的計算機上是在/usr/include/asm/unistd_32.h,可以通過find / -name unistd_32.h -print查找)

[cpp] view plain copy  
#ifndef _ASM_X86_UNISTD_32_H  
#define _ASM_X86_UNISTD_32_H  

/* 
 * This file contains the system call numbers. 
 */  

#define __NR_restart_syscall      0  
#define __NR_exit                 1  
#define __NR_fork                 2  
#define __NR_read                 3  
#define __NR_write                4  
#define __NR_open                 5

Linux中是通過寄存器%eax傳遞系統(tǒng)調(diào)用號,所以具體調(diào)用fork的過程是:將2存入%eax中,然后進行系統(tǒng)調(diào)用,偽代碼:

[plain] view plain copy  
mov     eax, 2  
int     0x80

對于參數(shù)傳遞,Linux是通過寄存器完成的。Linux最多允許向系統(tǒng)調(diào)用傳遞6個參數(shù),分別依次由%ebx,%ecx,%edx,%esi,%edi和%ebp這個6個寄存器完成。比如,調(diào)用exit(1),偽代碼是:

[plain] view plain copy  
mov    eax, 2  
mov    ebx, 1  
int    0x80

因為exit需要一個參數(shù)1,所以這里只需要使用ebx。這6個寄存器可能已經(jīng)被使用,所以在傳參前必須把當(dāng)前寄存器的狀態(tài)保存下來,待系統(tǒng)調(diào)用返回后再恢復(fù),這個在后面棧切換再具體講。

Linux中,在用戶態(tài)和內(nèi)核態(tài)運行的進程使用的棧是不同的,分別叫做用戶棧和內(nèi)核棧,兩者各自負責(zé)相應(yīng)特權(quán)級別狀態(tài)下的函數(shù)調(diào)用。當(dāng)進行系統(tǒng)調(diào)用時,進程不僅要從用戶態(tài)切換到內(nèi)核態(tài),同時也要完成棧切換,這樣處于內(nèi)核態(tài)的系統(tǒng)調(diào)用才能在內(nèi)核棧上完成調(diào)用。系統(tǒng)調(diào)用返回時,還要切換回用戶棧,繼續(xù)完成用戶態(tài)下的函數(shù)調(diào)用。

寄存器%esp(棧指針,指向棧頂)所在的內(nèi)存空間叫做當(dāng)前棧,比如%esp在用戶空間則當(dāng)前棧就是用戶棧,否則是內(nèi)核棧。棧切換主要就是%esp在用戶空間和內(nèi)核空間間的來回賦值。在Linux中,每個進程都有一個私有的內(nèi)核棧,當(dāng)從用戶棧切換到內(nèi)核棧時,需完成保存%esp以及相關(guān)寄存器的值(%ebx,%ecx...)并將%esp設(shè)置成內(nèi)核棧的相應(yīng)值。

而從內(nèi)核棧切換會用戶棧時,需要恢復(fù)用戶棧的%esp及相關(guān)寄存器的值以及保存內(nèi)核棧的信息。一個問題就是用戶棧的%esp和寄存器的值保存到什么地方,以便于恢復(fù)呢?答案就是內(nèi)核棧,在調(diào)用int指令機型系統(tǒng)調(diào)用后會把用戶棧的%esp的值及相關(guān)寄存器壓入內(nèi)核棧中,系統(tǒng)調(diào)用通過iret指令返回,在返回之前會從內(nèi)核棧彈出用戶棧的%esp和寄存器的狀態(tài),然后進行恢復(fù)。

相信大家一定聽過說,系統(tǒng)調(diào)用很耗時,要盡量少用。通過上面描述系統(tǒng)調(diào)用的實現(xiàn)原理,大家也應(yīng)該知道這其中的原因了。

  • 第一,系統(tǒng)調(diào)用通過中斷實現(xiàn),需要完成棧切換。
  • 第二,使用寄存器傳參,這需要額外的保存和恢復(fù)的過程。

6.2文件操作相關(guān)系統(tǒng)調(diào)用

在 Linux 系統(tǒng)中,文件操作是日常使用和開發(fā)中極為常見的任務(wù),而 open、read、write、close 等系統(tǒng)調(diào)用則是實現(xiàn)文件操作的核心工具。

open 系統(tǒng)調(diào)用用于打開或創(chuàng)建一個文件,它的函數(shù)原型為int open(const char *pathname, int flags, mode_t mode);。其中,pathname是要打開或創(chuàng)建的文件的路徑名;flags參數(shù)用于指定文件的打開方式,比如O_RDONLY表示以只讀方式打開,O_WRONLY表示以只寫方式打開,O_RDWR則表示以讀寫方式打開,還有一些可選的標志位,如O_CREAT表示如果文件不存在則創(chuàng)建新文件,O_APPEND表示以追加方式寫入文件等;mode參數(shù)在創(chuàng)建新文件時用于指定文件的訪問權(quán)限,如S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH表示文件所有者具有讀寫權(quán)限,同組用戶和其他用戶具有讀權(quán)限 。

read 系統(tǒng)調(diào)用用于從文件中讀取數(shù)據(jù),函數(shù)原型是ssize_t read(int fd, void *buf, size_t count);,fd是文件描述符,它是 open 系統(tǒng)調(diào)用成功返回的一個非負整數(shù),用于標識打開的文件;buf是用于存儲讀取數(shù)據(jù)的緩沖區(qū)指針;count表示期望讀取的字節(jié)數(shù),該函數(shù)返回實際讀取到的字節(jié)數(shù) 。

write 系統(tǒng)調(diào)用則用于向文件中寫入數(shù)據(jù),其函數(shù)原型為ssize_t write(int fd, const void *buf, size_t count);,參數(shù)含義與 read 類似,fd為文件描述符,buf是要寫入數(shù)據(jù)的緩沖區(qū)指針,count是要寫入的字節(jié)數(shù),返回值是實際寫入的字節(jié)數(shù) 。

close 系統(tǒng)調(diào)用用于關(guān)閉一個打開的文件,函數(shù)原型為int close(int fd);,fd為要關(guān)閉的文件描述符,關(guān)閉成功返回 0,失敗返回 - 1 。

以下是一個簡單的 C 語言代碼示例,展示了如何使用這些系統(tǒng)調(diào)用實現(xiàn)文件的讀取和寫入操作:

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

#define BUFFER_SIZE 1024

int main() {
    int source_fd, destination_fd;
    ssize_t bytes_read, bytes_written;
    char buffer[BUFFER_SIZE];

    // 打開源文件,以只讀方式
    source_fd = open("source.txt", O_RDONLY);
    if (source_fd == -1) {
        perror("無法打開源文件");
        return 1;
    }

    // 創(chuàng)建目標文件,以讀寫方式,如果文件不存在則創(chuàng)建,權(quán)限設(shè)置為所有者可讀可寫,其他用戶可讀
    destination_fd = open("destination.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    if (destination_fd == -1) {
        perror("無法創(chuàng)建目標文件");
        close(source_fd);
        return 1;
    }

    // 從源文件讀取數(shù)據(jù)并寫入目標文件
    while ((bytes_read = read(source_fd, buffer, BUFFER_SIZE)) > 0) {
        bytes_written = write(destination_fd, buffer, bytes_read);
        if (bytes_written != bytes_read) {
            perror("寫入目標文件失敗");
            close(source_fd);
            close(destination_fd);
            return 1;
        }
    }

    if (bytes_read == -1) {
        perror("讀取源文件失敗");
    }

    // 關(guān)閉文件
    close(source_fd);
    close(destination_fd);

    return 0;
}

在這個示例中,首先使用 open 系統(tǒng)調(diào)用以只讀方式打開名為source.txt的源文件,如果打開失敗,通過perror函數(shù)輸出錯誤信息并返回 1。接著,使用 open 系統(tǒng)調(diào)用以讀寫方式創(chuàng)建名為destination.txt的目標文件,如果文件已存在則截斷文件內(nèi)容,如果創(chuàng)建失敗同樣輸出錯誤信息并關(guān)閉已打開的源文件后返回 1 。

然后,通過一個循環(huán),使用 read 系統(tǒng)調(diào)用從源文件中讀取數(shù)據(jù)到緩沖區(qū)buffer中,每次最多讀取BUFFER_SIZE個字節(jié)。只要讀取到的數(shù)據(jù)長度大于 0,就使用 write 系統(tǒng)調(diào)用將緩沖區(qū)中的數(shù)據(jù)寫入目標文件。如果寫入的字節(jié)數(shù)與讀取的字節(jié)數(shù)不一致,說明寫入失敗,輸出錯誤信息并關(guān)閉兩個文件后返回 1 。

如果在讀取過程中bytes_read等于 - 1,說明讀取失敗,輸出錯誤信息。最后,使用 close 系統(tǒng)調(diào)用分別關(guān)閉源文件和目標文件,完成文件操作 。

通過這個示例,我們可以清晰地看到 open、read、write、close 系統(tǒng)調(diào)用在文件讀寫操作中的具體應(yīng)用和執(zhí)行流程,它們相互配合,實現(xiàn)了高效、準確的文件數(shù)據(jù)傳輸和管理 。

6.3進程管理相關(guān)系統(tǒng)調(diào)用

在 Linux 系統(tǒng)中,進程管理是操作系統(tǒng)的核心功能之一,fork、exec、wait 等系統(tǒng)調(diào)用在進程的創(chuàng)建、執(zhí)行和等待過程中發(fā)揮著關(guān)鍵作用。

fork 系統(tǒng)調(diào)用用于創(chuàng)建一個新的進程,稱為子進程,它的函數(shù)原型為pid_t fork(void);。調(diào)用 fork 后,系統(tǒng)會創(chuàng)建一個與原進程(父進程)幾乎完全相同的子進程,子進程復(fù)制了父進程的代碼段、數(shù)據(jù)段、堆棧段等資源。但父子進程也有一些不同之處,它們擁有不同的進程 ID(PID),通過getpid()函數(shù)可以獲取當(dāng)前進程的 PID,通過getppid()函數(shù)可以獲取父進程的 PID 。fork 函數(shù)的返回值非常特殊,在父進程中,返回值是新創(chuàng)建子進程的 PID;在子進程中,返回值為 0;如果創(chuàng)建子進程失敗,返回值為 - 1 。

exec 系統(tǒng)調(diào)用并不是一個單獨的函數(shù),而是一組函數(shù),如execl、execv、execle、execve等,它們的主要作用是在當(dāng)前進程中啟動另一個程序。當(dāng)進程調(diào)用 exec 函數(shù)時,會用新的程序替換當(dāng)前進程的正文、數(shù)據(jù)、堆和棧段,也就是說,當(dāng)前進程會被新的程序完全取代,從新程序的main函數(shù)開始執(zhí)行。由于 exec 并不創(chuàng)建新進程,所以前后的進程 ID 并未改變 。

wait 系統(tǒng)調(diào)用用于等待子進程的結(jié)束,并獲取子進程的退出狀態(tài),函數(shù)原型為pid_t wait(int *status);。status是一個指向整數(shù)的指針,用于存儲子進程的退出狀態(tài)信息。調(diào)用 wait 后,父進程會阻塞,直到有一個子進程結(jié)束,此時 wait 返回結(jié)束子進程的 PID,并將子進程的退出狀態(tài)存儲在status指向的變量中 。

下面通過一個簡單的代碼示例來說明進程創(chuàng)建和父子進程的執(zhí)行流程:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main() {
    pid_t pid;
    int status;

    // 創(chuàng)建子進程
    pid = fork();
    if (pid == -1) {
        perror("fork失敗");
        exit(1);
    } else if (pid == 0) {
        // 子進程執(zhí)行的代碼
        printf("我是子進程,我的PID是 %d,父進程的PID是 %d\n", getpid(), getppid());
        // 子進程執(zhí)行另一個程序,這里以執(zhí)行l(wèi)s命令為例
        execl("/bin/ls", "ls", "-l", NULL);
        perror("execl失敗");
        exit(1);
    } else {
        // 父進程執(zhí)行的代碼
        printf("我是父進程,我的PID是 %d,子進程的PID是 %d\n", getpid(), pid);
        // 父進程等待子進程結(jié)束
        wait(&status);
        printf("子進程已結(jié)束,退出狀態(tài)為 %d\n", WEXITSTATUS(status));
    }

    return 0;
}

在這個示例中,首先調(diào)用 fork 系統(tǒng)調(diào)用創(chuàng)建子進程。如果 fork 返回 - 1,說明創(chuàng)建子進程失敗,通過perror函數(shù)輸出錯誤信息并調(diào)用exit函數(shù)退出程序 。

如果 fork 返回 0,說明當(dāng)前是子進程,子進程打印自己的 PID 和父進程的 PID,然后調(diào)用execl函數(shù)執(zhí)行/bin/ls -l命令,列出當(dāng)前目錄下的文件詳細信息。如果execl執(zhí)行失敗,同樣輸出錯誤信息并退出 。

如果 fork 返回一個大于 0 的值,說明當(dāng)前是父進程,父進程打印自己的 PID 和子進程的 PID,然后調(diào)用 wait 系統(tǒng)調(diào)用等待子進程結(jié)束。當(dāng)子進程結(jié)束后,wait 返回,父進程獲取子進程的退出狀態(tài),并打印子進程已結(jié)束以及其退出狀態(tài) 。

通過這個示例,我們可以清楚地看到 fork、exec、wait 系統(tǒng)調(diào)用在進程管理中的協(xié)同工作,fork 用于創(chuàng)建新進程,exec 用于在子進程中執(zhí)行新程序,wait 用于父進程等待子進程結(jié)束并獲取其退出狀態(tài),它們共同構(gòu)成了 Linux 系統(tǒng)強大的進程管理機制 。

責(zé)任編輯:武曉燕 來源: 深度Linux
相關(guān)推薦

2011-01-11 16:11:03

2023-02-10 08:11:43

Linux系統(tǒng)調(diào)用

2025-06-03 04:00:05

glibc堆內(nèi)存Linux

2025-01-24 08:19:57

2024-03-27 10:14:48

2025-05-30 02:00:00

Spring接口限流

2016-05-18 17:15:17

互動出版網(wǎng)

2025-02-12 00:29:58

2024-09-30 09:13:14

協(xié)調(diào)通信機制

2024-12-18 21:37:24

2013-03-26 13:55:45

Android Bro

2025-05-09 01:30:00

JavaScript事件循環(huán)基石

2023-11-26 18:02:00

ReactDOM

2009-12-11 10:29:03

PHP插件機制

2023-10-13 13:30:00

MySQL鎖機制

2010-10-08 10:42:30

2021-09-13 09:40:35

Webpack 前端HMR 原理

2025-05-29 08:35:00

Nacos服務(wù)注冊開發(fā)

2023-09-18 11:34:17

Linux系統(tǒng)

2010-02-06 09:38:42

Android調(diào)用服務(wù)
點贊
收藏

51CTO技術(shù)棧公眾號