linux IPC --- 有名信號量詳解

在之前的博客中linux信號量—互斥與同步談到無名信號量。無名信號量主要用於線程間的通信,保存在內存中,如果想要在進程間同步就必須把無名信號量放在進程間的共享內存中。而在進程間的通信中同步用的通常是有名信號量。有名信號量一般保存在/dev/shm/ 目錄下。像文件一樣存儲在文件系統中。
無名信號量的操作主要涉及到以下六個函數:

  • sem_init 用於創建一個信號量,並能初始化它的值。
  • sem_wait 和 sem_trywait 相當於 P 操作,它們都能將信號量的值減一,兩者的區別在於若信號量小於零時,sem_wait 將會阻塞進程,而 sem_trywait 則會立即返回。
  • sem_post 相當於 V 操作,它將信號量的值加一同時發出信號喚醒等待的進程。
  • sem_getvalue 獲取信號量的值。
  • sem_destroy 刪除信號量

有名信號量和無名信號量的區別和聯繫:

  • 無名信號量的創建信號量函數是sem_init,有名信號量的則是sem_open函數。
  • 無名信號量的刪除信號量函數是sem_destroy,有名信號量的則是用sem_close函數關閉有名信號量,但是想要把信號量從文件系統刪除得用sem_unlink函數。
  • 其他的PV操作有名信號量是完全和無名信號量一致的。

我們主要來看看有名信號量獨特於無名信號量的函數,公共函數就不詳細敘述了,請參考博文linux信號量—互斥與同步

sem_open函數

#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);//打開一個有名信號量,此時有名信號量是已經存在了的。
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);//創建有名信號量

返回值:若成功,返回信號量的地址;若出錯,返回SEM_FAILED
參數:

  • name:信號量文件名。
  • flags:sem_open() 函數的行爲標誌。
  • mode:文件權限,可用八進制表示,如0777.
  • value:信號量初始值。

sem_close函數

#include <semaphore.h>
int sem_close(sem_t *sem);//關閉有名信號量

返回值:若成功,返回0;若出錯,返回-1
參數:

  • sem:指向信號量的指針。

sem_unlink函數

#include <semaphore.h>
int sem_unlink(const char *name);//刪除有名信號量文件

返回值:若成功,返回0;若出錯,返回-1
參數:

  • name:有名信號量文件名。

下文通過6個測試程序,解析有名信號量的各種運用場景:

  • name_sem.c 講的是有名信號量實現親緣進程間互斥功能。
  • sync_name_sem.c 講的是有名信號量實現親緣進程間同步功能。
  • NameSemWrite_1.c 和 NameSemWrite_2.c 講的是在兩個不同的程序測試有名信號量的同步功能。
  • share_memory_name_sem_1.c 和 share_memory_name_sem_2.c 講的是有名信號量來實現共享內存讀寫數據的同步。

測試程序:

有名信號量實現親緣進程間互斥功能:

/* name_sem.c*/

#include <unistd.h>  
#include <stdio.h>  
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>

#define FILENAME "name_sem_file"

int main ()   
{   
    sem_t *sem = NULL; 
    //有名信號量在fork子程序中繼承下來
    //跟open()打開方式很相似,不同進程只要名字一樣,那麼打開的就是同一個有名信號量  
    sem = sem_open("name_sem", O_CREAT|O_RDWR, 0666, 1); //信號量值爲 1  
     if(sem == SEM_FAILED)
     {
        perror("sem_open");  
        exit(-1);  
     }  

    pid_t pid; //pid表示fork函數返回的值  
    int count=0;  
    int fd = open(FILENAME,O_RDWR | O_CREAT,0777);
    if(fd < 0)
    {
        perror("open");
    }

    if ((pid = fork()) < 0)
    {
        perror("fork");
        exit(-1);
    }
    else if (pid == 0)
    {
        char write_buf[] = "1";
        int i = 0;
        printf("child: pid = %ld,ppid = %ld, fd = %d, count = %d\n",(long)getpid(),(long)getppid(),fd,count); 
        for(i = 0;i < 2;i++)
        {
            /*信號量減一,P 操作*/
            sem_wait(sem);
            for(i = 0;i<5;i++)
            {
                if (write(fd,write_buf,sizeof(write_buf)))
                {
                    perror("write");
                }
                count++;
            }
            printf("in child....and count = %d\n",count);
            /*信號量加一,V 操作*/
            sem_post(sem);
            sleep(2);
        }
        exit(0);
    }
    else
    {
        char write_buf[] = "2";
        int i = 0;
        printf("parent: pid = %ld,ppid = %ld, fd = %d, count = %d\n",(long)getpid(),(long)getppid(),fd,count);
        for(i = 0; i<2; i++)
        {
            /*信號量減一,P 操作*/
            sem_wait(sem);
            for(i = 0;i<5;i++)
            {
                if (write(fd,write_buf,sizeof(write_buf)))
                {
                    perror("write");
                }       
                count++;
            }
            printf("in father.... count = %d\n",count); 
            /*信號量加一,V 操作*/
            sem_post(sem);
            sleep(2);
        }  
        printf("Waiting for the child process to exit\n");
        //等待子進程退出
        waitpid(pid,NULL,0);
        sem_del("name_sem"); //刪除信號量文件 name_sem  
        exit(0);
    }
    //return 0;  
}

