Linux系統編程——POSIX IPC

POSIX IPC

POSIX IPC中的xxx_open()中的一些意義相同參數的取值: xxx可以是mq(消息隊列)、sem(信號量)、shm(共享內存)

參數oflag

O_RDONLY - 可讀
O_WRONLY - 可寫
O_RDWR - 可讀可寫
O_CREAT - 隊列不存則創建,需要使用mode和attr參數
          如果文件存在,mode和attr參數被忽略
O_EXCL - 如果使用O_CREAT,消息隊列存在則創建失敗
O_NONBLOCK - 非阻塞模式打開

參數mode:

S_IRWXU —— 文件擁有者可讀可寫可執行
S_IRUSR —— 用戶可讀
S_IWUSR —— 用戶可寫
S_IXUSR —— 用戶可執行

S_IRWXG —— 文件擁有者所在組成員可讀可寫可執行
S_IRGRP —— 組用戶可讀
S_IWGRP —— 組用戶可寫
S_IXGRP —— 組用戶可寫

S_IRWXO —— 其他用戶擁有可讀可寫可執行權限
S_IROTH —— 其他用戶可讀
S_IWOTH —— 其他用戶可寫
S_IXOTH —— 其他用戶可執行

消息隊列

消息隊列也具備描述符,消息隊列的描述符與文件的描述符類似
消息隊列的描述符是一個進程級別的句柄

子進程會繼承父進程打開的消息隊列描述符

創建或打開消息隊列

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <mqueue.h>

mqd_t mq_open(const char *name, int oflag);
mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);

name —— 消息隊列名字(消息隊列標識)
oflag —— 位掩碼,mq_open()操作選項,與文件的open()的flag參數差不多:
	O_RDONLY - 可讀
	O_WRONLY - 可寫
	O_RDWR - 可讀可寫
	O_CREAT - 隊列不存則創建,需要使用mode和attr參數
              如果文件存在,mode和attr參數被忽略
	O_EXCL - 如果使用O_CREAT,消息隊列存在則創建失敗
  	O_NONBLOCK - 非阻塞模式打開
        
mode —— 消息隊列權限掩碼,與文件的權限掩碼一致,但執行權限無效
attr —— 消息隊列的屬性,用於指定消息隊列的最大數量和最大大小
struct mq_attr {
    long mq_flags;       /* oflag */
    long mq_maxmsg;      /* 消息隊列消息最大數量 */
    long mq_msgsize;     /* 每條消息的大小 */
    long mq_curmsgs;     /* 消息隊列中當前存在的消息數量 */
};

RETURN VALUE 
    成功返回消息隊列描述符,失敗返回-1

關閉消息隊列

#include <mqueue.h>

int mq_close(mqd_t mqdes);
mqdes —— 消息隊列描述符

RETURN VALUE
    成功返回0, 失敗返回-1

刪除消息隊列

刪除name標識的消息隊列,標記其在所有使用它的進程使用完後銷燬該消息隊列

#include <mqueue.h>

int mq_unlink(const char *name);
name —— 消息隊列名字

RETURN VALUE
	成功返回0,失敗返回-1

獲取消息隊列的屬性

#include <mqueue.h>

int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
mqdes —— 消息隊列描述符
attr —— 消息隊列屬性
    
RETURN VALUE
    成功返回0,失敗返回-1

設置消息隊列的屬性

#include <mqueue.h>     

int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr, struct mq_attr *oldattr);
mqdes —— 消息隊列描述符
newattr —— 用來設置的消息隊列屬性
oldattr —— 獲取消息隊列的舊屬性

RETURN VALUE 
    成功返回0,失敗返回-1

發送消息

#include <time.h>
#include <mqueue.h>

int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio); 
 //發送消息數量達到attr字段mq_maxmsg進程將阻塞

int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio, const struct timespec *abs_timeout);

mqdes —— 消息隊列描述符
msg_ptr —— 發送的消息
msg_len —— 消息的大小
msg_prio —— 消息的優先級,posix的消息隊列可以設置優先級,
            優先級高的消息永遠放在隊列靠前的位置
    
