歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> 學習Linux >> 進程間通信(三)—信號量,進程通信信號量

進程間通信(三)—信號量,進程通信信號量

日期:2017/3/6 9:33:23   编辑:學習Linux

進程間通信(三)—信號量,進程通信信號量


進程間通信(三)—信號量,進程通信信號量


我會用幾篇博客總結一下在Linux中進程之間通信的幾種方法,我會把這個開頭的摘要部分在這個系列的每篇博客中都打出來

進程之間通信的方式

  • 管道
  • 消息隊列
  • 信號
  • 信號量
  • 共享存儲區
  • 套接字(socket)

進程間通信(二)—消息隊列傳送門:http://www.cnblogs.com/lenomirei/p/5642575.html

進程間通信(一)—管道傳送門:http://www.cnblogs.com/lenomirei/p/5636339.html

第三篇來了!前兩篇訪問量很多,真的是很感謝了

這次記下信號量的相關操作函數和方法,和以前一樣會在博文的最後把測試代碼貼出來!

學過操作系統這本書的話應該對信號量這個名詞不會感到陌生,同時信號和信號量是不同的!

信號量多用於進程間的同步與互斥,簡單的說一下同步和互斥的意思

同步:處理競爭就是同步,安排進程執行的先後順序就是同步,每個進程都有一定的個先後執行順序

互斥:互斥訪問不可共享的臨界資源,同時會引發兩個新的控制問題(互斥可以說是特殊的同步)

競爭:當並發進程競爭使用同一個資源的時候,我們就稱為競爭進程


簡單說一下信號量的工作機制(因為真的很簡單),可以直接理解成計數器(當然其實加鎖的時候肯定不能這麼簡單,不只只是信號量了),信號量會有初值(>0),每當有進程申請使用信號量,通過一個P操作來對信號量進行-1操作,當計數器減到0的時候就說明沒有資源了,其他進程要想訪問就必須等待(具體怎麼等還有說法,比如忙等待或者睡眠),當該進程執行完這段工作(我們稱之為臨界區)之後,就會執行V操作來對信號量進行+1操作。

臨界區:不是個簡單的區域!是加鎖區間的代碼!

臨界資源:只能被一個進程同時使用(不可以多個進程共享),要用到互斥

我們可以說信號量也是進程間通信的一種方式,比如互斥鎖的簡單實現就是信號量,一個進程使用互斥鎖,並通知(通信)其他想要該互斥鎖的進程,阻止他們的訪問和使用(老子正在用!@_@!)

其實信號量的操作函數和之前幾個都是類似的,都是套路

  • 信號量的創建

一定會覺得熟悉的!和消息隊列十分類似

  • 函數原型:int semget(key_t key, int nsems, int semflg);
  • 頭文件:#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>
  • 參數解析
    • key_t這個就不詳細解釋了,在消息隊列篇就講過了
    • nsems這個參數是指創造出幾個信號量,准確的來說,semget這個函數創建出來一個信號量集(就是包含好多信號量的那種,可以簡單的理解為信號量數組),這就是說創建有幾個信號量的信號量集,可以選擇只創建一個
    • semflg還是一樣的,用到兩個參數就夠了IPC_CREAT 和 IPC_EXCL怎麼用的話我在消息隊列篇講過了,就不多費口舌了

下面會看到信號量的一個缺點

  • 信號量的初始化

這說明信號量的初始化和創建是分開操作的,為什麼是分開的,因為創建的時候沒有給信號量的大小的參數,信號量是可以設置大的(不是只可以設置為1),當然設置為1就是互斥訪問了,比如典型的生產者和消費者問題,但是如果像是讀者寫者中的讀者信號量可不一樣(因為可以有多個讀者同時讀),這裡就不解釋這個模型問題了。

缺點就是,一旦初始化和創建分開之後,就會有線程安全的問題,進程A剛創建一信號量還沒有初始化,進程B便已經開始對該信號量進程P操作(哥哥!我還沒賦值初始化!)根本無法進行-1操作,會阻塞的,我都不知道咋回事(親身體驗該錯誤)。。。

