linux进程(IPC)通信方法及使用

linux进程之间的通信(IPC)

进程之间的通信分为5种:
1.管道通信
2.消息队列
3.共享内存
4.信号
5.信号量

本文将详细讲解对应部分,文章目录如下

一、管道通信

对于管道通信可以看作特殊的文件,对于他的读写可以使用write,read函数进程操作但他不是普通文件,并不存在于文件系统当中,而是存在于内存当中。且数据只能被读取一次

管道又分为有名管道无名管道,通常管道指的是无名管道,管道通信方式采用半双工方式进行通信,同一时间内数据单项,一方只能发送,一方只能接受;且读写端固定;

无名管道

适用于兄弟进程或者父子进程之间的通信;
需要pipe函数来开辟管道;
linux环境下通过查阅man pipe
可以得到pipe函数所包含的头文件及函数使用方法等信息;
这里只拿出函数及所需头文件;
#include <unsistd.h>
int pipe(int pipefd[2]);

int pipe(int pipefd[2]);

这个函数返回值是一个int型,0为成功-1为失败,参数是俩个文件描述符,分别为读端与写段
pipefd[0]为读端
pipefd[1]为写端
对于他的读写可以使用write及read进行操作;
且一个进程当中只能使用一个读或写功能,比较“残疾”

无名管道的编程:

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

int main()	//主函数
{
        int fd[2];	//管道参数
        pid_t pid;	
        char *buf;
//       int pipe(int pipefd[2]);       //函数原型
        if(pipe(fd)<0){		//如果创建失败打印相应信息
                printf("create pipe failure\n");
        }

        pid = fork();		//创建子进程
        if(pid<0){			//创建失败
                printf("create child failure\n");
        }

        if(pid>0){			//如果为父进程则发出消息
                printf("this is father\n");
                sleep(3);
                close(fd[0]);//父进程封闭fd[0]读端
                write(fd[1],"Strange heng shuai",strlen("Strange heng shuai"));//父进程通过fd[1]端写入数据
                wait();//接受子进程退出状态
        }else{				//如果为子进程则接受消息
                printf("this is child\n");
                close(fd[1]);//子进程封闭fd[1]
                read(fd[0],buf,128);//子进程从fd[0]读取消息
                printf("form father message:%s",buf);
                exit(0);//结束子进程
        }

        return 0;
}              

运行结果
在这里插入图片描述

在形象一点就是,如下面以图片形式画出俩个进程的通信;
在这里插入图片描述
就想上面程序一样,关闭了一端读写功能使用另一端读写,使俩个进程之间进行发送和接受通信。

有名管道(FIFO)

有名管道,和无名管道类似,只不过文件系统中多出了一个管道文件,这个文件可以随意起名,可以使用open打开文件,read读取,write写入;还是比较残疾;
引用新函数
mkfifo(const char *pathname, mode_t mode);
在man手册第三章可以查看到函数原型及需要导入的库

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

函数执行成功返回0,失败则返回-1;俩个参数分别为:创建管道的路径及文件名字,和权限。
例如:mkfifo("./file",0666)
这段表示在当前文件夹(目录)下创建名字为file的管道文件,权限为0666(可读可写);

拓展:文件的权限:r 表示可读取,w 表示可写入,x 表示可执行,分别表示为数字为r=4,w=2,x=1;
权限又按用户的不同分为三类:User、Group、及Other三类用户的权限。
如,对于User用户,若拥有rw权限,则为4+2=6,所以0666中的666代表User、Group、及Other的权限分别是6,6,6,即均为rw权限。
而0666中的0代表不设置特殊的用户id,此处还可设为4,2,1,4代表具有root权限(即suid),2代表sgid,1代表sticky

好了解到了mkfifo的使用之后我们来创建有名管道进行不同进程之间的通信。

先看以下代码,这段代码的作用是进行读取管道文件里面的内容,并打印输出。

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

int main()
{
        int fd;
        char buf[30];
        if(mkfifo("./file",0666)==-1 && errno == EEXIST){		//创建管道
                perror("way");	//输出为什么进入这里
        }

        fd = open("./file",O_RDONLY);		//使用open打开文件,(O_RDONLY)只读

        read(fd,buf,30);		//从fd文件描述里面读取30个字节到buf

        printf("read success,content :%s\n",buf);//将读到的内容打印

        close(fd);	//关闭文件
        return 0;
}

