Linux - 5种常见进程间通信方式

1. 常见的通信方式

  1. 管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
  2. 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  3. 共享内存SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
  4. 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
  5. 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  6. 套接字Socket:套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

1.1 管道

匿名管道

管道的特点:

  1. 是一种半双工的通信方式;
  2. 只能在具有亲缘关系的进程间使用.进程的亲缘关系一般指的是父子关系;
  3. 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

管道的函数原型:

#include <unistd.h>

int pipe(int pipefd[2]); 

代码实现:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

/*使用匿名管道实现进程间通信*/
int main()
{
        int fd[2];//fd[0]为读端 fd[1]为写端
        pid_t pid;
        char buf[128];
        //int pipe(int pipefd[2]);
        if(pipe(fd) == -1)//创建管道
        {
                printf("管道创建失败\n");
                perror("why");
        }

        pid = fork();

        if(pid < 0 )
        {
                printf("子进程开辟失败\n");
                perror("why");
        }else if(pid > 0){

                sleep(3);//让子进程先执行
                printf("这是一个父进程\n");//父进程完成写操作
                close(fd[0]);
                write(fd[1],"hello from father",strlen("hello from father"));
        }else{

                printf("这是一个子进程\n");//子进程完成读操作
                close(fd[1]);
                read(fd[0],buf,sizeof(buf));//没有数据来时,阻塞在这
                printf("buf = %s\n",buf);
        }

        return 0;
}

 

有名管道

FIFO,也叫做命名管道,它是一种文件类型。

FIFO的特点:

  1. FIFO可以在无关的进程之间交换数据,与无名管道不同;
  2. FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

FIFO的函数原型:

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

int mkfifo(const char *pathname, mode_t mode);

其中的 mode 参数与 open 函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件 I/O 函数操作它。

当 open 一个 FIFO 时,是否设置非阻塞标志(O_NONBLOCK)的区别:

  • 若没有指定 O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。
  • 若指定了 O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其 errno 置 ENXIO。

代码实现:

下列代码有效解决了,当管道存在时,程序报错的问题,减少了无关错误信息的打印。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
//       int mkfifo(const char *pathname, mode_t mode);

int main()
{
        if(mkfifo("myfifo",0600) == -1 && errno != EEXIST)
        {
                printf("mkfifo failed\n");
                perror("why");
        }

        return 0;
}

read.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

//       int mkfifo(const char *pathname, mode_t mode);

int main()
{
        int nread;
        char buf[30] = {'\0'};

        if(mkfifo("myfifo",0600) == -1 && errno != EEXIST)//创建命名管道
        {
                printf("mkfifo failed\n");
                perror("why");
        }

        int fd = open("./myfifo",O_RDONLY);//以只读的形式打开管道,程序阻塞在这,直到有另一个进程对其执行写操作
        if(fd < 0)
        {
                printf("read open failed\n");
        }else
        {
                printf("read open successn\n");
        }

        while(1)
        {
                nread = read(fd,buf,sizeof(buf));
                printf("read %d byte,context is:%s\n",nread,buf);
        }

        close(fd);

        return 0;
}

write.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
//       int mkfifo(const char *pathname, mode_t mode);

int main()
{
        int nread;
        char buf[30] = "message from myfifo";

        if(mkfifo("myfifo",0600) == -1 && errno != EEXIST)//创建命名管道
        {
                printf("mkfifo failed\n");
                perror("why");
        }

        int fd = open("./myfifo",O_WRONLY);//打开管道,程序阻塞在这,直到其他进程为读而打开它
        if(fd < 0)
        {
                printf("write open failed\n");
        }
        else
        {
                printf("write open success\n");
        }

        while(1)
        {
                sleep(1);
                write(fd,buf,strlen(buf));
        }
        close(fd);

        return 0;
}

 


1.2 信号 

 对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。

信号的相关概述:

1、信号的名字和编号:

每个信号都有一个名字和编号,这些名字都以“SIG”开头。我们可以通过kill -l来查看信号的名字以及序号。

