Linux-进程间的通信

1、IPC:

        Inter Process Communication(进程间通信):

        由于每个进程在操作系统中有独立的地址空间,它们不能像线程那样直接访问彼此的内存,所以必须通过某种方式进行通信。

        常见的    IPC     方式包括: 2、无名管道:

 管道的概念:

本质:

        1、内核缓冲区;

        2、伪文件-不占用磁盘空间;

特点:

        1、读端,写端对应两个文件描述符;

        2、数据写端流入,读端流出;

        3、操作管理的进程被销毁之后,管道自动被释放;

        4、管道默认式阻塞的;

创建匿名管道:

示例:

int pipe(int fd[2])

fd‐传出参数:
fd[0]‐读端
fd[1]‐写端

返回值:
0:成功
‐1:创建失败

实验:父子进程使用管道进行通信,实现 ps aux | grep “bash”:

        使用父子进程管道通信时读端和写端不能同时打开,父进程打开写端关闭读端;

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

int main()
{

        int ret;
        int fd[2];
        pid_t pid;
        ret = pipe(fd);
        pid  = fork();
        if(ret == -1)
        {
                printf("create pipe failed!");
                exit(1);
        }
        //ps aux
        if(pid>0)
        {
                close(fd[0]);//guanbi du duan da kai xie duan
                dup2(fd[1],STDOUT_FILENO);
                execlp("ps","ps","aux",NULL);
                exit(1);

        }
        //grep bush
        if(pid==0)
        {

                close(fd[1]);//guanbi xieduan da kai duduan
                dup2(fd[0],STDIN_FILENO);
                execlp("grep","grep","bush",NULL);

        }

        printf("pipe[0] is %d\n",fd[0]);
        printf("pipe[1] is %d\n",fd[1]);

        return 0;
}
~            

结果:

3、有名管道:

示例

int mkfifo(const char \*filename, mode_t mode)

功能: 创建管道文件;

参数: 管道文件名,权限,创建的文件权限和umask有关;

返回值: 创建成功返回0,创建失败返回-1;

特点:

        有名管道;

        伪文件,不占用磁盘空间;

        半双工通信方式;

使用场景:

        用于实现无血缘关系的进程间通信,使用mkfifo函数只会在用户区创建一个节点,不会在内核区创建管道,需要使用IO函数OPEN函数打开管道;

ret = mkfifo("/home/study/ipc/mkfifo",0755);
 
fd = open("./mkfifo",O_RDONLY);

实验:创建一个管道,让进程A写入进程B读出:

读进程:

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

int main()
{
        int ret;
        int fd;
        int nred;
        char readbuf[50]={0};
        //ret = mkfifo("/home/study/ipc/mufifo",0755);

        if(ret == -1)
        {
                printf("filed!");

        }
        printf("create success\n");

        fd = open("./mufifo",O_RDONLY);
        if(fd == -1)
        {
                printf("open filed\n");
        }
        printf("open success\n");

        nred = read(fd,readbuf,50);
        printf("read %d byte from fifo %s\n",nred,readbuf);
        printf("second read %d byte from fifo %s\n",nred,readbuf);
        close(fd);
        return 0;
}
~    
写进程:

#include<stdio.h>
#include<string.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
        char *str = "hello world";
        int fd;
        int ret;
        ret = mkfifo("/home/study/ipc/mufifo",0755);


        fd = open("./mufifo",O_WRONLY);

        if(fd < 0)
        {
                return -1;
        }
        printf("open fifo success\n");

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

        close(fd);
        return 0;

}                                                                                                                                                                                                                                                                                                                                                           

结果:

  注意:管道中的信息只能读取一次,读取完之后数据就被移除,但是管道还存在; 

4、消息队列:

概念:

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

特点:

        消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级;

        消息队列独立于发送和接收进程,进程终止时,消息队列及其内容仍存在;

        消息队列可以实现消息的随即查询,消息不一定要先进先出的次序读取,也可以按照消息类型进行读取;

两个进程间的通信:通过识别链表的ID来进行读写实现收发;