程序结果
在这里插入图片描述

执行以上代码会阻塞到open那里,这是因为打开的是管道文件,而不是普通文件,因为管道里面没有数据,他阻塞在这里等待另一个进程(程序)进行写入,然后才能继续向下执行。

这时我们在写一个程序进行给管道写入数据。

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

int main()
{
        int fd;
        char *buf = "from fifo message";		//写入的数据
        fd = open("./file",O_WRONLY);			//写方式打开管道文件

        write(fd,buf,strlen(buf));				//给管道写入数据

        close(fd);
        return 0;
}          

写好代码之后,如果先运行写端代码的话同样会等待就不演示程序结果了,与读端一样,而他和读端阻塞条件却不同,写端代码等读端,读端代码等写段。
先运行读端,在运行写段之后程序结果为:
在这里插入图片描述
这就是有名管道的使用。

二、消息队列

消息队列是存放消息的链表,他存在于Linux内核当中,每一个消息队列用一个标识符也就是队列id来标识。
特点
1、消息队列可以独立发送与接受,成功创建后,如果进程结束,消息队列节点并不会消失,他的消失是由Linux内核 来管理的
2、消息队列是面向记录的,有特定格式及特点的优先级
3、消息队列可以实现消息的随机查询,消息不一定要以先进先出的顺序依次读取,也可以使用消息类型读取。
在这里插入图片描述

函数原型及结构体

typedef struct msgbuf {
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
}msg;
//获取消息队列id号
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);
//控制队列(用来销毁)
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

下面分为俩段代码
这段为先发送再接受:

#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);

typedef struct msgbuf {
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
}msg;


int main()
{
        msg buf = {667,"hellow wrold"};
        msg readbuf;

        int msgid;
        ket_t key;
        key = ftok(".",1);//创建key值与当前路径关联
        //msgid = msgget(0x1234,IPC_CREAT|0777);
        msgid = msgget(key,IPC_CREAT|0777)//创建key消息队列,权限为0777
        if(msgid<0){
                printf("create failure\n");
        }
		//msgsnd(消息队列id,数据,数据大小,默认方式)
        msgsnd(msgid,&buf,strlen(buf.mtext),0);//发送667类型数据
		//msgrcv(消息队列id,数据,数据大小,类型,默认方式(阻塞等待))
		//IPC_NOWAIT 非阻塞方式等待
        msgrcv(msgid,&readbuf,128,666,0);//接收类型666的数据
        
        printf("return from get %s\n",readbuf.mtext);
        
        msgctl(msgid,IPC_RMID,NULL)//销毁队列
        
        return 0;
}

这段为先接受在发送

#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);

typedef struct msgbuf {
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
}msg;


int main()
{
        msg buf = {666,"hello zhangjiaju"};
        msg readbuf;

        int msgid;
        key_t key;
        key = ftok(".",1);//创建key值
        msgid = msgget(key, IPC_CREAT|0777);//创建key队列
        //msgid = msgget(0x1234,IPC_CREAT|0777);
        if(msgid<0){
                printf("create failure\n");
        }
		//msgrcv(消息队列id,数据,数据大小,类型,默认方式(阻塞等待))
		//IPC_NOWAIT 非阻塞方式等待
        msgrcv(msgid,&readbuf,128,667,0);//接受667类型数据
		//msgsnd(消息队列id,数据,数据大小,默认方式)
        msgsnd(msgid,&buf,strlen(buf.mtext),0);//发送666类型数据
        
        printf("return from get %s\n",readbuf.mtext);
        
        msgctl(msgid,IPC_RMID,NULL)//销毁队列
        return 0;
}

之前写的时候用同样端口进行收发则发现,自己可能把自己的数据读走,这一点要注意

三、共享内存

在Linux内核当中开辟一个内存空间这个内存空间只能是以兆(M)为单位
俩个进程分别可以写入数据和读取。
注意:共享内存可以让俩个随意读写,但这样的操作会让内容混乱,造成的后果就是信息丢失变乱码。

