IO进、线程——进程间通信的IPC对象(共享内存、消息队列和信号灯集)

进程间通信IPC对象

进程间通信(IPC,Inter-Process Communication)是指不同进程之间进行信息交换和数据共享的机制。在多进程编程中,进程间通信是非常重要的,它允许不同的进程之间进行协调和合作,从而实现更复杂的任务。

在Linux/Unix操作系统中,提供了多种IPC对象,其中包括共享内存、消息队列、信号量等。这些IPC对象允许进程之间共享数据、发送消息和进行同步操作。接下来,我们将详细介绍共享内存消息队列信号灯集这三种常用的进程间通信IPC对象。

ftok():

ftok 函数用于通过给定的文件名和项目ID生成一个唯一的IPC键值。IPC键值在创建IPC对象时被使用,确保进程能够正确地识别和访问同一个IPC对象。

#include 
key_t ftok(const char *pathname, int proj_id);

功能:通过参数给的简单值生成一个复杂值(IPC键值)
返回值:成功返回生成的key,失败返回-1
参数说明:
pathname:文件名 会使用文件的inode节点编号
proj_id:随便一个数值,用于与pathname联合生成IPC键值。

//生成IPC 值
    key_t key = ftok(".", 'n');
    if(key < 0){
        perror("ftok");
        return -1;
    }

1. 共享内存(Shared Memory)

共享内存是一种IPC对象,允许多个进程共享同一块物理内存区域,从而避免了进程间的数据复制,提高了进程间通信的效率。

直接读写内存(内核的空间),需要映射

1.1 创建/打开共享内存对象

#include 

int shmget(key_t key, size_t size, int shmflg);
在这里插入代码片

功能:打开一个共享内存对象(如果不存在则会先创建再打开)
返回值:成功返回对象ID号,失败返回-1
参数说明:
key:唯一表示某个对象的关键字,ftok()生成,也可以写IPC_PRIVATE,表示创建新的私有共享内存对象。
size:指定共享内存有多大,以字节为单位
shmflg:打开或创建对象的方式

IPC_CREAT:对象不存在则创建
IPC_CREAT | 0666

	//生成IPC 值
    key_t key = ftok(".", 'n');
    if(key < 0){
        perror("ftok");
        return -1;
    }
    //创建打开共享内存对象
    int shmid = shmget(key, 64, IPC_CREAT | 0666);
 	//shmid = shmget(IPC_PRIVATE, 128, IPC_CREAT | 0666);
    if(shmid < 0){
        perror("shmget");
        return -1;
    }

1.2 映射用户空间

void *shmat(int shmid, const void *shmaddr, int shmflg);

功能:把内核空间的地址映射到用户空间来,读写用户空间其实就是读写内核空间
返回值:成功返回映射后的地址,失败返回(void *)-1
参数说明:
shmid:已经打开的对象ID号
shmaddr:映射空间的地址,如果让系统帮忙分配空间就写NULL
shmflg:空间访问方式

SHM_RDONLY:只读
0:读写

	//映射用户空间
    char *shmaddr = (char *)shmat(shmID, NULL, 0);
    if(shmaddr == (char *)-1){
        perror("shmat");
        return -1;
    }

1.3 取消映射

int shmdt(const void *shmaddr);

功能:取消已经映射的空间
返回值:成功返回0,失败返回-1
参数说明:
shmaddr:映射后的地址

	//取消映射对象
    shmdt(shmadder);

1.4 删除对象

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

功能:整体操作共享内存对象(获取属性、设置属性、删除对象…)
返回值:成功返回0,失败返回-1
参数说明:
shmid:共享内存ID号
cmd:整体操作指令

IPC_STAT:获取属性
IPC_SET:设置属性
IPC_RMID:标记删除对象,只有最后一个连接的进程也标记了删除,才会真正的删除

buf:主要用于获取和设置属性,删除对象写NULL

