Linux系统进程间通信编程

目录

      • 1.进程间通信概述
        • Linux使用的进程间通信方式
          • 1)管道( pipe )
          • 2)信号量( semophore )
          • 3)消息队列( message queue )
          • 4)信号 ( singal )
          • 5)共享内存( shared memory )
          • 6)套接字( socket )
      • 2.管道通信原理
        • 1)匿名(无名)管道
          • 特点
          • 管道编程实战
        • 2)命名(有名)管道FIFO
          • 1.特点
          • 2.原型
        • 3)命名管道的数据通信编程实现
      • 3.消息队列的通信原理
      • 4.消息队列编程收发数据
      • 5.共享内存概述
      • 6.共享内存编程实现
      • 7.信号概述
      • 8.信号编程
      • 9.信号携带消息编程实现
      • 10.信号量

1.进程间通信概述

进程是一个独立的资源分配单元,不同进程(这里所说的进程通常指的是用户进程)之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源(例如打开的文件描述符)。

但是,进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信( IPC:Inter Processes Communication )。

Linux使用的进程间通信方式

1、管道(pipe),流管道(s_pipe)和有名管道(FIFO)

2、信号(signal)

3、消息队列

4、共享内存

5、信号量

6、套接字(socket)

1)管道( pipe )

管道这种通讯方式有两种限制,一是半双工的通信,数据只能单向流动,二是只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

流管道s_pipe: 去除了第一种限制,可以双向传输;

管道可用于具有亲缘关系进程间的通信;

命名(有名)管道:name_pipe克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;

2)信号量( semophore )

信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);

3)消息队列( message queue )

消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

4)信号 ( singal )

信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

主要作为进程间以及同一进程不同线程之间的同步手段。

5)共享内存( shared memory )

共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。

6)套接字( socket )

套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信

更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

2.管道通信原理

1.管道:通常指无名管道


2.特点:

(1).半双工,即数据只能在一个方向上流动,具有固定的读端和写段

(2).只能用于进程间的通信

(3).可看做一种特殊的存在于内存的文件,write,read等系统函数可对它进行操作


3.用法:
读时,关闭写
写时,关闭读


4.管道中数据读走就没了

1)匿名(无名)管道

特点

原型:

#include 
int pipe(int fd[2]);	//返回值:若成功返回0,失败返回-1

当一个管道建立时,它会创建两个文件描述符:

fd[0]	//读而打开
fd[1]	//写而打开

1.父进程创建管道,得到两个文件描述符指向管道的两端
Linux系统进程间通信编程_第1张图片
要关闭管道只需关闭两个文件描述符。

2.利用fork函数创建出子进程,则子进程也得到两个文件描述符指向同一管道

Linux系统进程间通信编程_第2张图片
3.父进程关闭读端(pipe[0]),子进程关闭写端pipe[1],则此时父进程可以往管道中进行写操作,子进程可以从管道中读,从而实现了通过管道的进程间通信。

#include
#include
 #include
int main()
{
int _pipe[2];
int ret=pipe(_pipe);
    if(ret<0)
    {
         perror("pipe\n");
    }
  pid_t id=fork();
  if(id<0)
{
       perror("fork\n");
   }
   else if(id==0)  // child
    {
        close(_pipe[0]);
        int i=0;
        char *mesg=NULL;
       while(i<100)
       {
           mesg="I am child";
           write(_pipe[1],mesg,strlen(mesg)+1);
           sleep(1);
           ++i;
        }
     }
    else  //father
   {
       close(_pipe[1]);
         int j=0;
        char _mesg[100];
         while(j<100)
        {
          memset(_mesg,'\0',sizeof(_mesg ));
          read(_pipe[0],_mesg,sizeof(_mesg));
          printf("%s\n",_mesg);
          j++;
        }
    }
   return 0;
}
#include 
#include 

int main()
{
	int fd[2]	//两个文件描述符
	pid_t pid;
	char buff[20];
	
	if(pipe(fd) < 0)	//创建管道
	{
		printf("Create Pipe Error!\n");
	}
	
	if((pid = fork()) < 0 )	//创建子进程
	{
		printf("Fork Error!\n");
	}else if(pid > 0)	//父进程
	{
		close(fd[0]);	//关闭读端
		write(fd[1],"hello  world\n",12);
	}
	
	else
	{
		close(fd[1]);	//关闭写端
		read(fd[0],buff,20);
		printf("%s",buff);
	}
	return 0;
}
管道编程实战
#include 
#include 
#include 