后面的信号量就是起到控制共享内存这个临界资源,使得一个进程对他进行修改另一个只能等待修改完成。起到控制效果,具体放到信号量开始讲解。

函数原型

//创建共享内存
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 shmctl(int shmid, int cmd, struct shmid_ds *buf);

Strange head小贴士:在Linux中可以使用指令ipcs -m来查看共享内存的使用情况

创建并写入消息代码

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <string.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 shmctl(int shmid, int cmd, struct shmid_ds *buf);


int main()
{
        int shmid;
        char *shmaddr=NULL;		//共享内存的地址
        key_t key;
        key = ftok(".",1);		//生成key的值
        shmid = shmget(key,1024*4,IPC_CREAT|0666);	//生成
        if(shmid<0){
                printf("shmget failure\n");
                perror("way");
        }       
        //shmid共享内存标识符
		//shmaddr指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
		//shmflg,SHM_RDONLY:为只读模式,其他为读写模式
        shmaddr = shmat(shmid,0,0)strcpy(shmaddr,"hello zhangjiaju");//给内存写值

        printf("write data success!\n");
        printf("long...5s\n");
        sleep(5);       					//等待5s

        shmdt(shmaddr);						//断开连接

        shmctl(shmid,IPC_RMID,NULL);		//释放内存

        return 0;
}

获取并输出代码

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <string.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;
        char *shmaddr=NULL;
        key_t key;
        key = ftok(".",1);
        shmid = shmget(key,1024*4,0);				//链接内存
        if(shmid<0){
                printf("shmget failure\n");
                perror("way");
        }	
        shmaddr = shmat(shmid,0,0);					//获取地址

        printf("data is :%s\n",shmaddr);			//输出内容

        shmdt(shmaddr);								//断开链接

        return 0;
}

执行结果:
在这里插入图片描述

操作不当如何手动释放shm

共享内存(shm):使用ipcrm命令删除共享内存。

ipcrm -m <shmid>

其中<shmid>是要删除的共享内存的标识符。

四、信号

信号的三种常用操作:忽略,捕捉,默认动作

1.忽略信号:多少信号都可以通过这个来处理,但是有俩种信号不能忽略,SIGKILL,SIGSTOP因为他们向超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人管的进程,显然Linux内核设计者不希望看到这个场景。

2.捕捉信号:需要告诉内核,用户如何处理信号,就是写一个信号处理函数来捕捉信号,然后处理函数,当信号产生时由Linux内核来调用你这个函数来执行信号发生的处理,这就是信号的捕捉及使用。

3.默认动作:对于每个信号,系统都会默认执行一些动作,当发送信号,系统就会自动执行,多数来说,都是直接杀死进程的粗暴方法。
使用kill -7 signal来查看系统具体的定义。

信号就是类似单片机中断,单片机是硬件中断,而这里的信号则是软中断,比如你在看电视,快递到了你得去开门拿快递,这时快递员敲门被你听到了,这就是信号,这时你可以去开门去处理这个信号,也可以忽略,我就不收你能这么办,这就是信号的处理,如果此时快递员听到你不理他,他强行进来把你门打烂把你的快递给你,这就是忽略不了的信号,如kill -9 xxxx的信号;

kill是Linux的信号发送

String head小贴士:使用方法kill -l查看所有kill信号
平常退出进程使用的Ctrl+c就是kill方法里面的一种

信号的种类有这么多:
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

平常使用的kill -9 xxxx ,这个-9就是上面的杀死信号、xxxx是进程id,也可以写成kill -SIGKILL xxxx
如Ctrl+c也是信号调用了上面序列号为3的功能给当前进程类似于kill -2 当前进程id
kill实质是指定干什么,及作用到哪些进程里面。
只不过平时音译过来kill是杀死的意思,所以听到kill就说他是杀死进程的指令。实则是给进程发信号执行。

捕捉信号

入门函数 :Signalandkill 重点在动作没有其他消息
高级函数:可以传递信息

初级函数Signal的使用方法,初级函数

接受信号函数原型(入门)

指针函数
typedef void (*sighandler_t)(int);
俩个参数代表,检测的指令,处理函数
sighandler_t signal(int signum, sighandler_t handler);

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


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

