進程間通信IPC(二)(共享內存、信號、信號量)

共享內存:
共享內存就是允許兩個或多個進程共享一定的存儲區。就如同 malloc() 函數向不同進程返回了指向同一個物理內存區域的指針。當一個進程改變了這塊地址中的內容的時候,其它進程都會察覺到這個更改。因為數據不需要在客戶機和服務器端之間復制,數據直接寫到內存,不用若干次數據拷貝,所以這是最快的一種IPC。

注意:
共享內存并未提供同步機制,也就是說,在第一個進程結束對共享內存的寫操作之前,并無自動機制可以阻止第二個進程開始對它進行讀取。所以我們通常需要用其他的機制來同步對共享內存的訪問,例如:信號量。
在這里插入圖片描述
共享內存的優點:
當中共享內存的大致原理相信我們可以看明白了,就是讓兩個進程地址通過頁表映射到同一片物理地址以便于通信,你可以給一個區域里面寫入數據,理所當然你就可以從中拿取數據,這也就構成了進程間的雙向通信,而且共享內存是IPC通信當中傳輸速度最快的通信方式沒有之一,理由很簡單,客戶進程和服務進程傳遞的數據直接從內存里存取、放入,數據不需要在兩進程間復制,沒有什么操作比這簡單了。再者用共享內存進行數據通信,它對數據也沒啥限制。

最后就是共享內存的生命周期隨內核。即所有訪問共享內存區域對象的進程都已經正常結束,共享內存區域對象仍然在內核中存在(除非顯式刪除共享內存區域對象),在內核重新引導之前,對該共享內存區域對象的任何改寫操作都將一直保留;簡單地說,共享內存區域對象的生命周期跟系統內核的生命周期是一致的,而且共享內存區域對象的作用域范圍就是在整個系統內核的生命周期之內。

共享內存的缺陷:
共享內存也并不完美,共享內存并未提供同步機制,也就是說,在一個服務進程結束對共享內存的寫操作之前,并沒有自動機制可以阻止另一個進程(客戶進程)開始對它進行讀取。這明顯還達不到我們想要的,我們不單是在兩進程間交互數據,還想實現多個進程對共享內存的同步訪問,這也正是使用共享內存的竅門所在。基于此,我們通常會用平時常談到和用到 信號量來實現對共享內存同步訪問控制。

操作共享內存的一般步驟:

  • 創建或打開共享內存
  • 將進程地址通過頁表映射到物理地址
  • 進行數據交換
  • 釋放共享內存

與共享內存有關的函數:

函數參數講解

shmget()函數:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);功能;創建或獲取一個共享內存
參數:
(1)第一個參數key是長整型(唯一非零),系統建立IPC通訊 ( 消息隊列、 信號量和 共享內存) 時必須指定一個ID值。通常情況下,該id值通過ftok函數得到,由內核變成標識符,要想讓兩個進程看到同一個信號集,只需設置key值不變就可以。
(2)第二個參數size指定共享內存的大小,它的值一般為一頁大小(4K也就是4M)的整數倍(未到一頁,操作系統向上對齊到一頁,但是用戶實際能使用只有自己所申請的大小)。
(3)第三個參數shmflg是一組標志,創建一個新的共享內存,將shmflg 設置了IPC_CREAT標志后,共享內存存在就打開。而IPC_CREAT | IPC_EXCL則可以創建一個新的,唯一的共享內存,如果共享內存已存在,返回一個錯誤。一般我們會還或上一個文件權限.返回值:成功返回共享內存的ID,出錯返回-1      

shmat()函數:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
void *shmat(int shm_id, const void *shm_addr, int shmflg);
功能:創建共享存儲段之后,將進程連接到它的地址空間,就是將進程地址映射到物理地址
參數:(1)第一個參數,shm_id是由shmget函數返回的共享內存標識。(2)第二個參數,shm_addr指定共享內存連接到當前進程中的地址位置,通常為空,表示讓系統來選擇共享內存的地址。一般為0,表示連接到由內核選擇的第一個可用地址上,否則,如果flag沒有指定SHM_RND,則連接到addr所指定的地址上,如果flag為SHM_RND,則地址取整 (3)第三個參數,shm_flg是一組標志位,通常為0,若指定了SHM_RDONLY則以只讀方式連接此段,否則以只寫方式連接此段。
返回值:成功返回指向共享存儲段的指針,并且內核將使其與該共享存儲段相關的shnid_ds結構中的shm_nattch計數器加一(類似于引用計數)出錯返回-1 