int main()
{
        int fd[2];
        int pid;
        char buf[128];

        //int pipe(int pipefd[2]);      

        if((pipe(fd)) == -1){
                printf("create pipe falied\n");
        }
        pid = fork();

        if(pid < 0){
                printf("create child failed\n");
        }else if(pid > 0){
                printf("this is father\n");

                close(fd[0]);
                write(fd[1],"hello from father",strlen("hello from father"));


        }else{
                printf("this is child\n");
                close(fd[1]);
                read(fd[0],buf,128);
                printf("read from father: %s\n",buf);
        }

        return 0;
}

在这里插入图片描述

int main(){
        int fd[2];
        pid_t repid;
        char buf[128];
        int repipe=pipe(fd);//创建无名管道
        if(repipe < 0){
                printf("creat unamed pipe failt\n");
        }
        repid=fork();//创建进程
 
        if(repid < 0){
                printf("creat fork failt\n");
        }
        else if(repid > 0){
                sleep(3);//等待3秒便于观察父子进程
                printf("this is father fork\n");
                close(fd[0]);//关闭可读通道
                write(fd[1],"you are a lucky dog",strlen("you are a lucky dog"));//往可写通道里写入内容
                wait(NULL);//让子进程先执行
        }
        else{
                printf("this is child fork\n");
                close(fd[1]);//关闭可写通道
                read(fd[0],buf,128);//读取可写通道内容
                printf("readBuf=%s\n",buf);
                exit(0);//正常退出
        }
        return 0;
}

在这里插入图片描述
无名管道不以文件的形式存放在磁盘的。

2)命名(有名)管道FIFO

1.特点

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

2.原型
#include 
//返回值:成功返回0,出错返回-1
int mkfifo(const char *pathname, mode_t mode);

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

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

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

#include 
#include 
#include 
#include 

int main()
{
        //int mkfifoat(int dirfd, const char *pathname, mode_t mode);

        int ret = mkfifo("./file1",0600);
        if(ret == 0){   //成功返回0
                printf("mkfifo success\n");
        }

        if(ret == -1){  //文件存在则打开失败
                printf("mkfifo failed\n");
        }


        return 0;
}

Linux系统进程间通信编程_第3张图片

#include 
#include 
#include 
#include 
#include 

int main()
{
        //int mkfifoat(int dirfd, const char *pathname, mode_t mode);

        if(mkfifo("./file1",0600) == -1 &&  errno == EEXIST){
                printf("mkfifo failed\n");
                perror("why");
        }

        else{
                if(errno == EEXIST){
                        printf("file exit\n");
                }else

                        printf("mkfifo success\n");
        }


        return 0;
}

Linux系统进程间通信编程_第4张图片

3)命名管道的数据通信编程实现

实现管道读取内容:

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

int main()
{
        char buf[1024] = {0};

        if((mkfifo("./file1",0600) == -1) &&  errno != EEXIST){
                printf("mkfifo failed\n");
                perror("why");
        }

        int fd = open("./file1",O_RDONLY);
        printf("open success\n");

        int n_read = read(fd,buf,40);

        printf("read %d byte from fifo, context:%s\n",n_read,buf);

        close(fd);
        return 0;
}

实现管道写入内容:

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

int main()
{
        char *str = "massage from fifo";


        int fd = open("./file1",O_WRONLY);
        printf("write open success\n");

        write(fd, str, strlen(str));

        close(fd);
        return 0;
}

先运行./read 再运行 ./write
Linux系统进程间通信编程_第5张图片

设置while(1)循环,一个不断地发一个不断地收:

读:

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

int main()
{
        char buf[1024] = {0};

        int n_read = 0;

        if((mkfifo("./file1",0600) == -1) &&  errno != EEXIST){
                printf("mkfifo failed\n");
                perror("why");
        }

        int fd = open("./file1",O_RDONLY);
        printf("open success\n");

        while(1){


                int n_read = read(fd,buf,40);

                printf("read %d byte from fifo, context:%s\n",n_read,buf);
        }


        close(fd);
        return 0;
}