//       sighandler_t signal(int signum, sighandler_t handler);

void handler(int signal)		//处理函数
{
        printf("signal = %d\n",signal);		//打印触发的信号指令代码
        printf("never quit\n");				//处理内容	
}
int main()
{
        printf("pid = %d\n",getpid());		//打印当前进程pid
        signal(SIGINT,handler);			//捕捉Ctrl+C的信号
        while(1);						//防止程序退出
        return 0;
}

执行结果

在这里插入图片描述

信号发送(入门)

俩个参数代表,目标pid,指令
发送信号函数原型int kill(pid_t pid, int sig);

#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
//       int kill(pid_t pid, int sig);

int main(int argc,int **argv)			//运行代码时接受俩个值
{
        int pid;
        int signum;

        pid = atoi(argv[2]);			//ascii转int
        signum = atoi(argv[1]);

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

        kill(pid,signum);		//发送指令

        printf("send signal ok\n");

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

 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 */
}

union sigval {
   int   sival_int;
   void *sival_ptr;
 };

接受端代码实现编程

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

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

           struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };

           siginfo_t {
               int      si_signo;    
               int      si_errno;    
               int      si_code;     
               int      si_trapno;   
                                     
                                     
               pid_t    si_pid;      
               uid_t    si_uid;     
               int      si_status;   
               clock_t  si_utime;    
               clock_t  si_stime;    
               sigval_t si_value;    
               int      si_int;      
               void    *si_ptr;      
               int      si_overrun;  
               int      si_timerid;  
               void    *si_addr;    
               long     si_band;     
                                     
               int      si_fd;       
               short    si_addr_lsb; 
                                       
           }

           union sigval {
               int   sival_int;
               void *sival_ptr;
           };
*/
//处理函数
void handler(int signum,siginfo_t *info,void *centext)
{
        printf("signum = %d\n",signum);//打印signum
        if(centext != NULL){	//如果有内容
        		//打印发送方的进程pid
                printf("from send pid = %d\n",info->si_pid);
                //打印发送的整形数据
                printf("be able to accept, message is %d\n",info->si_int);
                //打印发送方的整形数据,和上面一条作用一样
                printf("sival_int in si_value is %d\n",info->si_value.sival_int);
        }
}

int main()
{
        int signum;
        int pid;

        struct sigaction act;			//定义所需结构体

        printf("pid = %d\n",getpid());	//获取当前进程pid
										//mark不设置默认阻塞
        act.sa_sigaction = handler;		//设置处理函数
        act.sa_flags = SA_SIGINFO;		//设置可以接受消息

        sigaction(SIGUSR1,&act,NULL);	//配置sigaction函数

        while(1);						//防止程序结束

        return 0;
}

发送端代码实现编程

函数原型

#include <signal.h>

int sigqueue(pid_t pid, int sig, const union sigval value);

函数三个参数代表:pid进程id sig指令,value消息

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

int main(int argc ,char **argv)			//运行程序带参数
{
        int signum;	
        int pid;
        union sigval value;				//定义所需共用体
        signum = atoi(argv[1]);			//ascii转int
        pid = atoi(argv[2]);

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

        value.sival_int = 100;			//配置发送的整形数据

        sigqueue(pid,signum,value);		//向指定进程发送对应指令,并携带信息
        printf("done\n");
        return 0;
}

先运行接收端后运行发送端
接收端信息
在这里插入图片描述
发送端的信息
在这里插入图片描述

忽略信号

学习完高级信号编程之后,没错忽略信号就这么简单。

宏:SIG_IGN
导入头:signal.h
下述代码忽略Ctrl+C的信号
在这里插入图片描述
在这里插入图片描述

五、信号量

什么是信号量?

信号量就好比如,一间房子,一把钥匙,有很多人想进入这件房子办事情,第一个人来了,把钥匙取走进入房子,第二个人只能等待第一个人出来把钥匙拿上才能进入房子以此类推。那么这里的房子可以称作临界资源,钥匙就是信号量,钥匙也可以很多把,放到一起就是信号量集
信号量的俩个操作:
P操作:拿钥匙;
V操作:放钥匙;