mq_timedsend()的行爲與mq_send()類似,
但如果隊列已滿,並且沒有爲消息隊列描述啓用O_NONBLOCK標誌,
則將abs_timeout指向指定調用將阻塞多長時間的結構
 struct timespec {
     time_t tv_sec;        /* seconds */
     long   tv_nsec;       /* nanoseconds */
 };


RETURN VALUE 
    成功返回0, 失敗返回-1

接收消息

#include <mqueue.h>
#include <time.h>

ssize_t mq_receive(mqd_t mqdes, char *msg_ptr,
                          size_t msg_len, unsigned int *msg_prio);

ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr,
                        size_t msg_len, unsigned int *msg_prio,
                        const struct timespec *abs_timeout);
mqdes —— 消息隊列描述符
msg_ptr —— 發送的消息
msg_len —— 消息的大小,必須大於消息隊列屬性attr中mq_msgsize字段
msg_prio —— 消息的優先級

mq_timedreceive()的行爲與mq_receive()類似,但如果隊列是空的,
並且沒有爲消息隊列描述啓用O_NONBLOCK標誌,
則將abs_timeout指向指定調用將阻塞多長時間的結構
    
RETURN VALUE
    	成功返回接收到的數據大小,失敗返回-1

代碼

/**********************************************************************
  * @file:        xxx.c
  * @author:      guangjieMVP
  * @version:     v1.00.00
  * @date:        2020-xx-xx
  * @github:      https://github.com/guangjieMVP
  * @brief: 
************************************************************************/
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <mqueue.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


#define handler_err(err) \
        do{ perror(err); exit(0); } while(0)

char father_buf[] = "I am your father";

int main(int argc, char **argv)
{
    struct mq_attr m_attr;
    m_attr.mq_maxmsg = 10;
    m_attr.mq_msgsize = 40;
    mqd_t mq_fd = mq_open("/mq", O_RDWR | O_CREAT, S_IRWXU | S_IRWXG, &m_attr);

    if (mq_fd < 0)
    {
        handler_err("mq_open");
    }

    struct mq_attr get_attr;
    int ret = mq_getattr(mq_fd, &get_attr);
    if (ret < 0)
    {
        mq_close(mq_fd);
        handler_err("mq_getattr");
    }
    printf("maxmsg = %ld, msgsize = %ld\r\n", get_attr.mq_maxmsg, get_attr.mq_msgsize);

    pid_t pid = fork();
    if (pid == 0)
    {
        unsigned int msg_prio = 0;
        char msg_ptr[100];
        while(1)
        {
            if (mq_receive(mq_fd, msg_ptr, sizeof(msg_ptr), &msg_prio) < 0)//msg_ptr的大小必須大於或等於m_attr.mq_msgsize
            {
                handler_err("mq_receive");
            }
            printf("%s\r\n", msg_ptr);
        }
    }
    else
    {
        while (1)
        {
            if ( mq_send(mq_fd, father_buf, sizeof(father_buf), 1) < 0)
            {
                handler_err("mq_send");
            }
            printf("father is running\r\n");
            sleep(1);
        }        
    }
    
    mq_close(mq_fd);

    return 0;
}

信號量

進程或線程間同步、互斥訪問共享資源

信號量屬於系統範疇,進程退出也不會自動銷燬,需要手動刪除釋放資源

使用信號量的程序編譯時需要鏈接pthread庫:在編譯時加上下面編譯選項

-pthread

信號量類型

  • 有名信號量

    擁有名字,不同進程或線程之間可以通過名字訪問同一信號量
    
  • 無名信號量

    特點:

輕量化、基於內存(不存在於任何文件系統中)
多用於進程內部線程間的同步互斥
沒有名字,存放在內存預先指定位置
可以在進程或線程之間共享:
進程之間共享時,必須位於進程共享內存區域中
線程之間共享,信號量存放於線程共享內存區域中

有名信號量

打開or創建信號量

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#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);
name —— 信號量名字
oflag —— 信號量打開選項掩碼
mode —— 信號量的權限
value —— 信號量的初始化值
    