廢話不多說了,函數還是套路

  • 函數原型:int semctl(int semid, int semnum, int cmd, ...);
  • 頭文件:#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>
  • 參數解析
    • 第一個semid就不多說了標識信號量用的,semget成功時候的返回值就是它
    • semnum這個就很有用了,剛才說了申請的是一個信號量集,這個參數就是告訴函數要初始化的是第幾個信號量,該參數是從0開始的,0表示第一個信號量
    • cmd這次用這個SETVAL表示設置變量

當SETVAL被設置之後,可變參數列表的第四個參數就可以用上了,這裡傳入一個神奇的聯合體(我在手冊裡找到了,但是死活用不了它,還是自己寫了一個)

1 union semun {
2                int              val;    /* Value for SETVAL */
3                struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
4                unsigned short  *array;  /* Array for GETALL, SETALL */
5                struct seminfo  *__buf;  /* Buffer for IPC_INFO
6                                            (Linux-specific) */
7            };

就是它!就是它!就看第一個吧,其他我也沒用了(英語我也看不太懂),val就是設置的初值啦,傳入的時候直接傳入就行了,參數就是一個變量(不用傳指針)

這個函數一會刪除我們還會遇到它,ctl就是control控制的簡寫,很多操作都有它

  • 信號量操作

一開始就寫了操作的兩種方式,一種叫P操作,一種叫V操作,都是通過一個函數實現的,這個函數還關聯一個結構體

  • 函數原型:int semop(int semid, struct sembuf *sops, unsigned nsops);
  • 頭文件:#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>
  • 參數解析
    • 第一個semid老朋友了
    • 第二個就是那個結構體了,貼出來看
    • 第三個表示你想同時操作幾個信號量(為什麼不是表示index of信號量呢,在結構體裡有一起說),因為信號量集可能有很多信號量,每次一個一個操作不現實,這個參數就是提供一個同時操作的
1 struct sembuf
2 {
3     unsigned short sem_num;  /* semaphore number */
4     short          sem_op;   /* semaphore operation */
5     short          sem_flg;  /* operation flags */
6 };

sem_num就是index of信號量,你想操作的信號量的下標,sem_op是個短整型,這裡給1或者-1分別表示V和P操作,而最後一個semflg手冊中提到了兩個

IPC_NOWAIT:一看就是IPC通用的,非等待,不讓忙等待只好去休眠(或者別的,反正不能循環等待)

SEM_UNDO:這個一看就是信號量獨有的,看英文也能看出來UNDO是取消之前做過的,什麼意思呢,不得不提到,當進程在臨界區工作(持有鎖)的時候,是不允許掛(掛掉更不好啦)起的!!!這很重要(又要加篇幅啦!@_@),因為一旦掛起或者休眠,有可能就醒不了啦!(掛起有可能等待事件)這樣不好,會讓等待的其他進程饑餓,尤其是掛掉的時候,肯定就更糟糕了,根本就無法執行V操作,等待的進程就會無限的等待下去,遞歸申請信號量也是不好的,尤其是互斥信號量的時候會造成死鎖。這時候UNDO就出來把之前做過的東西都取消掉,P操作就當沒執行過,信號量又從0變回了1,皆大歡喜。

  • 信號量的刪除

semctl又來了,和消息隊列的刪除類似

  • 函數原型:剛才剛寫了~!
  • 頭文件:剛才剛寫了~!
  • 參數解析:剛才剛寫了~!
  • 不一樣的只是把SETVAL為換成IPC_RMID就行了(當然後面的可變參數列表也不要了,可以看一下測試的例子)

事已至此,基本操作就說完了,廢話少說,show me the code

我的程序分為comm.h(公共頭文件) comm.c(封裝基本函數) server.c(采用fork完成父子進程間使用信號量) 一共3個文件