不存在0信号,kill对于0信号有特殊的应用。

2、信号的处理:

信号的处理有三种方法,分别是:忽略、捕捉和默认动作。

  • 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILLSIGSTOP);
  • 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
  • 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。

信号处理注册函数:

  1. 入门版:signal
  2. 高级版:sigaction

信号处理发送函数:

  1. 入门版:kill
  2. 高级版:sigqueue

 

入门版

函数原型:

//接收函数,第二个参数指向信号处理函数
sighandler_t signal(int signum, sighandler_t handler);

//发送函数
int kill(pid_t pid, int sig);

接收端:

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


//       typedef void (*sighandler_t)(int);

//       sighandler_t signal(int signum, sighandler_t handler);
/*接受到信号后,让信号处理该函数*/
void handler(int signum)
{
        printf("signum = %d\n",signum);

        switch(signum){
                case 2:
                        printf("SIGINT\n");
                        break;
                case 9:
                        printf("SIGKILL\n");
                        break;
                case 10:
                        printf("SIGUSR1\n");
                        break;
        }
}

int main()
{
        signal(SIGINT,handler);
        signal(SIGKILL,handler);
        signal(SIGUSR1,handler);
        printf("进程id = %d\n", getpid());

        while(1);

        return 0;
}

发送端:

#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>

//       int kill(pid_t pid, int sig);

int main(int argc,char **argv)
{
        int signum;
        int pid;

        signum = atoi(argv[1]);//将字符型转为整型
        pid = atoi(argv[2]);

        kill(pid,signum);

        printf("signum = %d,pid = %d\n",signum,pid);

        return 0;
}

运行结果:

 

高级版

函数原型:

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

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、sa_sigaction只能任选其一

我们只需要配置 sa_sigaction以及sa_flags即可。

siginfo_t {
               int      si_signo;    /* Signal number */
               int      si_errno;    /* An errno value */
               int      si_code;     /* Signal code */
               int      si_trapno;   /* Trap number that caused
                                        hardware-generated signal
                                        (unused on most architectures) */
               pid_t    si_pid;      /* Sending process ID */
               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 */
}
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
   int   sival_int;
   void *sival_ptr;
 };

接收端:

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

//       int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

//(*sa_sigaction)(int, siginfo_t *, void *);
void handler(int signum, siginfo_t *info, void *context)
{
        printf("get signum is:%d\n",signum);

        if(context != NULL)
        {
                printf("get data = %d\n",info->si_int);
                printf("get data = %d\n",info->si_value.sival_int);
                printf("get pid is = %d\n",info->si_pid);
        }

}

int main()
{
        struct sigaction act;
        printf("pid = %d\n",getpid());
        act.sa_sigaction = handler;
        act.sa_flags = SA_SIGINFO;

        sigaction(SIGUSR1,&act,NULL);
        while(1);

        return 0;
}

发送端:

#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
//       int sigqueue(pid_t pid, int sig, const union sigval value);

int main(int argc,char **argv)
{
        int signum;
        int pid;

        signum = atoi(argv[1]);
        pid = atoi(argv[2]);

        union sigval value;
        value.sival_int = 100;

        sigqueue(pid,signum,value);
        printf("pid = %d,done\n",getpid());

        return 0;
}

运行结果:

注意:信号发送字符串,只有在父子进程或者是共享内存下才可发送。 

 


1.3 消息队列 

消息队列,是消息的链接表,存放在内核之中。一个消息队列由一个标识符(即队列ID)来标识。

用户进程可以向消息队列添加消息,也可以向消息队列读取消息。

消息队列的特点:

  1. 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级;
  2. 消息队列是独立于发送和接收进程的,进程终止时,消息队列及其内容并不会被删除;
  3. 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

消息队列函数的原型:

// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
// 添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

消息队列编程模型:

代码示例1:

msgA.c

#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>

#define TYPE  2

struct msgbuf{
	long type;
	char buf[20];
};