结构体 struct shmid_ds 包含了共享内存的相关属性信息,包括:
struct ipc_perm shm_perm:用于标识共享内存对象的权限信息。
size_t shm_segsz:共享内存段的大小,以字节为单位。
time_t shm_atime:最后一次附加(映射)共享内存的时间。
time_t shm_dtime:最后一次分离共享内存的时间。
time_t shm_ctime:最后一次更改共享内存的时间。
pid_t shm_cpid:创建共享内存的进程ID号。
pid_t shm_lpid:最后一次附加共享内存的进程ID号。
shmatt_t shm_nattch:当前附加(映射)共享内存的进程数量。

struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) /
uid_t uid; /
Effective UID of owner /
gid_t gid; /
Effective GID of owner /
uid_t cuid; /
Effective UID of creator /
gid_t cgid; /
Effective GID of creator /
unsigned short mode; /
Permissions + SHM_DEST and SHM_LOCKED flags /
unsigned short __seq; /
Sequence number */
};

 	//删除对象
    shmctl(shmID, IPC_RMID, NULL);

参考代码

#include 
#include 
#include 
#include 
#include 
#include 

#define SHM_SIZE 1024

int main() {
    int shmid;
    char *shmaddr;
    pid_t pid;

    // 创建共享内存对象
    shmid = shmget(IPC_PRIVATE, SHM_SIZE, IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(0);
    }

    // 将共享内存映射到进程的地址空间
    shmaddr = shmat(shmid, NULL, 0);
    if (shmaddr == (char *)-1) {
        perror("shmat");
        exit(0);
    }

    pid = fork();
    if (pid < 0) {
        perror("fork");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 子进程写入数据到共享内存
        sprintf(shmaddr, "Hello from child process!");
        exit(EXIT_SUCCESS);
    } else {
        // 父进程等待子进程退出
        wait(NULL);
        // 父进程读取共享内存中的数据并打印
        printf("Message from child process: %s\n", shmaddr);
    }

    // 取消映射并删除共享内存对象
    shmdt(shmaddr);
    shmctl(shmid, IPC_RMID, NULL);

    return 0;
}

2.消息队列(Message Queue)

消息队列是一种IPC对象,允许多个进程之间通过发送消息进行通信。每个消息都有一个特定的类型,接收进程可以选择接收某个特定类型的消息。

各种类型的消息统统放到对象里面,只需要进程收发在意类型的消息

2.1 创建/打开消息队列

#include 

int msgget(key_t key, int msgflg);

功能:打开消息队列对象,如果不存在则创建后再打开
返回值:成功返回消息队列ID号,失败返回-1
参数说明:
key:可以是IPC_PRIVATE也可以是ftok()返回值
msgflg:打开创建方式 (IPC_CREAT | 0777)

key_t key = ftok(".", 'a');
    if(key < 0){
        perror("ftok");
        return -1;
    }

    //创建打开消息队列
    int msgid = msgget(key, IPC_CREAT | 0666);
    if(msgid < 0){
        perror("msgget");
        return -1;
    }

2.2 发送消息

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

功能:往消息队列对象中添加一个类型的消息
返回值:成功返回0,失败返回-1
参数说明:
msqid:已经打开的消息队列ID号
msgp:要发送的消息所在的缓冲区

struct msgbuf{
long type; // > 0
char text[N];
}

msgsz:表示要发送的消息正文大小,以字节为单位
msgflg:发送选项

0:阻塞
IPC_NOWAIT:非阻塞

//发送消息msgsnd
signal(SIGCHLD, sig_handler);
msgbuf.mtype = 100;//指定消息类型
char buf[256];
while(1){
    fgets(msgbuf.mtext, 256, stdin);
    int msg_snd = msgsnd(msgid, &msgbuf, sizeof(msgbuf.mtext), 0);
    if(msg_snd < 0){
        perror("msgsnd");
        continue;
    }
    if(strncasecmp(msgbuf.mtext, "quit", 4) == 0){
        msgctl(msgid, IPC_RMID, NULL);
        kill(pid, SIGKILL);
    }
}

2.3 接收消息

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