相关函数:

1、int msgget(key_t key, int msgflg);
//创建或打开消息队列,
参数:
key:和消息队列关联的key值
msgflg:是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或
操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被
忽略,而只返回一个标识符。
返回值:成功返回队列ID,失败则返回‐1,

在以下两种情况下,msgget将创建一个新的消息队列:
如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志
key参数为IPC_PRIVATE
2.int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//读取消息,成功返回消息数据的长度,失败返回‐1
参数:
msgid:消息队列的ID
msgp:指向消息的指针,常用结构体msgbuf如下:
struct msgbuf
{
long mtype; //消息类型
char mtext[N]; //消息正文
}
size:发送的消息正文你的字节数
flag:
IPC_NOWAIT 消息没有发送完成函数也会立即返回0:知道发送完成函数才返回
返回值:
成功:0
失败:‐1
3.ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
//从一个消息队列中获取消息
参数:
msgid:消息队列的ID
msgp:要接收消息的缓冲区
size:要接收的消息的字节数
msgtype:
0:接收消息队列中第一个消息
大于0:接收消息队列中第一个类型为msgtyp的消息
小于0:接收消息队列中类型值不大于msgtyp的绝对值且类型值又最小的消息。
flag:
0:若无消息函数一直阻塞
IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG。
返回值:
成功:接收到的消息i长度
出错:‐1

函数msgrcv在读取消息队列时,type参数有下面几种情况
type ==0,返回队列中的第一消息
type >0,返回队列中消息队列类型为type的第一个消息
type <0,返回队列中消息类型值小于或等于type绝对值的消息,如果有多个,则取类型值最小的消息。
可以看出,type值非0时用于以非先进先出次序读取消息,也可以把type看成优先级的权值
4.int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//控制消息队列,成功返回0,失败返回‐1
参数:
msqid:消息队列的队列ID
cmd:
IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆
盖msgid_ds的值。
IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
IPC_RMID:删除消息队列
buf:是指向 msgid_ds 结构的指针,它指向消息队列模式和访问权限的结构
返回值:
成功:0
失败:‐1
ftok函数
key_t ftok( char * fname, int id )
//系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
参数:
fname就时你指定的文件名(该文件必须是存在而且可以访问的)。
id是子序号, 虽然为int,但是只有8个比特被使用(0‐255)。
返回值:
当成功执行的时候,一个key_t值将会被返回,否则 ‐1 被返回

4、共享内存:

概念:

        共享内存(Shared Memory)就是允许多个进程访问同一个内存空间,是在多个进程之间共享和传递数据最高效的方式。操作系统将不同进程之间共享内存安排为同一段物理内存,进程可以将共享内存连接到它们自己的地址空间中,如果某个进程修改了共享内存中的数据,其它的进程读到的数据也将会改变;

        共享内存会从内核区映射一个地址空间到用户区,需要访问用户区的地址空间才能实现读写功能;

相关函数:

1.int shmget(key_t key, size_t size, int shmflg);
//用来获取或创建共享内存
参数:
key:IPC_PRIVATE 或 ftok的返回值
size:共享内存区大小
shmflg:同open函数的权限位,也可以用8进制表示法
返回值:
成功:共享内存段标识符‐‐‐ID‐‐‐文件描述符
出错:‐1
2.void *shmat(int shm_id, const void *shm_addr, int shmflg);
//把共享内存连接映射到当前进程的地址空间
参数:
shm_id:ID号
shm_addr:映射到的地址,NULL为系统自动完成的映射
shmflg:
SHM_RDONLY共享内存只读
默认是0,表示共享内存可读写
返回值:
成功:映射后的地址
失败:NULL
3.int shmdt(const void *shmaddr);
//将进程里的地址映射删除
参数:
shmid:要操作的共享内存标识符
返回值:
成功:0
出错:‐1
4.int shmctl(int shm_id, int command, struct shmid_ds *buf);
//删除共享内存对象
参数:
shmid:要操作的共享内存标识符
cmd :
IPC_STAT (获取对象属性)‐‐‐ 实现了命令ipcs ‐m
IPC_SET (设置对象属性)
IPC_RMID (删除对象) ‐‐‐实现了命令ipcrm ‐m
buf :指定IPC_STAT/IPC_SET时用以保存/设置属性
返回值:
成功:0
出错:‐1