測試結果:

ubuntu:~/test/process_test$ ./name_sem       
parent: pid = 64282,ppid = 63437, fd = 3, count = 0
child: pid = 64283,ppid = 64282, fd = 3, count = 0
write: Success
write: Success
write: Success
write: Success
write: Success
in father.... count = 5
write: Success
write: Success
write: Success
write: Success
write: Success
in child....and count = 5
write: Success
write: Success
write: Success
write: Success
write: Success
in father.... count = 10
write: Success
write: Success
write: Success
write: Success
write: Success
in child....and count = 10
^C
ubuntu:~/test/process_test$ cat name_sem_file
22222111112222211111

有名信號量實現親緣進程間同步功能:

/* sync_name_sem.c*/

#include <unistd.h>  
#include <stdio.h>  
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>

#define FILENAME "sync_name_sem_file"

int main ()   
{   
    sem_t *sem_1 = NULL; 
    sem_t *sem_2 = NULL; 
    //有名信號量在fork子程序中繼承下來
    //跟open()打開方式很相似,不同進程只要名字一樣,那麼打開的就是同一個有名信號量  
    sem_1 = sem_open("sync_name_sem_1", O_CREAT|O_RDWR, 0666, 0); //信號量值爲 0
    sem_2 = sem_open("sync_name_sem_2", O_CREAT|O_RDWR, 0666, 1); //信號量值爲 1  

     if( (sem_1 == SEM_FAILED) | (sem_2 == SEM_FAILED))
     {
        perror("sem_open");  
        exit(-1);  
     }  

    pid_t pid; //pid表示fork函數返回的值  
    int count=0;  
    int fd = open(FILENAME,O_RDWR | O_CREAT,0777);
    if(fd < 0)
    {
        perror("open");
    }

    if ((pid = fork()) < 0)
    {
        perror("fork");
        exit(-1);
    }
    else if (pid == 0)
    {
        char write_buf[] = "1";
        int i = 0;
        printf("child: pid = %ld,ppid = %ld, fd = %d, count = %d\n",(long)getpid(),(long)getppid(),fd,count); 
        for(i = 0;i<2;i++)
        {
            /*信號量減一,P 操作*/
            sem_wait(sem_1);
            for(i = 0;i<5;i++)
            {
                if (write(fd,write_buf,sizeof(write_buf)))
                {
                    perror("write");
                }
                count++;
            }
            printf("in child....and count = %d\n",count);
            /*信號量加一,V 操作*/
            sem_post(sem_2);
            sleep(2);
        }
        exit(0);
    }
    else
    {
        char write_buf[] = "2";
        int i = 0;
        printf("parent: pid = %ld,ppid = %ld, fd = %d, count = %d\n",(long)getpid(),(long)getppid(),fd,count);
        for(i = 0;i<2;i++)
        {
            /*信號量減一,P 操作*/
            sem_wait(sem_2);
            for(i = 0;i<5;i++)
            {
                if (write(fd,write_buf,sizeof(write_buf)))
                {
                    perror("write");
                }       
                count++;
            }
            printf("in father.... count = %d\n",count); 
            /*信號量加一,V 操作*/
            sem_post(sem_1);
            sleep(2);
        }  
        printf("Waiting for the child process to exit\n");
        //等待子進程退出
        waitpid(pid,NULL,0);
        sem_del("sync_name_sem_1"); //刪除信號量文件 sync_name_sem_1 
        sem_del("sync_name_sem_2"); //刪除信號量文件 sync_name_sem_2 
        exit(0);
    }
    //return 0;  
}