int main(){
	//创建key
	key_t key = ftok(".",'q');
	if(-1 == key) printf("ftok 失败:%m\n"),exit(-1);
	printf("ftok %m\n");

	//创建消息队列
	int msgid = msgget(key,IPC_CREAT|0666);
	if(-1 == msgid) printf("创建消息队列失败:%m\n"),exit(-1);
	printf("创建消息队列%m\n");

	//接收
	int r;
	struct msgbuf msg;

	while(1){
		memset(msg.buf,0,20);
		r = msgrcv(msgid,&msg,sizeof msg,TYPE,IPC_NOWAIT);
		printf("r:%d,msg:%s,type:%u\n",r,msg.buf,msg.type);
		sleep(1);
	}
}

msgB.c

#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>

#define TYPE  1

struct msgbuf{
	long type;
	char buf[20];
};

int main(){
	//创建key
	key_t key = ftok(".",'q');
	if(-1 == key) printf("ftok 失败:%m\n"),exit(-1);
	printf("ftok %m\n");

	//创建消息队列
	int msgid = msgget(key,IPC_CREAT|0666);
	if(-1 == msgid) printf("创建消息队列失败:%m\n"),exit(-1);
	printf("创建消息队列%m\n");

	//发送
	int r;
	struct msgbuf msg;

	while(1){
		printf("请输入要发送的消息类型:");
		scanf("%d",&msg.type);
		printf("请输入要发送的消息:");
		scanf("%s",msg.buf);

		r = msgsnd(msgid,&msg,sizeof msg,IPC_NOWAIT);
		printf("r:%d\n",r);
	}
}

 

代码示例2:

msgSend.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf{
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};


int main()
{
        struct msgbuf sendbuf={888,"message from send"};
        struct msgbuf readbuf;

        key_t key;

        if((key = ftok(".",'z')) < 0){
                printf("ftok error\n");
        }
        int msgId = msgget(key,IPC_CREAT|0777);

        if(msgId == -1){
                printf("get quen failed\n");
        }

        msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
        printf("send over\n");

        msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),999,0);
        printf("read from get is:%s\n",readbuf.mtext);

        msgctl(msgId,IPC_RMID,NULL);

        return 0;
}

msgGet.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf{
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};

int main()
{
        struct msgbuf readbuf;
        memset(readbuf.mtext, '\0', sizeof(readbuf.mtext));
        struct msgbuf sendbuf={999,"thank for your reach"};

        key_t key;

        //获取key值
        if((key = ftok(".",'z')) < 0){
                printf("ftok error\n");
        }

        int msgId = msgget(key,IPC_CREAT|0777);

        if(msgId == -1){
                printf("get quen failed\n");
                perror("why");
        }

        msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),888,0);
        printf("read from send is:%s\n",readbuf.mtext);

        msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);

        msgctl(msgId,IPC_RMID,NULL);

        return 0;
}

 


1.4 共享内存

共享内存,指两个或多个进程共享一个给定的存储区。

ipcs -m 查看系统下已有的共享内存;ipcrm -m shmid可以用来删除共享内存。

共享内存的特点:

  1. 共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
  2. 因为多个进程可以同时操作,所以需要进行同步。
  3. 信号量 + 共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

共享内存函数的原型:

// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
// 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr); 
// 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

共享内存编程模型:

示例代码1:

shmA.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

//共享内存
int* p = NULL;

void hand(int s){
	if(2 == s){
		//5 卸载共享内存
		shmdt(p);
		printf("bye bye!\n");
		exit(0);
	}
}
int main(){

	signal(2,hand);

	//1 创建key
	key_t key = ftok(".",'m');
	if(-1 == key) printf("创建key失败:%m\n"),exit(-1);
	printf("创建key成功!\n");

	//2 创建共享内存   页  page  4K==1页
	int shmid = shmget(key,4096,IPC_CREAT);
	if(-1 == shmid) printf("创建共享内存失败:%m\n"),exit(-1);
	printf("创建共享内存:%m!\n");

	//3 挂载共享内存
	p = shmat(shmid,NULL,0);
	if(NULL == p) printf("挂载共享内存失败:%m\n"),exit(-1);
	printf("挂载共享内存:%m\n");

	//4 使用
	while(1){
		printf("%d\n",*p);
		sleep(1);
	}
}