写:

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

int main()
{
        char *str = "massage from fifo";
        int cnt = 0;

        int fd = open("./file1",O_WRONLY);
        printf("write open success\n");

        while(1){
                write(fd,str,strlen(str));
                sleep(1);
                if(cnt == 5){
                        break;
                }

        }


        close(fd);
        return 0;
}

Linux系统进程间通信编程_第6张图片

3.消息队列的通信原理

1.消息队列:即消息的链表,存放于内核中,每一个消息队列都有一个标识符

2.特点:

(1).消息队列是面向记录,具有特定的格式及优先级

(2).独立的接受与发送。进程终止时,消息队列不会被删除

(3).可以实现随机查询,可以按消息的类型读取

3.原型:API

int msgget(key_t key,int flag);//创建或打开消息队列,成功返回ID,反之-1

int msgsnd(ind msgid,const void *ptr,size_t size,int flag);//发送消息,成功返回0,反之-1

int msgrcv(int msgid,void *ptr,size_t size,long type,long flag);//读取消息,成功返回消息长度,反之-1

int msgctl((int msgid,int cmd,struct msgid_ds *buf);//控制消息队列,成功返回0 ,反之-1

4.参数功能:
从标识符为msgid的消息队列里接收一个指定类型的消息 并 存储于msgp中 读取后 把消息从消息队列中删除

msgtyp:为 0 表示无论什么类型 都可以接收
msgp:存放消息的结构体
msgsz:要接收的消息的大小 不包含消息类型占用的4字节
msgflg:如果是0 标识如果没有指定类型的消息 就一直等待,如果是IPC_NOWAIT 则表示不等待

4.消息队列编程收发数据

typedef struct msgbuf {
       long mtype;
       char mtext[80];
}msgs;
 
//key_t ftok(const char *pathname, int proj_id);
//int msgctl(int msqid, int cmd, struct msqid_ds *buf);
 
int main(){
        key_t key;//键值
        key=ftok(".",1);
        printf("key=%x\n",key);//%x 转化为16进制
        msgs readBuf;
        msgs sendBuf={998,"dugad is a handsome man"};
        int msgId=msgget(key,IPC_CREAT|0777);//创建消息队列,成功返回0,反之-1,并设置权限
        if(msgId == -1){//判断是否创建成功
                printf("get msg failure\n");
        }
        int receive=msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);//接受消息队列
        if(receive == -1){//判断是否接受成功
                printf("readBuf errno!\n");
        }
        else{
                printf("readBuf success!\n");
        }
        int send=msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//发送消息
        if(send == -1){//判断是否发送成功
                printf("sendBuf errno!\n");
        }
        else{
                printf("sendBuf success!\n");
        }
        printf("readBuf=%s\n",readBuf.mtext);//打印接受的内容
        int n_msgctl= msgctl(msgId,IPC_RMID,NULL);//清除消息队列
        printf("n_msgctl=%d\n",n_msgctl);
        if(n_msgctl == -1){//判断是否清除成功
                printf("remove msg errno!\n");
        }
        else{
                printf("remove msg success!\n");
        }
        return 0;
}
typedef struct msgbuf {
       long mtype;
       char mtext[80];
}msgs;
 
int main(){
        key_t key;
        key=ftok(".",1);
        printf("key=%x\n",key);
        msgs sendBuf={888,"this magsnd from quer"};
        msgs readBuf;
        int msgId=msgget(key,IPC_CREAT|0777);//创建消息队列,成功返回0,反之-1,并设置权限
 
        if(msgId == -1){
                printf("get msg failure\n");
        }
 
        int send=msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
        if(send == -1){
                printf("msgsend errno!\n");
        }
        else{
                printf("magsend success!\n");
        }
        int receive=msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),998,0);
        if(receive == -1){
                printf("readBuf errno!\n");
        }
        else{
                printf("readBuf success!\n");
        }
        printf("readBuf=%s\n",readBuf.mtext);
        int n_msgctl=msgctl(msgId,IPC_RMID,NULL);
        printf("n_msgctl=%d\n",n_msgctl);
        if(n_msgctl == -1){
                printf("remove msg errno!\n");
        }
        else{
                printf("remove msg success!\n");
        }
        return 0;
}