實驗結果:

ubuntu:~/test/process_test$ ./sync_name_sem
parent: pid = 64289,ppid = 63437, fd = 3, count = 0
write: Success
child: pid = 64290,ppid = 64289, fd = 3, count = 0
write: Success
write: Success
write: Success
write: Success
in father.... count = 5
write: Success
write: Success
write: Success
write: Success
write: Success
in child....and count = 5
write: Success
write: Success
write: Success
write: Success
write: Success
in father.... count = 10
write: Success
write: Success
write: Success
write: Success
write: Success
in child....and count = 10
^C
ubuntu:~/test/process_test$ cat sync_name_sem_file 
22222111112222211111

接下來,在兩個不同的程序測試有名信號量的同步功能:

//程序一
/*NameSemWrite_1.c*/

#include <unistd.h>  
#include <stdio.h>  
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>

#define FILENAME "write_name_sem_file"

int main ()   
{   
    sem_t *sem_1 = NULL; 
    sem_t *sem_2 = NULL; 
    sem_1 = sem_open("write_name_sem_1", O_CREAT|O_RDWR, 0666, 1); //信號量值爲 1  
    sem_2 = sem_open("write_name_sem_2", O_CREAT|O_RDWR, 0666, 0); //信號量值爲 1  
    if( (sem_1 == SEM_FAILED) | (sem_2 == SEM_FAILED))
    {
        perror("sem_open");  
        exit(-1);  
    }  
    int count=0;  
    int fd = open(FILENAME,O_RDWR | O_CREAT| O_APPEND,0777);
    if(fd < 0)
    {
        perror("open");
    }
    char write_buf[] = "1";
    int i = 0; 
    while(1)
    {
        /*信號量減一,P 操作*/
        sem_wait(sem_1);
        for(i = 0;i<5;i++)
        {
            if (write(fd,write_buf,sizeof(write_buf)))
            {
                perror("write");
            }
            count++;
        }
        printf("in child....and count = %d\n",count);
        /*信號量加一,V 操作*/
        sem_post(sem_2);
        sleep(2);
    }
    //sem_del("write_name_sem"); //刪除信號量文件 name_sem  
    return 0;
}

//程序二
/* NameSemWrite_2.c*/

#include <unistd.h>  
#include <stdio.h>  
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>

#define FILENAME "write_name_sem_file"

int main ()   
{   
    int count=0;  
    char write_buf[] = "2";
    int i = 0;
    sem_t *sem_1 = NULL; 
    sem_t *sem_2 = NULL; 
    sem_1 = sem_open("write_name_sem_1", O_CREAT|O_RDWR, 0666, 1); //信號量值爲 1  
    sem_2 = sem_open("write_name_sem_2", O_CREAT|O_RDWR, 0666, 0); //信號量值爲 1  
    if( (sem_1 == SEM_FAILED) | (sem_2 == SEM_FAILED))
    {
        perror("sem_open");  
        exit(-1);  
    }  
    int fd = open(FILENAME,O_RDWR | O_CREAT| O_APPEND,0777);
    if(fd < 0)
    {
        perror("open");
    }
    while(1)
    {
        /*信號量減一,P 操作*/
        sem_wait(sem_2);
        for(i = 0;i<5;i++)
        {
            if (write(fd,write_buf,sizeof(write_buf)))
            {
                perror("write");
            }       
            count++;
        }
        printf("in father.... count = %d\n",count); 
        /*信號量加一,V 操作*/
        sem_post(sem_1);
        sleep(2);
    }  
    return 0;  
}

把第一個程序成爲程序一,第二個程序成爲程序二。程序一和程序二使用有名信號量write_name_sem_1和write_name_sem_2 進行寫 write_name_sem_file 文件的同步。
我們將程序一放在A終端執行,程序二放在B終端執行,測試結果如下:
A終端:

ubuntu:~/test/process_test$ ./NameSemWrite_1
write: Success
write: Success
write: Success
write: Success
write: Success
in child....and count = 5
write: Success
write: Success
write: Success
write: Success
write: Success
in child....and count = 10
write: Success
write: Success
write: Success
write: Success
write: Success
in child....and count = 15
write: Success
write: Success
write: Success
write: Success
write: Success
in child....and count = 20

B終端:

ubuntu:~/test/process_test$ ./NameSemWrite_2
write: Success
write: Success
write: Success
write: Success
write: Success
in father.... count = 5
write: Success
write: Success
write: Success
write: Success
write: Success
in father.... count = 10
write: Success
write: Success
write: Success
write: Success
write: Success
in father.... count = 15
^C
ubuntu:~/test/process_test$ cat write_name_sem_file
11111222221111122222111112222211111

查看一下系統的有名信號量,發現之前創建的信號量並沒有銷燬。這裏只是測試用例,用在項目中一定要記得清理,在接下來的程序中也會考慮到這一點。

ubuntu:~/test/process_test$ ls -al /dev/shm/
total 20
drwxrwxrwt  2 root     root     140 Aug 18 20:12 .
drwxr-xr-x 23 root     root     880 Aug 18 19:51 ..
-rw-r--r--  1 chenting chenting  32 Aug 18 19:54 sem.name_sem
-rw-r--r--  1 chenting chenting  32 Aug 18 20:00 sem.sync_name_sem_1
-rw-r--r--  1 chenting chenting  32 Aug 18 20:00 sem.sync_name_sem_2
-rw-r--r--  1 chenting chenting  32 Aug 18 20:12 sem.write_name_sem_1
-rw-r--r--  1 chenting chenting  32 Aug 18 20:12 sem.write_name_sem_2

之前就講過不同進程間使用共享內存的實例,因爲多個進程同時對共享內存區寫數據,可能會造成不可預測的錯誤,所以,這裏使用有名信號量來實現共享內存讀寫數據的同步。

/* share_memory_name_sem_1.c*/

#include <unistd.h>  
#include <stdio.h>  
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/shm.h>
#include <string.h>

#define BUFF 128

int main ()   
{   
    int count=0;  
    char write_buf[] = "helloworld";
    int i = 0;
    sem_t *sem_1 = NULL; 
    sem_t *sem_2 = NULL; 
    sem_1 = sem_open("write_name_sem_1", O_CREAT|O_RDWR, 0666, 1); //信號量值爲 1  
    sem_2 = sem_open("write_name_sem_2", O_CREAT|O_RDWR, 0666, 0); //信號量值爲 1  
    if( (sem_1 == SEM_FAILED) | (sem_2 == SEM_FAILED))
    {
        perror("sem_open");  
        exit(-1);  
    }
    int shmid;
    char *shmaddr;//共享內存地址
    // 使用約定的鍵值創建共享內存
    //if((shmid=shmget(IPC_PRIVATE,BUFF,0666))<0)
    if((shmid=shmget((key_t) 1234,BUFF, 0666|IPC_CREAT))<0)
    {
        perror("shmget");
        exit(-1);
    }
    else
        printf("Create shared memory,id = %d\n",shmid);

    /*映射共享內存*/
    if((shmaddr=shmat(shmid,0,0))<(char *)0)
    {
        perror("shmat");
        exit(-1);
    }
    else
        printf("process 1 shmat shared memory success\n");  

    /*信號量減一,P 操作*/
    sem_wait(sem_1);
    //往共享內存追加寫數據
    strncat(shmaddr,write_buf,sizeof(write_buf));       
    /*信號量加一,V 操作*/
    sem_post(sem_2);

    sem_close(sem_1); //關閉有名信號量  sem_1
    sem_close(sem_2); //關閉有名信號量  sem_2
    //把共享內存從當前進程中分離  
    if(shmdt(shmaddr) == -1)  
    {  
        perror("shmdt");  
        exit(-1);  
    }  

    return 0;
}
/* share_memory_name_sem_2.c*/