shmctl()函數:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shm_id, int cmd, struct shmid_ds *buf); 
功能:操作共享內存
參數:(1)第一個參數,shm_id是shmget函數返回的共享內存標識符。(2)第二個參數,cmd是要采取的操作,它可以取下面的三個值 :    IPC_STAT:把shmid_ds結構中的數據設置為共享內存的當前關聯值,即用共享內存的當前關聯值覆蓋shmid_ds的值。    IPC_SET:如果進程有足夠的權限,就把共享內存的當前關聯值設置為shmid_ds結構中給出的值    IPC_RMID:刪除共享內存段(3)第三個參數,buf是一個結構指針,它指向共享內存模式和訪問權限的結構。(如果第二個參數是IPC_RMID,則這個參數返回的是結束共享內存時的一些信息,不關心可以設為NULL)shmid_ds結構至少包括以下成員:struct shmid_ds { uid_t shm_perm.uid; uid_t shm_perm.gid; mode_t shm_perm.mode; };  
返回值:調用成功時返回一個指向共享內存第一個字節的指針,如果調用失敗返回-1.

shmdt()函數:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr); 
功能:該函數用于將共享內存從當前進程中分離。注意,將共享內存分離并不是刪除它,只是使該共享內存對當前進程不再可用。該操作不從系統中刪除標識符和其數據結構,要顯示調用shmctl(帶命令IPC_RMID)才能刪除它。
參數:(1)addr參數是以前調用shmat時的返回值
返回值:成功返回0,出錯返回-1

發送端小demo:

#include <sys/shm.h>
#include<string.h>
#include <unistd.h>
#include<stdlib.h>
int main()
{key_t key;int shmid;char* atreturn;key=ftok(".",6);shmid=shmget(key,1024*4,IPC_CREAT|0666);if(shmid==-1){printf("creat/open fail\n");perror("why");exit(-1);}atreturn=shmat(shmid,0,0);if(!strcmp(atreturn,"-1")){printf("at fail\n");exit(-1);}strcpy(atreturn,"你好");sleep(5);shmdt(atreturn);shmctl(shmid,IPC_RMID,NULL);printf("quite\n");return 0;
}

接收端小demo:

#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<string.h>
#include <unistd.h>
#include<stdlib.h>
int main()
{key_t key;int shmid;char* atreturn;key=ftok(".",6);shmid=shmget(key,1024*4,IPC_CREAT|0666);if(shmid==-1){printf("creat/open fail\n");perror("why");exit(-1);}atreturn=shmat(shmid,0,0);if(!strcmp(atreturn,"-1")){printf("at fail\n");exit(-1);}printf("讀取到:%s\n",atreturn);shmdt(atreturn);return 0;
}

以上兩個demo沒有用到信號量對共享內存進行控制,接下來將介紹信號和信號量

信號的基本概念

什么是信號?

  • 日常生活中,當我們走到馬路上時,看到的綠燈是一種信號,它能提示我們怎樣安全的過馬路。又比如,新學期開始學校給每個班發的課表也是一種信號,它能提示同學們在適當的時間地點去上相應的課程而不是虛度光陰……生活中其實我們忽略了很多信號,正是由于這些信號的存在,才使得我們的生活方便而有序。
    總結一下你會發現信號是什么,信號就是當你看到它是知道它是什么,并且知道看到信號之后應該做什么,至于你遵不遵守就是你自己的事了,計算機中的信號也不例外。
  • 計算機中的信號
    同日常生活中的信號一樣,計算機在收到信號之后,并不一定會立即處理它,它會將收到的信號記錄在其相應進程的PCB中的信號部分,等待合適的時間再去處理它。換句話說,一個進程是否收到信號,需要查看其進程PCB中的信號信息,給進程發信號實則是向進程PCB中寫入信號信息。同時,我們的操作系統是很智能的,當任何一個進程接收到任何一個信號時,操作系統會自動地知道各信號應作何處理。對于linux來說實際信號就是軟中斷。

信號概述:

  • 信號的名字和編號:
    每個信號都有一個名字和編號,這些名字都以“SIG”開頭,例如“SIGIO ”、“SIGCHLD”等等。
    信號定義在signal.h頭文件中,信號名都定義為正整數。
    具體的信號名稱可以使用kill -l來查看信號的名字以及序號,信號是從1開始編號的,不存在0號信號。kill對于信號0又特殊的應用。

    在這里插入圖片描述

  • SIGHUP是掛起的意思、SIGINT是中斷的意思就是ctrl+c的信號、SIGQUIT是退出的意思、SIGILL是出現問題的意思、SIGABRT是丟棄的意思、SIGBUS總線信號、SIGKILL殺死進程信號、SIGALRM是鬧鐘信號、SIGSTOP是停止程序信號…等等