功能:从消息队列对象中读取一个在意类型的消息
返回值:成功返回收到的消息正文字节数,失败返回-1
参数说明:
msqid:消息队列ID号
msgp:用于存放消息的结构体

struct msgbuf{
long type; // > 0
char text[N];
}

msgsz:要收的消息正文大小
msgtyp:在意的消息类型
msgflg:接收方式,接收进程只接收该类型的消息

0:阻塞
IPC_NOWAIT:非阻塞

//接收消息msgrcv
msgbuf.mtype = 200;
while(1){
    int ret = msgrcv(msgid, &msgbuf, sizeof(msgbuf.mtext), msgbuf.mtype, 0);
    if(ret < 0){
        perror("msgrcv");
        continue;
    }
    printf("msgrcv form B:%s", msgbuf.mtext);
    if(strncasecmp(msgbuf.mtext, "quit",4) == 0){
        //如果接收到的内容是quit时就删除消息队列对象
        msgctl(msgid, IPC_RMID, NULL);
        exit(1);
    }
}

2.4 删除对象

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

功能:整体控制消息队列(获取、设置属性,删除对象…)
返回值:成功返回0,失败返回-1
参数说明:
msqid:要操作的消息队列ID号
cmd:操作指令

IPC_STAT
IPC_SET
IPC_RMID

buf:用于设置或获取属性,删除对象使用NULL

结构体 struct msqid_ds 包含了消息队列的相关属性信息,包括:

struct msqid_ds {
struct ipc_perm msg_perm:用于标识消息队列对象的权限信息。
time_t msg_stime:最后一次发送消息的时间。
time_t msg_rtime:最后一次接收消息的时间。
time_t msg_ctime:最后一次更改消息队列的时间。
unsigned long __msg_cbytes:消息队列中当前消息正文的总字节数。
msgqnum_t msg_qnum:当前消息队列中的消息数量。
msglen_t msg_qbytes:消息队列的最大容量,以字节为单位。
pid_t msg_lspid:最后一次发送消息的进程ID号。
pid_t msg_lrpid:最后一次接收消息的进程ID号。

};

msgctl(msgid, IPC_RMID, NULL);

参考代码

msg_A:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

struct msgbuf{
    long mtype;
    char mtext[256];
}msgbuf;

void sig_handler(int sig){
    int status;
    wait(&status);
    printf("exit = %d\n",(status >> 8) & 0xff);
    exit(0);
}

int main(int argc, char *argv[])
{ 
    key_t key = ftok(".", 'a');
    if(key < 0){
        perror("ftok");
        return -1;
    }

    //创建打开消息队列
    int msgid = msgget(key, IPC_CREAT | 0666);
    if(msgid < 0){
        perror("msgget");
        return -1;
    }

    pid_t pid = fork();
    if(pid < 0){
        perror("fork");
        return -1;
    }else if(pid == 0){
        //接收消息msgrcv
        msgbuf.mtype = 200;
        while(1){
            int ret = msgrcv(msgid, &msgbuf, sizeof(msgbuf.mtext), msgbuf.mtype, 0);
            if(ret < 0){
                perror("msgrcv");
                continue;
            }
            printf("msgrcv form B:%s", msgbuf.mtext);
            if(strncasecmp(msgbuf.mtext, "quit",4) == 0){
                //如果接收到的内容是quit时就删除消息队列对象
                msgctl(msgid, IPC_RMID, NULL);
                exit(1);
            }
        }
    }else{
        //发送消息msgsnd
        signal(SIGCHLD, sig_handler);
        msgbuf.mtype = 100;//指定消息类型
        char buf[256];
        while(1){
            fgets(msgbuf.mtext, 256, stdin);
            int msg_snd = msgsnd(msgid, &msgbuf, sizeof(msgbuf.mtext), 0);
            if(msg_snd < 0){
                perror("msgsnd");
                continue;
            }
            if(strncasecmp(msgbuf.mtext, "quit", 4) == 0){
                msgctl(msgid, IPC_RMID, NULL);
                kill(pid, SIGKILL);
            }
        }
    }

    return 0;
} 