shmB.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

//共享内存
int* p = NULL;

void hand(int s){
	if(2 == s){
		//5 卸载共享内存
		shmdt(p);
		printf("bye bye!\n");
		exit(0);
	}
}
int main(){

	signal(2,hand);

	//1 创建key
	key_t key = ftok(".",'m');
	if(-1 == key) printf("创建key失败:%m\n"),exit(-1);
	printf("创建key成功!\n");
#if 0
	//2 获取共享内存   页  page  4K==1页
	int shmid = shmget(key,4096,IPC_CREAT);
	if(-1 == shmid) printf("创建共享内存失败:%m\n"),exit(-1);
	printf("创建共享内存:%m!\n");
#endif
	//3 挂载共享内存
	p = shmat(9535502,NULL,0);
	if(NULL == p) printf("挂载共享内存失败:%m\n"),exit(-1);
	printf("挂载共享内存:%m\n");

	//4 使用
	int  n=6;
	while(1){
		*p = n++;
		sleep(1);
	}
}

 

示例代码2:

shmw.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>

//       int shmget(key_t key, size_t size, int shmflg);
// void *shmat(int shmid, const void *shmaddr, int shmflg);

//       int shmdt(const void *shmaddr);

int main()
{
        int shmId;
        key_t key;
        char *shmaddr;

        if((key = ftok(".",1)) < 0){
                printf("ftok error\n");
        }

        shmId = shmget(key, 1024*4, IPC_CREAT|0666);//内存大小必须得是MB的整数倍

        if(shmId == -1){
                printf("shmget error\n");
                exit(-1);
        }

        /*第二个参数一般写0,让linux内核自动分配空间,第三个参数也一般写0,表示可读可写*/
        shmaddr = shmat(shmId, 0, 0);
        printf("shmat OK\n");
        strcpy(shmaddr,"I am so cool");

        sleep(5);//等待5秒,让别的进程去读

        shmdt(shmaddr);
        shmctl(shmId, IPC_RMID, 0);//写0表示不关心
        printf("quit\n");

        return 0;
}

shmr.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>

//       int shmget(key_t key, size_t size, int shmflg);
// void *shmat(int shmid, const void *shmaddr, int shmflg);

//       int shmdt(const void *shmaddr);

int main()
{
        int shmId;
        key_t key;
        char *shmaddr;

        if((key = ftok(".",1)) < 0){
                printf("ftok error\n");
        }

        shmId = shmget(key, 1024*4, 0);//内存大小必须得是MB的整数倍

        if(shmId == -1){
                printf("shmget error\n");
                exit(-1);
        }

        /*第二个参数一般写0,让linux内核自动分配空间,第三个参数也一般写0,表示可读可写*/
        shmaddr = shmat(shmId, 0, 0);
        printf("shmat OK\n");
        printf("data : %s\n",shmaddr);

        shmdt(shmaddr);

        return 0;
}

 


1.5 信号量

信号量与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

信号量的特点:

  1. 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
  2. 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
  3. 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
  4. 支持信号量组

信号量的函数原型:

// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);  
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);

当 semget 创建新的信号量集合时,必须指定集合中信号量的个数(即 num_sems),通常为 1; 如果是引用一个现有的集合,则将 num_sems 指定为 0 。

在 semop 函数中,sembuf 结构的定义如下:

struct sembuf 
{
    short sem_num; // 信号量组中对应的序号,0~sem_nums-1
    short sem_op;  // 信号量值在一次操作中的改变量
    short sem_flg; // IPC_NOWAIT, SEM_UNDO
}

在 semctl 函数中的命令有多种,这里就说两个常用的:

  • SETVAL:用于初始化信号量为一个已知的值。
  • IPC_RMID:删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。