实验:生成共享内存,并通过标准输入写入共享内存,并读出来打印屏幕上;

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>      // fork()
#include <sys/ipc.h>     // 共享内存所需
#include <sys/shm.h>     // shmget, shmat

int main()
{
        int shmid;
        int key;
        char *p;
        key = ftok("alarm.c",1);
        if(key<0)
        {
                printf("ftok failure\n");
                return -1;
        }
        printf("ftok success key:%x\n",key);
        shmid = shmget(key,128,IPC_CREAT|0777);
        if(shmid<0)
        {
                printf("create failure\n");
                return -2;
        }
        printf("create succ");
        system("ipcs -m");

        p = (char *)shmat(shmid,NULL,0);
        if(p = NULL)
        {
                printf("shmat funtion failure\n");
                return -3;
        }
        //write to
        fgets(p,128,stdin);
        //read
        printf("share data is %s",p);
        return 0;
}

结果:

5、信号:

信号通信的框架:

信号通信的框架:
    信号的发送(发送信号的进程):kill、raise、alarm
    信号的接收(接收信号的进程):pause()、sleep、while(1)
    信号的处理(接收信号的进程):signal
5.1、信号的发送(发送信号的进程):
1、kill:

#include<signal.h>
#include<sys/types.h>
函数原型:int kill(pid_t pid, int sig);
参数:
函数传入值:pid
正数:要接收信号的进程的进程号
0:信号被发送到所有和pid进程在同一个进程组的进程
‐1:信号发给所有的进程表中的进程(除了进程号最大的进程外)
sig:信号
函数返回值:成功 0 出错 ‐1
2、raise:发送信号给自己 == kill(getpid() , sig)

所需头文件:
#include<signal.h>
#include<sys/types.h>
函数原型:
int raise(int sig);
参数:
函数传入值:sig:信号
函数返回值:
成功 0 出错 ‐1

实验raise==kill(getpid(),sig):

#include<stdio.h>
#include <signal.h>


int main()
{

        printf("before sig\n");
        raise(9);
        printf("after sig\n");
        return 0 ;
}
~    

结果:  打印完“before sig”后进程执行kill动作,结束进程,不会打印“after sig”;

 5.2、alarm:发送闹钟信号的函数:

5.2.1、alarm与raise函数的比较:

相同点:让内核发送信号给进程

不同点:alarm只会发送SIGALARM信号;

               alarm会让内核定时一段时间在发送信号,raise会立即发送信号;

所需头文件#include <unistd.h>
函数原型 unsigned int alarm(unsigned int seconds)
参数:
seconds:指定秒数
返回值:
成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则 返回上一个闹钟时间的剩余时间,否则返回0。
出错:‐1

 实验:定时7秒向进程发送信号:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{

        int i = 0;
        printf("before alarm\n");
        alarm(7);
        printf("after alarm\n");
        while(i<20)
        {
                i++;
                sleep(1);
                printf("process %d\n",i);

        }
        return 0;

}
                                                                                               
                                                                                                 

结果:

        在第7秒时想进程发送信号,但是由于没有设置信号处理函数对这个信号进行处理,所以这个信号会让进程终止;

 

 信号的含义及默认操作:

信号名含义默认操作
SIGHUP
该信号在用户终端连接 ( 正常或非正常 ) 结束时发出,通常是在终端的控制进程结束 时,通知同一会话内的各个作业与控制终端不再关联。
终止
SIGINT
该信号在用户键入 INTR 字符 ( 通常是 Ctrl-C) 时发出,终端驱动程序发送此信号并送到 前台进程中的每一个进程。
终止
SIGQUIT
该信号和 SIGINT 类似,但由 QUIT 字符 ( 通常是 Ctrl-) 来控制。
终止
SIGILL
该信号在一个进程企图执行一条非法指令时 ( 可执行文件本身出现错误,或者试图执 行数据段、堆栈溢出时) 发出。
终止
SIGFPE
该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出 及除数为0 等其它所有的算术的错误。
终止
SIGKILL
该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略
终止
SIGALRM
该信号当一个定时器到时的时候发出。
终止
SIGSTOP
该信号用于暂停一个进程,且不能被阻塞、处理或忽略
暂停进程
SIGTSTP
该信号用于暂停交互进程,用户可键入 SUSP 字符 ( 通常是 Ctrl-Z) 发出这个信号
暂停进程
SIGCHLD
子进程改变状态时,父进程会收到这个信号
忽略
SIGABORT
该信号用于结束进程
终止
5.3、信号的处理:

收到信号的进程,应该怎样处理?

处理的方式:

        1、进程的默认处理(内核为用户进程设置的默认处理方式)

         A:忽略、B:终止进程、C:暂停

        2、自己的处理方式:自己处理信号的方法告诉内核,这样你的进程收到了这个信号就会采用你自己的的处理方式

所需头文件 #include <signal.h>
函数原型 void (*signal(int signum, void (*handler)(int)))(int);
函数传入值
signum:指定信号
handler
SIG_IGN:忽略该信号。
SIG_DFL:采用系统默认方式处理信号
自定义的信号处理函数指针
函数返回值
成功:设置之前的信号处理方式
出错:‐1
signal 函数有二个参数,第一个参数是一个整形变量(信号值),第二个参数是一个函数指针,是我们自己写的处理函
数;这个函数的返回值是一个函数指针。

6、信号灯 

信号灯集合(可以包含多个信号灯)IPC对象是一个信号的集合(多个信号量)

1、semget

函数原型: int semget(key_t key, int nsems, int semflg);
//创建一个新的信号量或获取一个已经存在的信号量的键值。
所需头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
函数参数:
key:和信号灯集关联的key值
nsems: 信号灯集中包含的信号灯数目
semflg:信号灯集的访问权限
函数返回值:
成功:信号灯集ID
出错:‐1
2、semctl:

函数原型:int semctl ( int semid, int semnum, int cmd,…union semun arg(不是地址));
//控制信号量,删除信号量或初始化信号量
所需头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
函数参数:
semid:信号灯集ID
semnum: 要修改的信号灯编号
cmd :
GETVAL:获取信号灯的值
SETVAL:设置信号灯的值
IPC_RMID:从系统中删除信号灯集合
函数返回值:
成功:0
出错:‐1
3、P V 操作:

int semop(int semid ,struct sembuf *sops ,size_t nsops);
//用户改变信号量的值。也就是使用资源还是释放资源使用权
包含头文件:
include<sys/sem.h>
参数:
semid : 信号量的标识码。也就是semget()的返回值
sops是一个指向结构体数组的指针。
struct sembuf{
unsigned short sem_num;//信号灯编号;
short sem_op;//对该信号量的操作。‐1 ,P操作,1 ,V操作
short sem_flg;0阻塞,1非阻塞
};
sem_op : 操作信号灯的个数
//如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权;如果sem_op的值为负
数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用
权;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。

实验: 通过信号灯操作和共享内存来共同实现父子进程通信:

 实现思路:

1、声明 P V 操作并进行初始化:p v操作需要配置函数,在使用P  V都可以这样进行配置

void Poperation(int index,int semid)
{
        struct sembuf sop;
        sop.sem_num = index;
        sop.sem_op = -1;
        sop.sem_flag = 0;

        semop(semid,&sop,1);


}

void Voperation(int index,int semid)
{
        struct sembuf sop;
        sop.sem_num = index;
        sop.sem_op = 1;
        sop.sem_flg = 0;

        semop(semid,&sop,1);


}

2、使用union semun初始化信号量:常与semctl()函数一起使用

union semun
{

        int val;
}