5.共享内存概述

1原理:就是开辟一个独立的内存空间,来实现通信。

通俗点说:就好像两个学生在上课的时候,由于不能说话,就只好拿一个本子来聊天,这个本子就相当于共享的内存。

2.原形:

int shmget(key_t key,size_t size,int flag);//创建或者获取一个共享内存,成功返回ID,反之-1

void *shmat(int shm_id,const void *addr,int flag);//连接共享内存到当前进程的地址空间,成功返回共享内存指针,失败返回-1、

int shmdt(void *addr);//断开与共享内存的连接,成功返回0,反之-1

int shmctl(int shm_id,int cmd,struct shmid_ds *buf);//控制共享内存相关信息,成功返回0,失败返回-1

3.注意:

key为ftok生成的键值 size为共享内存的长度,以字节为单位


flag为所需要的操作和权限,可以用来创建一个共享存储空间并返回一个标识符或者获得一个共享标识符。


flag的值为IPC_CREAT:如果不存在key值的共享存储空间,且权限不为0,则创建共享存储空间,并返回一个共享存储标识符。如果存在,则直接返回共享存储标识符。


flag的值为 IPC_CREAT |IPC_EXCL:如果不存在key值的共享存储空间,


且权限不为0,则创建共享存储空间,并返回一个共享存储标识符。如果存在,则产生错误。

4.补充:

函数ftok把一个已存在的路径名和一个整数标识符转换成一个key_t值,称为IPC键值(也称IPC key键值)。

ftok函数原型及说明:ftok(把一个已存在的路径名和一个整数标识符转换成IPC键值)

5.步骤:

1.创建共享内存/打开   shmget
2.映射                         shmat
3.数据交换                  
4.释放共享内存          shmdt
5.关闭共享内存           shmctl

6.共享内存编程实现

1.共享内存空间的读取

int main(){
        int shmId;
        char* addr;
        key_t key;
        key=ftok(".",1);
        shmId=shmget(key,1024*3,0);//直接获取空间
        if(shmId == -1){//判断是否创建成功
                printf("creat shared memory errno!\n");
                exit(-1);
        }
        //void *shmat(int shmid, const void *shmaddr, int shmflg);//链接共享内存到当前进程空间
        addr=shmat(shmId,0,0);
        if(*addr == -1){//判断是否链接成功
                printf("shmat errno!\n");
        }
        else{
                printf("shmat success!\n");
        }
        //strcpy(addr,"dugad is a handsome man");
        printf("read datas:%s\n",addr);//读取写入共享内存的数据
 
        //int shmdt(const void *shmaddr);//断开与共享内存的空间
        int n_shmdt=shmdt(addr);
        if(n_shmdt == -1){
                printf("shmdt errno!\n");
        }
        else{
                printf("shmdt success!\n");
        }
 
        //int shmctl(int shmid, int cmd, struct shmid_ds *buf);//对共享内存进行控制
        /*shmid就是shmget函数返回的共享存储标识符
        cmd有三个,常用删除共享内存的为IPC_RMID;IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中;IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内。
        buf就是结构体shmid_ds
        */
        int n_shmctl=shmctl(shmId,IPC_RMID,0);
        if(n_shmctl == -1){
                printf("shmctl errno!\n");
        }
        else{
                printf("shmctl success!\n");
        }
        return 0;
}

2.共享内存空间的写入

int main(){
        int shmId;
        char* addr;
        key_t key;
        key=ftok(".",1);
        shmId=shmget(key,1024*3,IPC_CREAT|0666);//创建共享内存空间
        if(shmId == -1){//判断是否创建成功
                printf("creat shared memory errno!\n");
                exit(-1);
        }
        //void *shmat(int shmid, const void *shmaddr, int shmflg);//链接共享内存到当前进程空间
        addr=shmat(shmId,0,0);
        if(*addr == -1){//判断是否链接成功
                printf("shmat errno!\n");
        }
        else{
                printf("shmat success!\n");
        }
        strcpy(addr,"dugad is a handsome man");//往共享内存内写入数据
 
        sleep(10);//给读取内容的shmWrite一点时间,不然后面就直接清除内存空间了
        //int shmdt(const void *shmaddr);//断开与共享内存的空间
        int n_shmdt=shmdt(addr);
        if(n_shmdt == -1){
                printf("shmdt errno!\n");
        }
        else{
                printf("shmdt success!\n");
        }
        //int shmctl(int shmid, int cmd, struct shmid_ds *buf);//对共享内存进行控制
       
        int n_shmctl=shmctl(shmId,IPC_RMID,0);
        if(n_shmctl == -1){
                printf("shmctl errno!\n");
        }
        else{
                printf("shmctl success!\n");
        }
        return 0;
}