临界资源:多道程序系统中存在许多进程,它们共享各种资源,然而有很多资源一次只能供一个进程使用。一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如输入机、打印机、磁带机等。

信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量VI,然后将Acquire Semaphore VI (获取信号量VI)以及Release Semaphore VI (释放信号量 VI)分别放置在每 个关键代码段的首末端。确认这些信号量VI引用的是初始创建的信号量。

函数原型

//key参数:该信号量集合对应的key值
//nsems参数:如果是创建新信号量集合,那么nsems代表新信号量集合中的信号量的数目。如果是引用一个现有的信号量集合,那么此参数指定为0
//flag参数:创建/使用信号量集合时的标志
int semget(key_t key, int nsems, int semflg);
//semid信号量id
//semnum操作第几个信号量
//cmd可设置SETVAL设置信号量的值,却省值为union semun类型的值
//cmd也可以设置GETVAL获取以及有的信号值,缺省值为NULL
int semctl(int semid, int semnum, int cmd, ...);
//nsops参数:对应于参数2的sops数组的元素个数
int semop(int semid, struct sembuf *sops, unsigned nsops)//P,V,操作
//semctl函数所需的共用体
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) */
};

//semop中所需的结构体
struct sembuf set;
set.sem_num = 0;
set.sem_op = 0;	// 0  -1   1
set.sem_flg = 0;	// SEM_UNDO
        
/*
一、如果sem_op是0
作用:这表示调用进程希望“等待”到该信号量值变为0
如果信号量值当前是0:则此函数立即返回
二、如果sem_op是正值
作用:释放sem_num对应的信号量的资源(所以信号量的值增加)。sem_op的值增加到sem_num对应的信号量上
如果指定了undo标志:则也从该进程的此信号量调整值中减去sem_op
三、如果sem_op是负值
作用:获取sem_num对应的信号量的资源(所以信号量的值减少)
1.如果该信号量的值>=sem_op的绝对值:从信号量值中减去sem_op的绝对值。这能保证信号量的结果值>=0。(如果指定了undo标志,则sem_op的绝对值也加到该进程的此信号量调整值上)
2.如果该信号量的值<sem_op的绝对值
默认值填0
SEM_UNDO :进程退出后,该进程对sem进行的操作将被撤销(例如对信号量值进行加1或减1操作,则进程退出后这些操作都撤销)
*/

使用信号量控制父子进程

实现功能

信号量集合一共为1个信号量,初始值为0,父子进程同时进入执行自己程序,因为信号量初始值为0,父亲拿不到信号,阻塞。此时子进程直接执行自己的程序并往里面放了一个信号,此时信号为1,父亲立马检测拿起信号执行程序所以,通过信号量控制先执行子进程后执行父进程。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.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, unsigned 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) */
};

void pGetKey(int semid)         //getkey
{
        struct sembuf set;
        set.sem_num = 0;                //semaphore id is 0
        set.sem_op = -1;                //key redusing 1
        set.sem_flg = SEM_UNDO;         //blocking
        semop(semid,&set,1);            //id,struct ,numbers
}

void vPutKey(int semid)
{
        struct sembuf set;
        set.sem_num = 0;                //semaphore id is 0
        set.sem_op = 1;                 //key add 1
        set.sem_flg = SEM_UNDO;         //blocking
        semop(semid,&set,1);            //id struct ,numbers

}

int main()
{
        int pid;
        int semid;
        key_t key;

        union semun initsem;

        key = ftok(".",2);

        semid = semget(key,1,IPC_CREAT|0666);   //use key create semid

        initsem.val = 0;        //number is keys

        semctl(semid,0,SETVAL,initsem);         //Operte the 0th semaphore,value the initsem
        pid = fork();           //create child

        if(pid>0){
                pGetKey(semid);
                printf("this is father\n");
                vPutKey(semid);
                semctl(semid,0,IPC_RMID);       //free semid
        }else if (pid ==0){
                printf("this is child\n");
                vPutKey(semid);
        }else{
                printf("create child fail\n");
        }


        return 0;
}

执行结果
在这里插入图片描述

使用信号量控制共享内存

实现功能