3、生成共享内存:

 int shmid; 
 char *shmaddr;       
 shmid = shmget(key,128,IPC_CREAT|0755);

4、宏定义读写两个信号量:

#define SEM_WRITE 1
#define SEM_READ 0

5、 使用semctl函数来控制读写两个信号量:

 //init semaphore
        union semum myun;
        //init semaphore read
        myun.val=0;
        semctl(semid,SEM_READ,SETVAL,myun);
        //init semaphore write
        myun.val = 1;
        semctl(semid,SEM_WRITE,SETVAL,myun);

6、生成父子进程,并分别实现父子进程的动作:

父进程实现从键盘输入,并执行写操作,读操作暂停:

子进程执行读操作,写操作暂停;

       pid_t pid;
       pid = fork();
        if(pid == 0)//child process
        {
                while(1)
                {
                        shmaddr = (char *)shmat(shmid,NULL,0);

                        Poperation(SEM_READ,semid);
                        printf("get share memory is %s\n",shmaddr);
                        Voperation(SEM_WRITE,semid);
                }
        }
        else if(pid>0)//father process
        {
                while(1)
                {
                        shmaddr =( char *)shmat(shmid,NULL,0);

                        Poperation(SEM_WRITE,semid);
                        printf("please input to share memory\n");
                        fgets(shmaddr,32,stdin);
                        Voperation(SEM_READ,semid);
                }

        }

完整代码:

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

#define SEM_WRITE 1
#define SEM_READ 0


//ding yi xin hao lian he ti 
// 必须手动定义 semun union,因为它不在标准头文件中定义
union semun {
    int val;                // 用于 SETVAL
    struct semid_ds *buf;   // 用于 IPC_STAT 和 IPC_SET
    unsigned short *array;  // 用于 GETALL 和 SETALL
    struct seminfo *__buf;  // 用于 IPC_INFO(可选,可以忽略)
};


void Poperation(int index,int semid)
{
        struct sembuf sop;
        sop.sem_num = index;
        sop.sem_op = -1;
        sop.sem_flg = 0;

        semop(semid,&sop,1);


}

void Voperation(int index,int semid)
{
        struct sembuf sop;
        sop.sem_num = index;
        sop.sem_op = 1;
        sop.sem_flg = 0;
        semop(semid,&sop,1);


}

void Voperation(int index,int semid)
{
        struct sembuf sop;
        sop.sem_num = index;
        sop.sem_op = 1;
        sop.sem_flg = 0;

        semop(semid,&sop,1);


}

int main()
{

        key_t key;
        key = ftok(".",123);
        if(key == -1)
        {
                perror("ftok");
                return -1;
        }
        int semid;
        int shmid;
        char *shmaddr;
        semid = semget(key,2,IPC_CREAT|0755);
        if(semid<0)
        {
                perror("semget");
                return -1;
        }
        shmid = shmget(key,128,IPC_CREAT|0755);
        if(shmid<0)
        {

                perror("shmget");
                return -2;
 }
        //init semaphore
        union semun myun;
        //init semaphore read
        myun.val=0;
        semctl(semid,SEM_READ,SETVAL,myun);
        //init semaphore write
        myun.val = 1;
        semctl(semid,SEM_WRITE,SETVAL,myun);

        pid_t pid;
        pid = fork();
        if(pid == 0)//child process
        {
                while(1)
                {
                        shmaddr = (char *)shmat(shmid,NULL,0);

                        Poperation(SEM_READ,semid);
                        printf("get share memory is %s\n",shmaddr);
                        Voperation(SEM_WRITE,semid);
                }
        }
        else if(pid>0)//father process
        {
                while(1)
                {
                        shmaddr =( char *)shmat(shmid,NULL,0);

                        Poperation(SEM_WRITE,semid);
                        printf("please input to share memory\n");
                        fgets(shmaddr,32,stdin);
                        Voperation(SEM_READ,semid);
                }

        }
        return 0;


}

 结果:父子进程就会交替执行读写操作,并不会同时占用共享内存:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值