7.信号概述

1.信号:对Linux来说就是软中断,与单片机的硬件中断类似。如在linux中输入 ctrl+c 来停止一个程序

2.信号的名字与编号:可在linux中通过 kill -l 查询(不存在0信号,0信号有特殊的应用:在系统级的应用中被占用)
Linux系统进程间通信编程_第7张图片
3. 信号的处理方式:忽略,捕捉和默认动作

忽略:就跟字面意思一样忽略掉它(注意:SIGKILL,SIGSTOP不能被忽略)

捕捉:就是一些信号处理的函数,然后让这个函数告诉内核,当信号产生时,内核调用该函数,实现某种信号的处理

默认动作:每个信号都有其对应的默认的处理动作,当触发了某种信号,系统就会立刻去执行。

4.信号处理函数的注册:signal ,sigaction

5.信号处理发送函数:kill ,sigqueue

8.信号编程

#include 
#include 
 
       //typedef void (*sighandler_t)(int);//函数指针,无返回值,传入参数为一个int型
       //sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum){
 
        switch(signum){
                case 2:
                        printf("get SIGINT,signum=%d\n",signum);
                        break;
 
                case 9:
                        printf("get SIGKILL,signum=%d\n",signum);
                        break;
                case 10:
                        printf("get SIGUSR1,signum=%d\n",signum);
                        break;
        }
        printf("never quit\n");
}
 
int main(){
 
        signal(SIGINT,handler);//捕捉信号
        signal(SIGKILL,handler);
        signal(SIGUSR1,handler);
        for(;;);//等于while(1);
 
        return 0;
}
       //int kill(pid_t pid, int sig);  
       //typedef void (*sighandler_t)(int);//函数指针,无返回值,传入参数为一个int型
       //sighandler_t signal(int signum, sighandler_t handler);
 
int main(int argc,char **argv){
 
        int signum;
        int pid;
        char cmd[128];
 
        signum=atoi(argv[1]);//atoi(),将字符串转为整型
        pid=atoi(argv[2]);
        printf("num=%d,pid=%d\n",signum,pid);
        //kill(pid,signum);//发送信号
        sprintf(cmd,"kill -%d %d",signum,pid);
 
        system(cmd);
        printf("send signal ok");
 
        return 0;
}

9.信号携带消息编程实现

#include 
#include 
#include 
#include 
 
// int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
//sigactio(),会依照参数signum指定的信号编号来设置该信号的处理函数
//const struct sigaction *act,你要做什么
//struct sigaction *oldact,是否备份,不备份用NULL
/* struct sigaction {
      void     (*sa_handler)(t struct sigaction *actnt);
此参数和signal()的参数handler相同,此参数主要用来对信号旧的安装函数signal()处理形式的支持
      void     (*sa_sigaction)(int, siginfo_t *, void *);
新的信号安装机制,处理函数被调用的时候,不但可以得到信号编号,而且可以获悉被调用的原因以及产生问题的上下文的相关信息。
      sigset_t   sa_mask;
用来设置阻塞,阻塞作用,默认就是阻塞的作用
      int        sa_flags;
用来设置信号处理的其他相关操作,下列的数值可用,可用OR运算(|)组合 
SA_SIGINFO:信号处理函数是带有三个参数的sa_sigaction 
      void     (*sa_restorer)(void);
 };
*/
 