示例代码1:

semA.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
 union semun {
    int              val;   
    struct semid_ds *buf;   
    unsigned short  *array;  
    struct seminfo  *__buf;  
};

int main(){
	//创建key
	key_t key = ftok(".",'s');
	if(-1 == key) printf("ftok 失败:%m\n"),exit(-1);
	printf("ftok %m\n");

	//创建信号量
	int semid = semget(key,1,IPC_CREAT|0666);
	if(-1 == semid)printf("创建信号量 失败:%m\n"),exit(-1);
	printf("创建信号量 %m\n");

	//初始化信号量
	union semun u;
	u.val = 5;

	semctl(semid,0,IPC_SET,&u);

	//使用信号量

	struct sembuf buf;

	buf.sem_num = 0;
	buf.sem_op = -1;//减1  加上 -1
	buf.sem_flg = 0;//操作方式 默认的

	int n=1;
	while(1){
		printf("讲课%d小时!\n",n++);
		semop(semid,&buf,1);//操作1次
	}

}

semB.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>

int main(){
	//创建key
	key_t key = ftok(".",'s');
	if(-1 == key) printf("ftok 失败:%m\n"),exit(-1);
	printf("ftok %m\n");

	//获取信号量
	int semid = semget(key,1,IPC_CREAT|0666);
	if(-1 == semid)printf("创建信号量 失败:%m\n"),exit(-1);
	printf("创建信号量 %m\n");

	

	//使用信号量

	struct sembuf buf;

	buf.sem_num = 0;
	buf.sem_op = 2;//加上 1
	buf.sem_flg = 0;//操作方式 默认的

	int n=1;
	while(1){
		semop(semid,&buf,1);//操作1次
		sleep(1);
	}

}

 

示例代码2:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>

//       int semget(key_t key, int nsems, int semflg);
//       int semctl(int semid, int semnum, int cmd, ...);
//       int semop(int semid, struct sembuf *sops, size_t nsops);
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) */
};

//P操作,拿钥匙
void PGetKey(int semid)
{
        struct sembuf sops;
        sops.sem_num = 0;
        sops.sem_op = -1;
        sops.sem_flg = SEM_UNDO;

        semop(semid, &sops, 1);
}

//V操作,放回钥匙
void VPutBackKey(int semid)
{
        struct sembuf sops;
        sops.sem_num = 0;
        sops.sem_op = 1;
        sops.sem_flg = SEM_UNDO;

        semop(semid, &sops, 1);
}

int main()
{
        key_t key;
        int semid;
        if((key == ftok(".",6)) < 0)
        {
                printf("ftok error\n");
        }

        semid = semget(key , 1,  IPC_CREAT|0666);//创造钥匙,数量为1

        union semun sem;
        sem.val = 0;//初始状态为没有钥匙
        semctl(semid, 0, SETVAL, sem);//SETVAL初始化信号量为一个已知的值,这时就需要第四个参数
                     //0表示操作第一把钥匙
        int pid = fork();

        if(pid < 0)
        {
                printf("fork failed\n");
        }else if(pid == 0)
        {
                printf("this is child\n");
                VPutBackKey(semid);//首先把钥匙放回     
        }else
        {
                PGetKey(semid);//等子进程将钥匙放回后才会进行操作,保证子进程先执行
                printf("this is father\n");
                VPutBackKey(semid);
                semctl(semid, 0, IPC_RMID);//销毁钥匙
        }

        return 0;
}

 


2. 总结

  • 管道:速度慢,容量有限,只有父子进程能通讯;
  • FIFO:任何进程间都能通讯,但速度慢;
  • 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题;
  • 共享内存:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题;
  • 信号:有入门版和高级版两种,区别在于入门版注重动作,高级版可以传递消息。只有在父子进程或者是共享内存中,才可以发送字符串消息;
  • 信号量:不能传递复杂消息,只能用来同步。用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
posted @ 2023-01-06 14:47  [BORUTO]  阅读(115)  评论(0编辑  收藏  举报