msg_B:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

struct msgbuf{
    long mtype;
    char mtext[256];
}msgbuf;

void sig_handler(int sig){
    int status;
    wait(&status);
    printf("exit = %d\n",(status >> 8) & 0xff);
    exit(0);
}

int main(int argc, char *argv[])
{ 
    key_t key = ftok(".", 'a');
    if(key < 0){
        perror("ftok");
        return -1;
    }

    //创建打开消息队列
    int msgid = msgget(key, IPC_CREAT | 0666);
    if(msgid < 0){
        perror("msgget");
        return -1;
    }

    pid_t pid = fork();
    if(pid < 0){
        perror("fork");
        return -1;
    }else if(pid == 0){
        //接收消息msgrcv
        msgbuf.mtype = 100;
        while(1){
            int ret = msgrcv(msgid, &msgbuf, sizeof(msgbuf.mtext), msgbuf.mtype, 0);
            if(ret < 0){
                perror("msgrcv");
                continue;
            }
            printf("msgrcv form A:%s", msgbuf.mtext);
            if(strncasecmp(msgbuf.mtext, "quit",4) == 0){
                //如果接收到的内容是quit时就删除消息队列对象
                msgctl(msgid, IPC_RMID, NULL);
                exit(1);
            }
        }
    }else{
        //发送消息msgsnd
        signal(SIGCHLD, sig_handler);
        msgbuf.mtype = 200;//指定消息类型
        char buf[256];
        while(1){
            fgets(msgbuf.mtext, 256, stdin);
            int msg_snd = msgsnd(msgid, &msgbuf, sizeof(msgbuf.mtext), 0);
            if(msg_snd < 0){
                perror("msgsnd");
                continue;
            }
            if(strncasecmp(msgbuf.mtext, "quit", 4) == 0){
                msgctl(msgid, IPC_RMID, NULL);
                kill(pid, SIGKILL);
            }
        }
    }
    return 0;
} 

3.信号灯集(Semaphore Set)

信号灯集是一种IPC对象,允许多个进程对一组信号灯进行操作。信号灯集主要用于实现进程间的同步和互斥,确保多个进程之间按照特定顺序进行执行。

信号量的集合

3.1 打开/创建信号灯集对象

#include 

int semget(key_t key, int nsems, int semflg);

功能:打开对象,不存在则创建后再打开
返回值:成功返回ID号,失败返回-1
参数说明:
key:ftok()返回值
nsems:信号灯集下面信号灯的种类数
semflg: IPC_CREAT | 0666

 //生成IPC值
key_t key = ftok(".", 'a');
if(key < 0){
    perror("ftok");
    return -1;
}
//创建打开信号量数组对象
int semid = semget(key, 3, IPC_CREAT | 0666);
if(semid < 0){
    perror("semget");
    return -1;
}

3.2 初始化信号量

int semctl(int semid, int semnum, int cmd, ...);

功能:整体操作(初始化个数、获取属性、删除对象)
返回值:成功返回0,失败返回-1
参数说明:
semid:对象ID号
semnum:信号灯集下某种信号灯的编号,从0开始

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

cmd:操作指令

SETVAL:初始化值
IPC_SET
IPC_STAT
IPC_RMID:删除对象

//初始化信号量数组中的所有信号量
int init_sem(int semid, int* init_vals){
    union semun arg;
    //myun arg
    arg.array = (unsigned short *)init_vals;
    return semctl(semid, 0, SETALL, arg);
}

3.3 PV操作

int semop(int semid, struct sembuf *sops, size_t nsops);

功能:PV操作的集合,可以通过参数不同而实现申请释放
返回值:成功返回0,失败返回-1
参数说明:
semid:对象ID号
sops:所有的操作所在的结构体

struct sembuf {
unsigned short sem_num; /* 信号灯编号 /
short sem_op; /
信号量操作:-1表示P操作(申请),1表示V操作(释放) /
short sem_flg; /
操作标志:0表示阻塞,IPC_NOWAIT表示非阻塞 */
};