信號的處理有三種方法,分別是:忽略、捕捉和默認動作:

  • 忽略信號,大多數信號可以使用這個方式來處理,但是有兩種信號不能被忽略(分別是SIGKILL和SIGSTOP)。因為他們向內核和超級用戶提供了進程終止和停止的可靠方法,如果忽略了,那么這個進程就變成了沒人能管理的的進程,顯然是內核設計者不希望看到的場景。
  • 捕捉信號,需要告訴內核,用戶希望如何處理某一種信號,說白了就是寫一個信號處理函數,然后將這個函數告訴內核。當該信號產生時,由內核來調用用戶自定義的函數,以此來實現某種信號的處理。
  • 系統默認動作,對于每個信號來說,系統都對應由默認的處理動作,當發生了該信號,系統會自動執行。具體的信號默認動作可以使用man 7 signal來查看系統的具體定義。
  • 可以通過kill -9 加進程號殺死進程

信號處理函數的注冊:

  • 入門版:函數signal
  • 高級版:函數sigaction

信號發送函數:

  • 入門版:kill
  • 高級版:sigqueue

低級版本:

signal()函數:

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
參數:使用SIG_IGN這個宏就是表示將信號忽略掉參數1:我們要進行處理的信號。系統的信號我們可以再終端鍵入 kill -l查看(64)。其實這些信號時系統定義的宏。參數2:我們處理的方式(是系統默認還是忽略還是捕獲)。handler函數的參數就是第一個參數signum,就是捕獲到的信號。這個參數要求是指向函數的指針,就是函數名。
返回值:它返回一個函數指針這里的typedef就是定義了一種函數指針sighandler_t。typedef在語句中有變量的功能,也有定義類型的功能,如果typedef后面只跟了一個新的類型那么它就是定義類型的功能

關于typedef void ( * sighandler_t)(int)的理解

signal使用示例:

#include<stdio.h>
#include <signal.h>
void handler(int sig)
{switch(sig){case 2:printf("接受到ctrl+c\n");break;case 9:printf("接受到kill指令\n");break;case 10:printf("接受到SIGUSR1指令\n");break;}
}
int main()
{signal(SIGINT,handler);signal(SIGKILL,handler);signal(SIGUSR1,handler);while(1);return 0;
}
程序可以捕獲ctrl+c(SIGINI)、SIGUSR1和SIGKILL指令
捕獲SIGINI和SIGUSR1指令后程序不退出,但是捕獲SIGKILL
指令后程序會強制退出。kill -9 +進程ID  表示:將序號9信號(SIGKILL)發送給進程號為ID的進程
kill -10 +進程ID  表示:將序號10信號(SIGUSR1)發送給進程號為ID的進程
kil +進程ID  表示:殺死該進程

kill()函數:

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);函數參數:pid:指定進程的進程ID,注意用戶的權限,比如普通用戶不可以殺死1號進程(init)。pid>0:發送信號給指定進程pid=0:發送信號給與調用kill函數進程屬于同一進程組的所有進程pid<0:發送信號給pid絕對值對應的進程組pid=-1:發送給進程有權限發送的系統中的所有進程
sig參數:建議使用信號名(宏名)而不是信號編號,因為涉及到跨平臺的程序時,可能因為不同平臺信號編號不同會導致錯誤。
返回值說明: 成功執行時,返回0。失敗返回-1,errno被設為以下的某個值 EINVAL:指定的信號碼無效(參數 sig 不合法) EPERM;權限不夠無法傳送信號給指定進程 ESRCH:參數 pid 所指定的進程或進程組不存在

atoi()函數:

#include <stdlib.h>
int atoi(const char *nptr);//字符串轉int
long atol(const char *nptr);//字符串轉long int
long long atoll(const char *nptr);//字符串轉long long int

命令發送程序:

#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
#include<string.h>
int main(int argc,char* argv[])
{int ret;char* cmd;cmd=(char*)malloc(128);if(argc!=3){printf("輸入有誤,請重新輸入\n");}//int id=atoi(argv[2]);//int sig=atoi(argv[1]);/*ret=kill(pid,sig);if(ret>0){printf("命令發送成功\n");}*/printf("sig:%s,id:%s\n",argv[1],argv[2]);sprintf(cmd,"kill -%s %s",argv[1],argv[2]);printf("%s\n",cmd);system(cmd);return 0;
}

高級版本:

發信號思考:

  • 用什么發信號,sigqueue()函數
  • 既然都已經把信號發送過去了,為何不能再攜帶一些數據呢?
  • 怎么將內容放入信號

讀信號思考

  • 用什么函數收消息并處理接收的信號,這里用到的是sigaction() 函數
  • 怎么讀取信號所帶的內容