void  handler(int signum,siginfo_t *info, void *context){//void *context,空值无数据,非空有数据传来
 
        printf("receive signum:%d\n",signum);
 
        if(context != NULL){//判断是否有数据传来
                printf("get datas:%d\n",info->si_int);//siginfo_t,是一个结构体,通过调用相应的结构体类型打印接受的值            
                printf("get datas:%d\n",info->si_value.sival_int);//si_value,也是一个结构体             
                printf("get pid:%d\n",info->si_pid);//打印进程号
                printf("get string:%s\n",(char*)info->si_value.sival_ptr);//接受字符串
        }
 
}
int main(){
        struct sigaction act;
        printf("pid=%d\n",getpid());
        act.sa_sigaction=handler;//函数指针指向handler
        act.sa_flags=SA_SIGINFO;//接受信息需要使用该宏定义
 
        int n_sig=sigaction(SIGUSR1,&act,NULL);//注册信号
        if(n_sig == -1){
                printf("errno!\n");
        }
        else{
                printf("success!\n");
        }
        for(;;);//等于while(1)
        return 0;
}
// int sigqueue(pid_t pid, int sig, const union sigval value);//发送消息
/*
pid_t pid:信号的pid号
int sig:要发送的信号
const union sigval value:共用体
*/
int main(int argc ,char **argv){
 
        int signum;
        pid_t pid;
 
        signum=atoi(argv[1]);//传入的参数1
        pid=atoi(argv[2]);//传入的参数2
 
        union sigval value;//定义一个共用体
        value.sival_int=100;//发送一个整型数据
        value.sival_ptr="you are a lucky dog\n";
//      strcpy(value.sival_ptr,"you are a lucky dog");  
        int n_sigqueue=sigqueue(pid,signum,value);
 
        printf("sigqueue's pid=%d\n",getpid());
        printf("Ending!\n");
        return 0;
}

10.信号量

1.信号量:是一个计数器,用于实现进程间的互斥与同步,不用于存储进程间的通信数据

2.特点:

(1).用于进程间同步,若要在进程间传递数据需要结合共享内存

(2).信号量是基于PV操作,程序对信号量是原子操作

(3).对信号量的操作不仅限于对信号的+1,-1,可以是任意数

3.原型:

int semget(key_t key,int num_sems,int sem_flags);//创建或获取一个信号量组,成功返回ID,反之-1

int semop(int semid,struct sembuf semoparry[],size_t numops);//对信号量组操作,改变信号量的值,成功返回0,反之-1

int semctl(int semid,int sem_num,int cmd,…);//控制信号量相关操作

4.PV操作

可以这样理解:一间房间的门前有一个盒子,盒子有钥匙,一个人拿了钥匙(P操作),开了门并走进了房间,且门外还有人等着,得等进去的人出来放钥匙(V操作),这个人才能拿钥匙(P操作)进去房间

        //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) */
};
void pgetKey(int semId){//取钥匙操作
 
        struct sembuf set;
        set.sem_num=0;//信号量的编号
        set.sem_op=-1;//取钥匙-1
        set.sem_flg=SEM_UNDO;//设置为当进程截止的时候,取消对锁的操作
        printf("getkey success!\n");
}
 
void vputBackKey(int semId){//放钥匙操作
 
        struct sembuf set;
        set.sem_num=0;//信号量的编号
        set.sem_op=1;//取钥匙+1
        set.sem_flg=SEM_UNDO;//设置为当进程截止的时候,取消对锁的操作
 
        semop(semId,&set,1);
        printf("putBackey success!\n");
}
int main(){
 
        int semId;
        key_t key;
        key=ftok(".",3);
 
        semId=semget(key,1,IPC_CREAT|0666);//创建或获取信号量,1,表示信号量集合中有一个信号量
 
        union semun initsem;
        initsem.val=0;//设置钥匙(这里表示钥匙被拿了)
        int n_semctl=semctl(semId,0,SETVAL,initsem);//初始化信号量,0,表示操作第0个信号(即操作第一位);SETVAL设置信号量的初值,设置>为inisem
 
        pid_t pid;
        pid=fork();//创建进程
        if(pid > 0){
                pgetKey(semId);//拿锁(前面val设置为0,等于没有锁,拿不到锁会让子进程先运行)
                printf("this is father fork!\n");
                vputBackKey(semId);//放钥匙
        }
        else if(pid == 0){
 
                printf("this is child fork!\n");
                vputBackKey(semId);//放钥匙
        }
        else{
                printf("creat fork errno!\n");
        }
 
        return 0;
}

你可能感兴趣的:(Linux,C语言学习)