nsops:要操作的信号灯个数

//对信号量数组中的某个信号执行P操作
int sem_P(int semid, int sem_num){
    struct sembuf sb;
    //mysembuf sb;
    sb.sem_num = sem_num;
    sb.sem_op = -1;//P操作,申请
    //设置SEM_UNDO标志,确保进程结束时会释放信号量
    sb.sem_flg = SEM_UNDO;
    semop(semid, &sb, 1);
}

//对信号量数组中的某个信号执行V操作
int sem_V(int semid, int sem_num){
    struct sembuf sb;
    //mysembuf sb;
    sb.sem_num = sem_num;
    sb.sem_op = 1;//V操作,释放
    sb.sem_flg = SEM_UNDO;
    return semop(semid, &sb, 1);
}

参考代码:

#include 
#include 
#include 
#include 
union semun{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *__buf;
}myun;
#if 0
struct sembuf {
    unsigned short sem_num; /* 信号灯编号 */
    short sem_op; /* 信号量操作:-1表示P操作(申请),1表示V操作(释放)*/
    short sem_flg; /* 操作标志:0表示阻塞,IPC_NOWAIT表示非阻塞 */
}mysembuf;
#endif

//初始化信号量数组中的所有信号量
int init_sem(int semid, int* init_vals);

//对信号量数组中的某个信号执行P操作
int sem_P(int semid, int sem_num);

//对信号量数组中的某个信号执行V操作
int sem_V(int semid, int sem_num);

int main(int argc, char *argv[])
{ 
    //生成IPC值
    key_t key = ftok(".", 'a');
    if(key < 0){
        perror("ftok");
        return -1;
    }
    //创建打开信号量数组对象
    int semid = semget(key, 3, IPC_CREAT | 0666);
    if(semid < 0){
        perror("semget");
        return -1;
    }

    //初始化信号量数组
    int init_vals[3] = {1, 0, 1};
    int ret = init_sem(semid, init_vals);
    if(ret < 0){
        perror("semctl");
        return -1;
    }

    //创建子进程
    pid_t pid = fork();
    if(pid < 0){
        perror("fork");
        return -1;
    }else if(pid == 0){
        //子进程执行P操作
        sem_P(semid, 0);//申请第一个信号量
        printf("子进程申请的资源 1\n");
        sleep(3);
        //子进程执行V操作
        sem_V(semid, 2);//释放第三个信号量
        printf("子进程释放的资源 3\n");
    }else{
        //父进程执行P操作
        sem_P(semid, 2);//申请第三个信号量
        printf("父进程申请的资源 3\n");
        sleep(3);
        //子进程执行V操作
        sem_V(semid, 0);//释放第三个信号量
        printf("父进程释放的资源 1\n");
    }

    //删除信号量数组对象
    int de_sem = semctl(semid, 0, IPC_RMID);
    if(de_sem < 0){
        perror("semctl");
        return -1;
    }

    return 0;
} 


//初始化信号量数组中的所有信号量
int init_sem(int semid, int* init_vals){
    union semun arg;
    //myun arg
    arg.array = (unsigned short *)init_vals;
    return semctl(semid, 0, SETALL, arg);
}

//对信号量数组中的某个信号执行P操作
int sem_P(int semid, int sem_num){
    struct sembuf sb;
    //mysembuf sb;
    sb.sem_num = sem_num;
    sb.sem_op = -1;//P操作,申请
    //设置SEM_UNDO标志,确保进程结束时会释放信号量
    sb.sem_flg = SEM_UNDO;
    semop(semid, &sb, 1);
}

//对信号量数组中的某个信号执行V操作
int sem_V(int semid, int sem_num){
    struct sembuf sb;
    //mysembuf sb;
    sb.sem_num = sem_num;
    sb.sem_op = 1;//V操作
    sb.sem_flg = SEM_UNDO;
    return semop(semid, &sb, 1);
}

你可能感兴趣的:(IO进,线程,算法,linux,运维,c语言,网络,服务器)