sigaction()函數:

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);第一個參數:signum參數指出要捕獲的信號類型
第二個參數:是struct sigaction類型的結構體指針,act參數,指定新的信號處理方式
第三個參數:也是struct sigaction類型的結構體指針,oldact參數,記錄原來對信號的處理方式如果不為空,那么可以對之前的信號配置進行備份,以方便之后進行恢復。
返回值:0 表示成功,-1 表示有錯誤發生。錯誤原因存于error中struct sigaction {void       (*sa_handler)(int); //信號處理程序,不接受額外數據,SIG_IGN 為忽略,SIG_DFL 為默認動作void       (*sa_sigaction)(int, siginfo_t *, void *); //信號處理程序,能夠接受額外數據和sigqueue配合使用sigset_t   sa_mask;//阻塞關鍵字的信號集,可以再調用捕捉函數之前,把信號添加到信號阻塞字,信號捕捉函數返回之前恢復為原先的值。int        sa_flags;//影響信號的行為SA_SIGINFO表示能夠接受數據};在這個結構體中,成員 sa_handler 是一個函數指針,其含義與 signal 函數中的信號處理函數類似。成員:sa_sigaction 則是另一個信號處理函數,它有三個參數,可以獲得關于信號的更詳細的信息。sa_flags 成員的值:1、包含了 SA_SIGINFO 標志時,系統將使用 sa_sigaction 函數作為信號處理函數,否則使用 sa_handler 作為信號處理函數。在某些系統中,成員 sa_handler 與 sa_sigaction 被放在聯合體中,因此使用時不要同時設置。2、sa_mask 成員用來指定在信號處理函數執行期間需要被屏蔽的信號,特別是當某個信號被處理時它自身會被自動放入進程的信號掩碼,因此在信號處理函數執行期間這個信號不會再度發生。3、re_restorer 成員則是一個已經廢棄的數據域,不要使用。sa_flags 成員用于指定信號處理的行為,它可以是一下值的“按位或”組合。◆SA_RESTART:使被信號打斷的系統調用自動重新發起。◆ SA_NOCLDSTOP:使父進程在它的子進程暫停或繼續運行時不會收到 SIGCHLD 信號。◆ SA_NOCLDWAIT:使父進程在它的子進程退出時不會收到 SIGCHLD 信號,這時子進程如果退出也不會成為僵尸進程。◆ SA_NODEFER:使對信號的屏蔽無效,即在信號處理函數執行期間仍能發出這個信號。◆ SA_RESETHAND:信號處理之后重新設置為默認的處理方式。◆ SA_SIGINFO:使用 sa_sigaction 成員而不是 sa_handler 作為信號處理函數。

關于上面提到的void ( * sa_sigaction)(int, siginfo_t * , void * ):

void* 是接收到信號所攜帶的額外數據,通過判斷他是否為0來決定是否進行下面的操作
而struct siginfo這個結構體主要適用于記錄接收信號的一些相關信息。siginfo_t {int      si_signo;    /* Signal number */int      si_errno;    /* An errno value */int      si_code;     /* Signal code */int      si_trapno;   /* Trap number that causedhardware-generated signal(unused on most architectures) */pid_t    si_pid;      /* Sending process ID發送者的PID */uid_t    si_uid;      /* Real user ID of sending process */int      si_status;   /* Exit value or signal */clock_t  si_utime;    /* User time consumed */clock_t  si_stime;    /* System time consumed */sigval_t si_value;    /* Signal value 是聯合體,里面可以是整型,或者字符串*/int      si_int;      /* POSIX.1b signal 這個是整型*/void    *si_ptr;      /* POSIX.1b signal */int      si_overrun;  /* Timer overrun count; POSIX.1b timers */int      si_timerid;  /* Timer ID; POSIX.1b timers */void    *si_addr;     /* Memory location which caused fault */int      si_band;     /* Band event */int      si_fd;       /* File descriptor */
}
其中的成員很多,si_signo 和 si_code 是必須實現的兩個成員。可以通過這個結構體獲取到信號的相關信息。
關于發送過來的數據是存在兩個地方的,sigval_t si_value這個成員中有保存了發送過來的信息
同時,在si_int或者si_ptr成員中也保存了對應的數據。

sigqueue()函數:

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {int   sival_int;void *sival_ptr;//發送字符串只能在 共享內存或者 同一程序下才可以發送};使用這個函數之前,必須要有幾個操作需要完成:
1、使用 sigaction 函數安裝信號處理程序時,制定了 SA_SIGINFO 的標志。
2、sigaction 結構體中的 sa_sigaction 成員提供了信號捕捉函數。如果實現的時 sa_handler 成員,那么將無法獲取額外攜帶的數據。
3、sigqueue 函數只能把信號發送給單個進程,可以使用 value 參數向信號處理程序傳遞整數值或者指針值。sigqueue 函數不但可以發送額外的數據,還可以讓信號進行排隊(操作系統必須實現了 POSIX.1的實時擴展)
對于設置了阻塞的信號,使用 sigqueue 發送多個同一信號,在解除阻塞時,接受者會接收到發送的信號隊列中的信號,而不是直接收到一次。
但是,信號不能無限的排隊,信號排隊的最大值受到SIGQUEUE_MAX的限制,達到最大限制后,sigqueue 會失敗,errno 會被設置為 EAGAIN。

sigqueue()發送信號程序demo:

#include<stdio.h>
#include <signal.h>
#include <stdlib.h>
#include<string.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char*argv[])
{union sigval value;value.sival_int=10;int signum=atoi(argv[1]);int pid=atoi(argv[2]);if(argc!=3){printf("輸入有誤\n");}sigqueue(pid,signum,value);printf("my pid:%d\n",getpid());return 0;
}
注意:
發送時因為需要使用聯合體,那么一次要么發送整型,要么發送字符串
還有一點需要注意:發送字符串只能在 共享內存或者同一程序下才可以發送

sigaction()接收端demo:

#include<stdio.h>
#include <signal.h>
#include<stdlib.h>
void action(int signum,siginfo_t *info,void* context)
{printf("get signum:%d\n",signum);if(context !=NULL){printf("發送命令的進程號是:%d\n",info->si_pid);printf("接受到的整數是:%d\n",info->si_int);printf("接受到si_value的整數是:%d\n",info->si_value.sival_int);}else{printf("沒有接受到數據\n");}
}
int main()
{struct sigaction act;act.sa_flags=SA_SIGINFO;act.sa_sigaction=action;sigaction(SIGUSR1,&act,NULL);while(1);return 0;
}

信號量:

什么是信號量?

  • 信號量(semaphore)與已經介紹過的 IPC 結構不同,它是一個計數器。信號量用于實現進程間的互斥與同步,而不是用于存儲進程間通信數據。為了防止出現因多個程序同時訪問一個共享資源而引發的一系列問題,我們需要一種方法,它可以通過生成并使用令牌來授權,在任一時刻只能有一個執行線程訪問代碼的臨界區域。臨界區域是指執行數據更新的代碼需要獨占式地執行。而信號量就可以提供這樣的一種訪問機制,讓一個臨界區同一時間只有一個線程在訪問它,也就是說信號量是用來調協進程對共享資源的訪問的。
  • 信號量是一個特殊的變量,程序對其訪問都是原子操作,且只允許對它進行等待和發送信息操作。最簡單的信號量是只能取0和1的變量,這也是信號量最常見的一種形式,叫做二進制信號量。而可以取多個正整數的信號量被稱為通用信號量。
  • 臨界資源:多道程序系統中存在許多進程,它們共享各種資源,然而有很多資源一次只能供一個進程使用。一次僅允許一個進程使用的資源稱為臨界資源。許多物理設備都屬于臨界資源,如輸入機、打印機、磁帶機等。
  • 信號量集:所謂信號量集,就是由多個信號量組成的一個數組。作為一個整體,信號量集中的所有信號量使用同一個等待隊列。Linux的信號量集為進程請求多個資源創造了條件。Linux規定,當進程的一個操作需要多個共享資源時,如果只成功獲得了其中的部分資源,那么這個請求即告失敗,進程必須立即釋放所有已獲得資源,以防止形成死鎖。
  • 原子性:保證某個操作是一個整體,要么全部做完,要么根本不做,不會出現第三種狀態。同理,我們用信號量保證我們訪問臨界資源的操作是原子操作,而操作系統會為我們保證信號量的變動也是原子操作。這就是為什么信號量能夠達到互斥的效果。對信號量的操作被稱為P、V操作,這是荷蘭科學家迪杰斯特拉提出的,在荷蘭語里,P是申請資源,V是釋放資源的意思。所以執行P操作,信號量的值會減少,執行V操作,信號量的值會增加。
  • PV操作的含義:PV操作由P操作原語和V操作原語組成(原語是不可中斷的過程),針對信號量進行相應的操作。
  • PV操作的定義:其中S表示信號量的值,P表示P操作,V表示V操作。
  • P(S):①將信號量S的值減1,即進行S = S-1;②如果S < 0,則該進程進入阻塞隊列; ③如果S >= 0, 則該進程繼續執行; ④執行一次P操作其實就是意味請求分配一個資源,所以針對②和③來說就好理解了,當信號量的值小于0,那么就表示沒有可用資源,那么進程就只能進行等待其他擁有該資源的進程釋放資源之后,才能進行執行;當信號量大于0的時候,那么表示還有足夠的資源,所以,當前進程就可以繼續執行;
  • V(S):①將信號量S的值加1,即 S = S + 1; ②如果S > 0,則該進程繼續執行; ③如果S < 0, 則釋放阻塞隊列中的第一個等待信號量的進程; ④執行一次V操作其實就是意味釋放一個資源,所以針對②和③來說就好理解了,當信號量的值大于0,那么就表示有可用資源,那么表示信號量的資源足夠進程進行申請,就不需要將進程進行放入到阻塞隊列中;而當信號量小于0的時候,就表示針對這個信號量,還有其他的進程是已經進行了申請信號量的操作,而只是之前是無法滿足進程獲取資源的,簡單點說,就是表示阻塞隊列中還有其他的進程是執行了P操作,在等待信號量,所以,這樣的話,就講阻塞隊列中的第一個等待信號量的進程進行處理即可;

特點:

  • 信號量用于進程間同步,若要在進程間傳遞數據需要結合共享內存
  • 信號量基于操作系統的PV操作,程序對信號量的操作都是原子操作
  • 每次對信號量的PV操作不僅限于對信號量加1或減1,而且可以加減任意正整數。
  • 支持信號量組

最簡單的信號量是只能取0和1的變量,這也是信號量最常見的一種形式,叫做二進制信號量。而可以取多個正整數的信號量被稱為通用信號量。linux下的信號函數都是在通用的信號數組上進行操作,而不是在一個單一的二值信號量上進行操作。

信號量需要用到的函數:

semget()函數:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
功能:創建一個新的信號量或獲取一個已經存在的信號量的鍵值。
參數:key_t 為整型值,用戶可以自己設定。有兩種情況:1.鍵值是IPC_PRIVATE,該值通常為0,意思就是不能用于毫無關系的 進程間通信。2.鍵值不是IPC_PRIVATE,我們可以指定鍵值,例如1234;也可以一個ftok()函數來取得一個唯一的鍵值。nsems 表示初始化信號量集中信號量的個數。比如我們要創建一個信號量,則該值為1.,創建2個就是2。semflg:信號量的創建方式或權限。有IPC_CREAT,IPC_EXCL。IPC_CREAT如果信號量不存在,則創建一個信號量,否則獲取。IPC_EXCL只有信號量不存在的時候,新的信號量才建立,否則就產生錯誤。返回值:成功返回信號量的標識碼ID。失敗返回-1如果用semget創建了一個新的信號量集對象時,則semid_ds結構成員變量的值設置如下:sem_otime設置為0。sem_ctime設置為當前時間。msg_qbytes設成系統的限制值。sem_nsems設置為nsems參數的數值。semflg的讀寫權限寫入sem_perm.mode中。sem_perm結構的uid和cuid成員被設置成當前進程的有效用戶ID,gid和cuid成員被設置成當前進程的有效組ID。

semctl()函數:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, union semun arg);功能:在這個函數中我們可以刪除信號量或初始化信號量,控制信號量的信息。
參數:semid:信號量的標志碼(ID),也就是semget()函數的返回值semnum:  操作信號在信號集中的編號。從0開始。cmd:命令,表示要進行的操作。IPC_STAT讀取一個信號量集的數據結構semid_ds,并將其存儲在semun中的buf參數中。IPC_SET設置信號量集的數據結構semid_ds中的元素ipc_perm,其值取自semun中的buf參數。IPC_RMID將信號量集從內存中刪除。GETALL用于讀取信號量集中的所有信號量的值。GETNCNT返回正在等待資源的進程數目。GETPID返回最后一個執行semop操作的進程的PID。GETVAL返回信號量集中的一個單個的信號量的值。GETZCNT返回這在等待完全空閑的資源的進程數目。SETALL設置信號量集中的所有的信號量的值。SETVAL設置信號量集中的一個單獨的信號量的值。Semunion :第4個參數是可選的;semunion :union semun的實例。union semun {int val;          /*SETVAL用的值*/struct semid_ds* buf; /*IPC_STAT、IPC_SET用的semid_ds結構*/unsigned short* array; /*SETALL、GETALL用的數組值*/struct seminfo *_buf;   /*為控制IPC_INFO提供的緩存*/} ;返回值:返回值:成功返回0,失敗返回-1

semop()函數:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);功能:用戶改變信號量的值,也就是使用資源還是釋放資源使用權。
參數:semid : 信號量的標識碼。也就是semget()的返回值。sops指向進行操作的信號量集結構體數組的首地址,此結構的具體說明如下:struct  sembuf{unsigned short  sem_num;//信號量集合中的信號量編號,0代表第1個信號量short  sem_op;//若sem_op>0進行V操作信號量值加val,表示進程釋放控制的資源 /*若sem_op<0進行P操作信號量值減val,若(semval-val)<0(semval為該信號量值),則調用進程阻塞,直到資源可用若設置IPC_NOWAIT不會睡眠,進程直接返回EAGAIN錯誤*//*若val==0時阻塞等待信號量為0,調用進程進入睡眠狀態,直到信號值為0;若設置IPC_NOWAIT,進程不會睡眠,直接返回EAGAIN錯誤*/short sem_flg; /*0 設置信號量的默認操作*//*IPC_NOWAIT 對信號的操作不能滿足時,semop()不會阻塞,并立即返回,同時設定錯誤信息。*//*SEM_UNDO 程序結束時(不論正常或不正常),保證信號值會被重設為semop()調用前的值。這樣做的目的在于避免程序在異常情況下結束時未將鎖定的資源解鎖,造成該資源永遠鎖定*/};sem_num:  進行操作信號量的個數,即sops結構變量的個數,需大于或等于1。最常見設置此值等于1,只完成對一個信號量的操作返回值:返回值:成功返回0,失敗返回-1

利用信號量控制父子進程運行順序示例:

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
union semum{int val;          struct semid_ds* buf; unsigned short* array; struct seminfo *_buf; 
};void pGet(int id)
{struct sembuf set;set.sem_num=0;set.sem_op=-1;set.sem_flg=SEM_UNDO;semop(id,&set,1);printf("get recourse\n");
}
void vPut(int id)
{struct sembuf put;put.sem_num=0;put.sem_op=1;put.sem_flg=SEM_UNDO;semop(id,&put,1);printf("put recourse\n");
}
int main()
{key_t key;int semid,pid;union semum initsem;initsem.val=0;key=ftok(".",3);	semid=semget(key,1,IPC_CREAT|0600);semctl(semid,0,SETVAL,initsem);pid=fork();if(pid>0){pGet(semid);printf("這是父進程\n");vPut(semid);semctl(semid,0,IPC_RMID);}else if(pid==0){printf("這是子進程\n");vPut(semid);}else{printf("frok error\n");}return 0;
}

補充:
系統中共享內存、消息隊列、信號的查看和刪除

進程組:
進程組是一個或多個進程的集合,通常它們與一組作業相關聯,可以接受來自同一終端的各種信號。每個進程組都有一個組長,進程組的ID和進程組長ID一致。

權限保護:
root用戶可以發送信號給任何用戶,而普通信號不可以向系統用戶(的進程)或者其他普通用戶(的進程)發送任何信號。普通用戶只可以向自己創建的進程發送信號。

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

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

相關文章

分布式理論CAP定理

CAP原則又稱CAP定理&#xff0c;指的是在一個分布式系統中&#xff0c; Consistency&#xff08;一致性&#xff09;、 Availability&#xff08;可用性&#xff09;、Partition tolerance&#xff08;分區容錯性&#xff09;&#xff0c;三者不可兼得。 998年&#xff0c;加州…

xmlWriter 以UTF-8格式寫xml問題

dom4j中的XMLWriter提供以下幾種構造方法&#xff1a; XMLWriter() XMLWriter(OutputFormat format) XMLWriter(OutputStream out) XMLWriter(OutputStream out, OutputFormat format) XMLWriter(Writer writer) XMLWriter(Writer writer, OutputFormat format) 最簡單常…

linux線程(互斥鎖、條件)

線程概念&#xff1a; 典型的UNIX/Linux進程可以看成只有一個控制線程&#xff1a;一個進程在同一時刻只做一件事情。有了多個控制線程后&#xff0c;在程序設計時可以把進程設計成在同一時刻做不止一件事&#xff0c;每個線程各自處理獨立的任務。 進程是程序執行時的一個實例…

centos下防火墻設置

1、開啟防火墻 systemctl start firewalld2、開放指定端口firewall-cmd --zonepublic --add-port8888/tcp --permanent命令含義&#xff1a; --zone #作用域 --add-port1935/tcp #添加端口&#xff0c;格式為&#xff1a;端口/通訊協議 --permanent #永久生效&#xff0c;沒有…

linux網絡編程、socket編程

進程間通信&#xff1a; 特點&#xff1a;依賴于內核&#xff0c;造成缺陷——無法實現多機通信。 網絡&#xff1a; 地址&#xff1a;由IP地址&#xff08;IP地址是IP協議提供的一種統一的地址格式&#xff0c;它為互聯網上的每一個網絡和每一臺主機分配一個邏輯地址&#xf…

【Python】Python Mako模板使用

參考資料&#xff1a; Mako Templates for Python官網&#xff1a;http://www.makotemplates.org/ Python模板庫Mako的用法&#xff1a;http://my.oschina.net/u/877170/blog/290438 Mako 1.0.4 Documentation&#xff1a;http://docs.makotemplates.org/en/latest/usage.html#…

簡單的ftp服務器(客戶端、服務器端、socket)

查看本機IP地址&#xff1a; 127.0.0.1 服務器功能&#xff1a; 可以獲取服務器文件使用get指令可以展示服務器有哪些文件使用ls指令進入服務器某個文件夾使用指令cd文件夾名稱上傳本地文件到服務器&#xff0c;使用指令putpwd可以查看客戶端在當前服務器的位置 客戶端本地…

JavaScript 全局對象

JavaScript 全局對象 全局屬性和函數可用于所有內建的 JavaScript 對象。 頂層函數&#xff08;全局函數&#xff09; 函數描述decodeURI()解碼某個編碼的 URI。decodeURIComponent()解碼一個編碼的 URI 組件。encodeURI()把字符串編碼為 URI。encodeURIComponent()把字符串編碼…

Redis持久化方式~RDB 持久化和AOF 持久化

持久化 Redis 是內存型數據庫&#xff0c;為了保證數據在斷電后不會丟失&#xff0c;需要將內存中的數據持久化到硬盤上。 RDB 持久化 將某個時間點的所有數據都存放到硬盤上。 可以將快照復制到其它服務器從而創建具有相同數據的服務器副本。 如果系統發生故障&#xff0…

Linux文件和目錄權限筆記

查看文件或者目錄的權限命令&#xff1a;ls -al # -a 表示全部文件包含隱藏文件&#xff0c;-l 表示列出每個文件的詳細信息比如執行 ls -altotal 115drwxr--x--- 4 root root 4096 Oct 24 02:07 install.log格式說明&#xff1a;通過 ls -al 格式化輸出的文件詳細信息&#x…

python基礎:序列(列表、元組、字符串)、函數、字典、集合

Python語言運行環境&#xff1a; windowslinuxunixMacos等等 博客記錄內容&#xff1a; Python3的所有語法、面向對象思維、運用模塊進行編程、游戲編程、計算機仿真。 Python是什么類型的語言&#xff1a; Python是腳本語言&#xff0c;腳本語言(Scripting language)是電腦…

Redis 與 Memcached的區別

Redis 與 Memcached 兩者都是非關系型內存鍵值數據庫&#xff0c;主要有以下不同&#xff1a; 數據類型 Memcached 僅支持字符串類型&#xff0c;而 Redis 支持五種不同的數據類型&#xff0c;可以更靈活地解決問題。 數據持久化 Redis 支持兩種持久化策略&#xff1a;RDB…

python基礎(文件、異常、模塊、類、對象)

文件&#xff1a; 打開文件使用open函數&#xff0c;open()的第一個參數是&#xff1a;要打開文件的路徑&#xff0c;如果只傳入文件名那么將在當前文件下查找文件并打開。第二個參數是&#xff1a;文件的打開模式&#xff0c;其他參數都是默認的。文件的打開模式如下圖所示&a…

redis 和 memcached 的區別

redis 和 memcached 的區別 對于 redis 和 memcached 我總結了下面四點。現在公司一般都是用 redis 來實現緩存&#xff0c;而且 redis 自身也越來越強大了&#xff01; redis支持更豐富的數據類型&#xff08;支持更復雜的應用場景&#xff09;&#xff1a;Redis不僅僅支持簡…

樹莓派入門(樹莓派登錄的幾種方式)

什么是嵌入式&#xff1f; 嵌入式即嵌入式系統&#xff0c;IEEE&#xff08;美國電氣和電子工程師協會&#xff09;對其定義是用于控制、監視或者輔助操作機器和設備的裝置&#xff0c;是一種專用的計算機系統。國內普遍認同的嵌入式系統定義是以應用為中心&#xff0c;以計算…

git 如何刪除本地創建的倉庫(轉載自 https://segmentfault.com/q/1010000002996177?_ea=262685)...

可以先清除本地文件夾下的git文件&#xff0c;然后在重新初始化新建的git倉庫 //刪除文件夾下的所有 .git 文件 find . -name ".git" | xargs rm -Rf接著add&#xff0c;commit等操作即可轉載于:https://www.cnblogs.com/xiahl/p/5289266.html

mysql中SQL查詢優化方法總結

1.對查詢進行優化&#xff0c;應盡量避免全表掃描&#xff0c;首先應考慮在 where 及 order by 涉及的列上建立索引。 2.應盡量避免在 where 子句中對字段進行 null 值判斷&#xff0c;否則將導致引擎放棄使用索引而進行全表掃描&#xff0c;如&#xff1a;select id from t w…

Linux庫概念及相關編程(動態庫、靜態庫、環境變量)

分文件編程&#xff1a; 好處&#xff1a;分模塊編程思想&#xff0c;功能和責任劃分清楚便與調試&#xff0c;main函數簡潔&#xff0c;代碼易于閱讀。編程時頭文件有的是使用<>這個符號括起來的&#xff0c;有的是" "使用的是雙引號&#xff0c;使用尖括號括…

復利計算器

import java.util.Scanner;public class Fulijisuan {public static void main(String[] args) {// TODO Auto-generated method stubdouble F0;double singlesum0;double doublesum0;double m0;Scanner scanner new Scanner(System.in);System.out.println("1:復利計算\n…