RETURN VALUE    
    成功返回信號量的句柄,失敗返回SEM_FAILED

關閉信號量

#include <semaphore.h>

int sem_close(sem_t *sem);
sem —— 信號量句柄

RETURN VALUE
       成功返回0,失敗返回-1

刪除信號量

#include <semaphore.h>

int sem_unlink(const char *name);
name —— 信號量名字
RETURN VALUE
       成功返回0; 失敗返回-1

釋放信號量

#include <semaphore.h>

int sem_post(sem_t *sem);
sem —— 信號量句柄
RETURN VALUE
    成功返回0,失敗返回 -1,信號量的值不變

等待信號量

#include <semaphore.h>

int sem_wait(sem_t *sem);
sem —— 信號量句柄

RETURN VALUE
  	成功返回0,失敗返回 -1,信號量的值不變

獲取信號量當前值

#include <semaphore.h>

int sem_getvalue(sem_t *sem, int *sval);
sem —— 信號量句柄
sval —— 存放獲取的信號量值
RETURN VALUE
    成功返回0,失敗返回 -1

代碼

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
  #include <stdlib.h>

#define handler_err(err) \
                do{ perror(err);} while(0)

#define SEM_NAME    "/name_sem"   //信號量的名字

int main(int argc ,char **argv)
{
    sem_t *name_sem = sem_open(SEM_NAME,  O_RDWR | O_CREAT,  S_IRWXU | S_IRWXG, 0);
    if (name_sem == SEM_FAILED)
    {
        handler_err("sem_open");
        exit(0);
    }

    pid_t pid = fork();
    if (pid == 0)
    {
        int ret;
        while (1)
        {
            ret = sem_wait(name_sem);
            if (ret == 0)
            {
                printf("child is running\r\n");
            }
        }
    }
    else
    {
        char buf[100];
        char *ret;
        while (1)
        {
            ret = fgets(buf, sizeof(buf), stdin);  
            if (ret != NULL)
            {
                printf("father : %s\r\n", buf);
            }
            if (strstr(buf, "post")  != NULL)
            {
                 sem_post(name_sem);      //釋放信號量
            }    
        }
        sem_close(name_sem);
        sem_unlink(SEM_NAME);
    }
    
    return 0;
}

無名信號量

一般多用於進程的線程間通信

初始化無名信號量

 #include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);
sem —— 信號量句柄
pshared —— 無名信號量共享方式:
    pshared = 0,同一個進程的線程之間共享
    pshared != 0, 不同進程之間之間共享,無名信號量必須存在共享內存區域(POSIX共享內存、system V共享內存、內存映射)
value —— 信號量的初始化值
    
RETURN VALUE
   成功返回0; 失敗返回-1

銷燬無名信號量

不存在進程或者線程等待時才能夠安全銷燬信號量

#include <semaphore.h>

int sem_destroy(sem_t *sem);
sem —— 信號量句柄
RETURN VALUE
    成功返回0; 失敗返回-1

代碼

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>

#define handler_err(err) \
                do{ perror(err); } while(0)

int main(int argc ,char **argv)
{
    sem_t  *p_sem = (sem_t *)mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); //MAP_ANONYMOUS 匿名映射
   if (p_sem != MAP_FAILED){
        printf("sem addr : %p\r\n", p_sem);
    }else{
        handler_err("mmap");
        exit(0);
    }

    if (sem_init(p_sem, 2, 0) < 0) {  //初始化無名信號量
        handler_err("sem_open");
        exit(0);
    }

    pid_t pid = fork();
    if (pid == 0){
        int ret;
        int sem_val;
        int cnt = 0;
        while (1){
            ret = sem_wait(p_sem);
            if (ret == 0){
                printf("child is running\r\n");
            }else{
                perror("sem_wait");
            }
            sem_getvalue(p_sem, &sem_val);
            printf("sem val : %d\r\n", sem_val);
            printf("cnt : %d\r\n", cnt++);
        }
    }else{
        char buf[100];
        char *ret;
        int sem_val;
        while (1){
            ret = fgets(buf, sizeof(buf), stdin);  
            if (ret != NULL){
                // printf("father : %s\r\n", buf);
            }
            if (strstr(buf, "post")  != NULL){
                 if (sem_post(p_sem) < 0){  //釋放信號量
                    perror("sem_post");
                 }
                sem_getvalue(p_sem, &sem_val);
                printf("sem val : %d\r\n", sem_val);
            }    
        }
       
    }

    sem_close(p_sem);
    sem_destroy(p_sem);

    return 0;
}