#include <unistd.h>  
#include <stdio.h>  
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/shm.h>
#include <string.h>
#include <stddef.h>

#define BUFF 128

int main ()   
{   
    int count=0;  
    char write_buf[] = "2";
    int i = 0;
    sem_t *sem_1 = NULL; 
    sem_t *sem_2 = NULL; 
    sem_1 = sem_open("write_name_sem_1", O_CREAT|O_RDWR, 0666, 1); //信號量值爲 1  
    sem_2 = sem_open("write_name_sem_2", O_CREAT|O_RDWR, 0666, 0); //信號量值爲 1  
    if( (sem_1 == SEM_FAILED) | (sem_2 == SEM_FAILED))
    {
        perror("sem_open");  
        exit(-1);  
    }
    int shmid;
    char *shmaddr;//共享內存地址
    // 使用約定的鍵值創建共享內存
    //if((shmid=shmget(IPC_PRIVATE,BUFF,0666))<0)
    if((shmid=shmget((key_t) 1234,BUFF, 0666|IPC_CREAT))<0)
    {
        perror("shmget");
        exit(-1);
    }
    else
        printf("Create shared memory,id = %d\n",shmid);

    /*映射共享內存*/
    if((shmaddr=shmat(shmid,0,0))<(char *)0)
    {
        perror("shmat");
        exit(-1);
    }
    else
        printf("process 2 shmat shared memory success\n");  

    /*信號量減一,P 操作*/
    sem_wait(sem_2);
    //讀取共享內存的數據
    printf("read from share memory: %s\n",shmaddr);
    /*信號量加一,V 操作*/
    sem_post(sem_1);
    sem_close(sem_1); //關閉有名信號量  sem_1
    sem_close(sem_2); //關閉有名信號量  sem_2
    sleep(2);
    printf("remove write_name_sem_1\n");
    //刪除有名信號量
    if(sem_unlink("write_name_sem_1") < 0)  
    {  
        perror("sem_unlink");  
    }
    printf("remove write_name_sem_2\n");
    if(sem_unlink("write_name_sem_2") < 0)  
    {  
        perror("sem_unlink");  
    }
    printf("detach shmaddr\n");
    //把共享內存從當前進程中分離  
    if(shmdt(shmaddr) == -1)  
    {  
        perror("shmdt");  
        exit(-1);  
    }
    printf("remove shmaddr\n");
    //刪除共享內存  
    if(shmctl(shmid, IPC_RMID, 0) == -1)  
    {  
        perror("shmctl"); 
        exit(-1);  
    } 
    printf("it is end!\n");
    return 0;
}

A終端:

ubuntu:~/test/process_test/share_memory_ctrl$ ./share_memory_name_sem_2
Create shared memory = 786432
process 2 shmat shared memory success
read from share memory: helloworldhelloworld
remove write_name_sem_1
remove write_name_sem_2
detach shmaddr
remove shmaddr
it is end!

B終端:

ubuntu:~/test/process_test/share_memory_ctrl$ ./share_memory_name_sem_1
Create shared memory = 786432
process 1 shmat shared memory success

以上,share_memory_name_sem_1.c 和share_memory_name_sem_2.c 兩個程序使用約定的鍵值創建共享內存,然後使用信號量使share_memory_name_sem_1.c 先往共享內存追加寫數據,然後再讓 share_memory_name_sem_2.c 讀取共享內存的數據,從而達到同步的效果。接着讀取完數據之後,釋放資源,關閉有名信號量,刪除有名信號量,把共享內存從當前進程中分離 ,然後刪除共享內存 。
再看看有名信號量和共享內存的資源有沒有被釋放。執行ipcs -m 命令查看共享內存的資源使用情況,使用 ls -al /dev/shm/ 查看有名信號量的使用情況。
結果如下,所有的資源都被釋放了。我們寫程序,一定要把申請的資源釋放掉。

ubuntu:~/test/process_test/share_memory_ctrl$ ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      

ubuntu:~/test/process_test/share_memory_ctrl$ ls -al /dev/shm/
total 0
drwxrwxrwt  2 root root  40 Aug 18 20:28 .
drwxr-xr-x 23 root root 880 Aug 18 19:51 ..
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章