基本功能:模擬線程安全(簡單版本),父進程會打印"A""A"子進程會打印"B""B",因為執行順序的原因可能會交叉打印,但我不要這樣,我要讓AA和BB分別連著!完成這個功能

先看之前的版本

交叉打印不是我想要的

功能完成連續打印

comm.h

 1 #include <stdio.h>
 2 #include <sys/ipc.h>
 3 #include <string.h>
 4 #include <unistd.h>
 5 #include <sys//sem.h>
 6 #include <stdlib.h>
 7 #include <errno.h>
 8 
 9 
10 #define _PATH_NAME_ "/tmp"
11 #define _PROJ_ID_ 0x6666
12 
13 static int comm_create_sem(int flags,int num);
14 int create_sem(int num);
15 int get_sem();
16 void P_sem(int sem_id,int index);
17 void V_sem(int sem_id,int index);

comm.c

 1 #include "comm.h"
 2 
 3 union semun {
 4                int              val;    /* Value for SETVAL */
 5                struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
 6                unsigned short  *array;  /* Array for GETALL, SETALL */
 7                struct seminfo  *__buf;  /* Buffer for IPC_INFO
 8                                            (Linux-specific) */
 9            };
10 
11 
12 static int comm_create_sem(int flags,int num)
13 {
14   key_t _key=ftok(_PATH_NAME_,_PROJ_ID_);
15   if(_key<0)
16   {
17     printf("%d:%s",errno,strerror(errno));
18   }
19   int sem_id;
20   if((sem_id=semget(_key,num,flags))<0)
21   {
22     printf("semget errno,%d:%s",errno,strerror(errno));
23   }
24   return sem_id;
25 }
26 
27 int create_sem(int num)
28 {
29   int flags=IPC_CREAT | IPC_EXCL;
30   int sem_id=comm_create_sem(flags,num);
31   union semun v;
32   v.val=1;
33   if(semctl(sem_id,0,SETVAL,v)<0)//init
34   {
35     printf("init error,%d:%s",errno,strerror(errno));
36   }
37   return sem_id;
38 }
39 
40 int get_sem()
41 {
42   int flags=0;
43   return comm_create_sem(flags,0);
44 }
45 
46 
47 void P_sem(int sem_id,int index)
48 {
49   struct sembuf s;
50   s.sem_num=index;
51   s.sem_op=-1;
52   s.sem_flg=0;
53   if(semop(sem_id,&s,1)<0)
54   {
55     printf("op errro,%d:%s",errno,strerror(errno));
56   }
57 }
58 
59 void V_sem(int sem_id,int index)
60 {
61   struct sembuf s;
62   s.sem_num=index;
63   s.sem_op=1;
64   s.sem_flg=0;
65   if(semop(sem_id,&s,1)<0)
66   {
67     printf("op error,%d:%s",errno,strerror(errno));
68   }
69 
70 }
71 
72 void destory_sem(int sem_id)
73 {
74   semctl(sem_id,0,IPC_RMID);
75 }

server.c

 1 #include "comm.h"
 2 
 3 
 4 
 5 int main()
 6 {
 7   int pid;
 8   pid=fork();
 9   if(pid>0)
10   {
11     //father
12     int sem_id=create_sem(1);
13     while(1)
14     {
15       P_sem(sem_id,0);
16       printf("A");
17       fflush(stdout);
18       sleep(1);
19       printf("A");
20       fflush(stdout);
21       V_sem(sem_id,0);
22     }
23   }
24   else
25   {
26     //child
27     while(1)
28     {
29       int sem_id=get_sem();
30       P_sem(sem_id,0);
31       printf("B");
32       fflush(stdout);
33       sleep(1);
34       printf("B");
35       fflush(stdout);
36       V_sem(sem_id,0);
37     }
38   }
39 
40   return 0;
41 }

http://xxxxxx/Linuxjc/1140282.html TechArticle

Copyright © Linux教程網 All Rights Reserved