之前共享内存访问直接可以通过接收端到发送端的收发没有限制,限制通过信号量控制共享内存的访问
首先接受端创建信号量,初始值为0,接收端接着拿信号,会发现拿不上,就会一直等待发送端放下信号,发送端创建共享内存并往里面放入消息之后,放下信号,就会被接收端捕获,获取共享内存的值并打印。

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

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) */
};

void pGetKey(int semid)	//拿信号
{
        struct sembuf set;
        set.sem_num = 0;
        set.sem_op = -1;
        set.sem_flg = SEM_UNDO;
        semop(semid,&set,1);
}

int main()			
{
		//共享内存需要的参数
        int shmid;
        int pc2_pid;
        char *shmbuffer;
        key_t shm_key;
        
		//信号量需要的参数
        int semid;
        key_t sem_key;
        union semun initsem;
        
		//打印当前进程pid号,并创建俩个需要的key值
        printf("pc2_pid = %d\n",getpid());
        shm_key = ftok(".",2);
        sem_key = ftok(".",1);

		//创建信号量,个数为1个,id号为0,权限为0666
        semid = semget(sem_key,1,IPC_CREAT|0666);
	
		//设置id为0的初始值
        initsem.val = 0;
        semctl(semid,0,SETVAL,initsem);
        
        //拿起信号(因为初始值为0会一直阻塞于此)
        pGetKey(semid);
        shmid = shmget(shm_key,1024*4,0);
        if(shmid<0){
                printf("Mount fail\n");
                perror("way");
        }

		//指向共享内存打印内容
        shmbuffer = shmat(shmid,0,0);
        printf("context : %s\n",shmbuffer);

		//释放相关的资源
        shmdt(shmbuffer);
        shmctl(shmid,IPC_RMID,NULL);
        semctl(semid,0,IPC_RMID);
        return 0;
}

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <string.h>
#include <sys/sem.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 shmctl(int shmid, int cmd, struct shmid_ds *buf);
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) */
};

void vPutKey(int semid)		//放信号
{
        struct sembuf set;
        set.sem_num = 0;
        set.sem_op = 1;
        set.sem_flg = SEM_UNDO;
        semop(semid,&set,1);
}

int main()
{
		//共享内存需要的参数
        key_t shm_key;
        int pc1_pid;
        int shmid;
        char *shmbuffer;
		//信号量需要的参数
        int semid;
        key_t sem_key;
        union semun initsem;

		//生成key值
        sem_key = ftok(".",1);
        shm_key = ftok(".",2);

        printf("pc1_pid = %d\n",getpid());

		//获取信号量和共享内存创建的id
        semid = semget(sem_key,1,0666);
        shmid = shmget(shm_key,1024*4,IPC_CREAT|0666);
        if(shmid<0){
                printf("create show memory fail\n");
                perror("way");
        }
        if(semid<0){
                printf("create semaphore fail\n");
                perror("way");
        }

        semctl(semid,0,GETVAL,NULL);
        shmbuffer = shmat(shmid,0,0);
		
		//往内存写数据
        strcpy(shmbuffer,"zhangjiajuhengshuai !!!");
        shmdt(shmbuffer);
        //放下信号
        vPutKey(semid);

        return 0;
}

操作不当如何释放sem

信号量(sem):使用ipcrm命令删除信号量。

ipcrm -s <semid>

其中<semid>是要删除的信号量的标识符。

也可以使用semctl系统调用删除信号量

semctl(semid, 0, IPC_RMID);

注意:在删除共享内存和信号量之前,确保所有使用该共享内存和信号量的进程都已经终止。

如果还有进程在使用共享内存和信号量,则不能删除它们。这可能会导致程序崩溃或数据丢失。

在销毁共享内存和信号量之后,还应该使用shmdt和semctl系统调用来断开与共享内存和信号量的连接。

在使用这些命令或系统调用之前,请确保您有适当的管理员权限或root权限。

重要的是,在使用共享内存和信号量时,应该遵循良好的编程实践,在不再使用它们时及时删除它们,以避免内存泄漏和其他问题。

六、结束

希望高手们指教,后续涉及更深的内容,会持续完善此文章。
如果对您有帮助,点个赞支持一下吧;
转载请联系本人。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Strange_Head

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值