共享內存

共享內存的使用一般要加上互斥,防止多進程寫入數據造成數據踐踏

使用步驟:

1、shm_open()打開一個共享內存對象

2、ftruncate()設置共享內存對象大小

3、mmap()使用flags指定MAP_SHARED進行內存映射

shm_open()mmap()分別類似system V 共享內存的shmget()shmat()操作

創建共享內存對象

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

int shm_open(const char *name, int oflag, mode_t mode);
name —— 共享內存對象名字
oflag —— 打開方式選項
mode —— shm權限
RETURN VALUE  
	成功返回非負的文件描述符,失敗返回-1

新創建的共享內存對象長度會被設置爲0,需調用**ftruncate()**函數設置長度,新增加的字節的值被初始化爲0

設置共享內存對象的長度

#include <unistd.h>
#include <sys/types.h>

int ftruncate(int fd, off_t length);
fd —— 描述符(文件描述符、共享內存描述符等)
length —— 設置的長度
RETURN VALUE
     成功返回0,失敗返回-1 

刪除共享內存對象

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

int shm_unlink(const char *name);
name —— 共享內存對象名字

RETURN VALUE
  	成功返回0,失敗返回-1   
/**********************************************************************
  * @file:        xxx.c
  * @author:      guangjieMVP
  * @version:     v1.00.00
  * @date:        2020-xx-xx
  * @github:      https://github.com/guangjieMVP
  * @brief: 
************************************************************************/
#include <sys/mman.h>
#include <sys/stat.h>        /* For mode constants */
#include <fcntl.h>           /* For O_* constants */
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <semaphore.h>
#include <sys/types.h>

#define handler_err(err) \
                do{ perror(#err); } while(0)

#define SHM_NAME  "/shm_test"

#define SEM_NAME    "/name_sem"

int main(int argc ,char **argv)
{
    int shm_fd = shm_open(SHM_NAME, O_RDWR | O_CREAT, S_IRWXU | S_IRWXG); //打開共享內存
    if (shm_fd < 0){
        handler_err(shm_open);
        exit(0);
    }

    if (ftruncate(shm_fd, 1024) < 0){ //設置共享內存大小
        handler_err(ftruncate);
        exit(0);
    }

    struct stat shmstat;
    int ret = fstat(shm_fd, &shmstat); //獲取共享內存信息
    if (ret < 0){
        handler_err(fstat);
        exit(0);
    }
    printf("shm size : %ld\r\n", shmstat.st_size);
	//內存映射
    char *p = mmap(NULL, shmstat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);

    if (p == MAP_FAILED){
        handler_err(mmap);
        exit(0);
    }
    
    sem_t *name_sem = sem_open(SEM_NAME,  O_RDWR | O_CREAT,  S_IRWXU | S_IRWXG, 0);//打開信號量
    if (name_sem == SEM_FAILED){
        handler_err(sem_open);
        exit(0);
    }

    pid_t pid = fork();
    if (pid == 0){
        while(1){
            ret = sem_wait(name_sem);
            if (ret == 0) {
                printf("child is running\r\n");
            }
            printf("%s\r\n", p);
        }
    }else{
        int cnt = 0;
        // char buf[40];
        while(1){
            sprintf(p, "Hello, Linux %d\r\n", cnt++);
            sem_post(name_sem);      //釋放信號量
            sleep(1);
        }
    }

    if ( shm_unlink(SHM_NAME) < 0){
        handler_err(shm_unlink);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章