Linux学习------进程间通讯

1.管道 pipe 和 FIFO

  1. 管道 通常指无名管道 半双工 只能用于有亲缘关系(父子进程、兄弟进程)的进程之间通讯

  2. FIFO 命名管道 用于无关进程之间的数据交换 创建之后可以用open等文件io函数操作 open时可以用设置非阻塞标志(O_NONBLOCK )(没意义) FIFO时在读或者写文件都行 两种管道都是半双工机制,只能一端读一端写

  3. 无名管道使用

    // fd[0]为读而打开,fd[1]为写而打开。为父子进程通讯
    #include<stdio.h>
    #include<unistd.h>
     
    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;
    }
    
  4. FIFO 定义读和写文件
    write_fifo.c

    #include<stdio.h>
    #include<stdlib.h>   // exit
    #include<fcntl.h>    // O_WRONLY
    #include<sys/stat.h>
    #include<time.h>     // time
     
    int main()
    {
        int fd;
        int n, i;
        char buf[1024];
        time_t tp;
     
        printf("I am %d process.\n", getpid()); // 说明进程ID
     
        if((fd = open("fifo1", O_WRONLY)) < 0) // 以写打开一个FIFO
        {
            perror("Open FIFO Failed");
            exit(1);
        }
     
        for(i=0; i<10; ++i)
        {
            time(&tp);  // 取系统当前时间
            n=sprintf(buf,"Process %d's time is %s",getpid(),ctime(&tp));
            printf("Send message: %s", buf); // 打印
            if(write(fd, buf, n+1) < 0)  // 写入到FIFO中
            {
                perror("Write FIFO Failed");
                close(fd);
                exit(1);
            }
            sleep(1);  // 休眠1秒
        }
     
        close(fd);  // 关闭FIFO文件
        return 0;
    }
    

    read_fifo.c

    #include<stdio.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<fcntl.h>
    #include<sys/stat.h>
     
    int main()
    {
        int fd;
        int len;
        char buf[1024];
     
        if(mkfifo("fifo1", 0666) < 0 && errno!=EEXIST) // 创建FIFO管道
            perror("Create FIFO Failed");
     
        if((fd = open("fifo1", O_RDONLY)) < 0)  // 以读打开FIFO
        {
            perror("Open FIFO Failed");
            exit(1);
        }
     
        while((len = read(fd, buf, 1024)) > 0) // 读取FIFO管道
            printf("Read message: %s", buf);
     
        close(fd);  // 关闭FIFO文件
        return 0;
    }
    

2.消息队列 massage

关心如何加消息and如何读取消息到消息队列
消息队列本质是带有序列的单链表,链表由Linux内核管理。链表的节点即为一条消息。从链表中读取消息之后消息不回被删除。
消息队列一个进程可以边读边写,两个A和B进程需使用同一个队列通讯
消息队列可以对链表中的消息进行条件筛选,没有读取次序要求,根据读函数msgrcv的第三个参数long msgtype

msg_server.c 服务端

#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
 
// 用于创建一个唯一的key
#define MSG_FILE "."       /// 表示当前文件夹
 
// 消息结构
struct msg_form {
    long mtype;
    char mtext[256];
};
 
int main()
{
    int msqid;
    key_t key;
    struct msg_form msg;
 
    // 获取key值,第一个参数表示哪个文件夹  "." 当前文件夹     第二个参数表示id是子序号,使用8bits(1-255)
    if((key = ftok(MSG_FILE,'z')) < 0)
    {
        perror("ftok error");
        exit(1);
    }
 
    // 打印key值
    printf("Message Queue - Server key is: %d.\n", key);
 
    // 创建消息队列
    if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
    {
        perror("msgget error");
        exit(1);
    }
 
    // 打印消息队列ID及进程ID
    printf("My msqid is: %d.\n", msqid);
    printf("My pid is: %d.\n", getpid());
 
    // 循环读取消息,客户端进程边读边写
    for(;;)
    {
    	//		 256指定mtext的大小,第三参表示消息类型mtype,第四参表示设置阻塞
        msgrcv(msqid, &msg, 256, 888, 0);// 返回类型为888的第一个消息
        printf("Server: receive msg.mtext is: %s.\n", msg.mtext);
        printf("Server: receive msg.mtype is: %d.\n", msg.mtype);
 
        msg.mtype = 999; // 客户端接收的消息类型
        sprintf(msg.mtext, "hello, I'm server %d", getpid());
        msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
        //               第三参,消息数据的长度
    }
    return 0;
}

msg_client.c 客户端

#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
 
// 用于创建一个唯一的key
#define MSG_FILE "."
 
// 消息结构
struct msg_form {
    long mtype;
    char mtext[256];
};
 
int main()
{
    int msqid;
    key_t key;
    struct msg_form msg;
 
    // 获取key值
    if ((key = ftok(MSG_FILE, 'z')) < 0)
    {
        perror("ftok error");
        exit(1);
    }
 
    // 打印key值
    printf("Message Queue - Client key is: %d.\n", key);
 
    // 打开消息队列
    if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
    {
        perror("msgget error");
        exit(1);
    }
 
    // 打印消息队列ID及进程ID
    printf("My msqid is: %d.\n", msqid);
    printf("My pid is: %d.\n", getpid());
 
    // 添加消息,类型为888
    msg.mtype = 888;
    sprintf(msg.mtext, "hello, I'm client %d", getpid());
    msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
 
    // 读取类型为999的消息
    msgrcv(msqid, &msg, 256, 999, 0);
    printf("Client: receive msg.mtext is: %s.\n", msg.mtext);
    printf("Client: receive msg.mtype is: %d.\n", msg.mtype);
    return 0;
}

3.共享内存 shared memory

无名管道和命名管道都是半双工通讯方式,而共享内存是全双工。

两个人通过纸条传送消息
第一种通讯:A写数据在纸条上,纸条放在桌子上,B拿走纸条读取数据(管道通讯
第二种通讯:A把写有数据的纸条1放在桌子上,B可以直接看到(读取),纸条留在桌子上不拿走;B把写有数据的纸条2放在桌子上,A可以直接看到(读取),纸条不拿走(消息队列
第三种通讯:桌子上有一张白纸,A和B都能在纸张上写数据以及看到纸张上的内容,纸张一直放在桌子上(共享内存

步骤:a.创建共享内存 b.映射内存到进程A和进程B c.读写数据进行数据交换 d.断开映射 e.删除共享内存

案例:

  1. 在共享内存写数据

    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    
    //int shmget(key_t key, size_t size, int shmflg);
    int main()
    {
    	
    	int shmid;
    	char *shmaddr;
    	key_t key;
    	key = ftok(".",1);
    	
    	// 第二个参数1024*4 共享内存的大小单位M,第三个参数表示创建共享内存
    	shmid = shmget(key,1024*4,IPC_CREAT|0666);  	 // 写的时候创建,读的时候默认flg为0
    	if(shmid == -1){
    		printf("shmget failed!\n");
    		exit(-1);
    	}
    	// 第二参,内核自动安排内存,第三参,0表示映射的内存可读可写。可以改为只读/只写
    	shmaddr = shmat(shmid,0,0); 					 // 将共享内存和进程链接,链接到指针shmaddr
    	printf("shmat ok\n");
    	strcpy(shmaddr,"chenlichen");					 // 复制字符串到shmaddr(指针)
    	sleep(5);										 // 等待读取数据
    	shmdt(shmaddr);									 // 断开映射
    	shmctl(shmid, IPC_RMID, 0);						 // 关闭共享内存
    	printf("quit\n");
    	return 0;
    }
    

    参考:strcpy函数详解:字符串【复制】的利器

  2. 从共享内存读数据

    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
    	int shmid;
    	char *shmaddr;
    	key_t key;
    	key = ftok(".",1);
    	// 创建共享内存,第三参,0直接获取共享内存
    	shmid = shmget(key,1024*4,0);
    	if(shmid == -1){
    		printf("shmget noOk\n");
    		exit(-1);
    	}
    	// 映射
    	shmaddr = shmat(shmid,0,0);
    	// 读数据
    	printf("data: %s\n:",shmaddr);
    	// 关闭映射
    	shmdt(shmaddr);
    	// 退出
    	printf("quit\n");
    	return 0;
    }
    
  3. 如果共享内存没有删除 通过下述指令查看并删除
    在这里插入图片描述

    两个指令ipcs -mipcs -m id号

4.信号signal

传送门:Linux信号(signal)

  1. 本质是主进程在运行时收到子进程的信号通知后,在主进程中进行操作函数。对Linux而言 信号本质是软中断。
    对信号而言最大的意义是实现异步通讯手段(捕捉信号)

    案例:

    • 一个人在看电视(主进程),有人敲门(信号1),有人给他打电话(信号2
    • 这个人先处理那个信号呢?(信号优先权
    • 听到敲门声一定会给那个人开门吗?(捕捉信号,可以先问对方是谁在开门)
    • 这个人既不理会敲门人又忽略电话铃声呢?(忽略信号
    • 听到手机铃声通常就第一时间拿起手机接听(默认动作) man 7 signal
  2. 案例,捕捉键盘的Ctrl + C(对应sigint信号),进行其他操作 (默认动作杀死程序的进程)
    参考:关于linux中的Ctrl+C,Ctrl+Z,Ctrl+D

    • 初级信号处理
    #include <signal.h>
    #include <stdio.h>
    // typedef void (*sighandler_t)(int);
    // sighandler_t signal(int signum, sighandler_t handler);
    // 重新定向的函数,参数为信号编号
    void handler(int signum){
    	printf("get signum=%d\n",signum);
    	switch(signum){
    		case 2:
    			printf("SIGINT\n");
    			break;
    		case 9:
    			printf("SIGKILL\n");
    			break;
    		case 10:
    			printf("SIGUSR1\n");
    			break;
    	}
    	printf("never quit\n");
    }
    int main(){
    	signal(SIGINT, SIG_IGN);		// 忽略SIGINT信号,对应Ctrl+C指令
    	signal(SIGKILL, handler);		// 对于SIGKILL信号,无法捕捉忽略重新定向
    	signal(SIGUSR1, handler);		// 捕捉SIGUSR1信号,对应指令kill -10  进程号
    	while(1);
    	return 0;
    }
    

    通过另一个程序发送指令

    #include <signal.h>
    #include <stdio.h>
    #include <sys/types.h>
    
    int main(int argc ,char **argv){
    	int signum;
    	int pid;
    	char cmd[128]={0};
    
    	signum = atoi(argv[1]);						// atoi将字符串转为整数
    	pid = atoi(argv[2]);
    	printf("num=%d,pid=%d\n", signum, pid);	
    
    //	kill(pid, signum); 							// 默认kill发指令
    	sprintf(cmd,"kill -%d %d", signum, pid);  	// 将sprintf组合成 kill -9  4656  类型指令存到cmd中
    	system(cmd);								// 函数system将cmd指令发
    	printf("send signal ok");	
    	return 0;
    }
    

    入门级信号处理kill发送指令,signal绑定指令;
    入门信号处理忽略了信号携带的信息。(只敲门不讲话)
    函数atoi的使用
    函数sprintf的使用
    函数system的使用

    • 高级信号处理
      在这里插入图片描述

      信号发送函数用到的结构体

      #include <signal.h>				// // 收发数据的结构体
      int sigqueue(pid_t pid, int sig, const union sigval value);
      union sigval {					
         int   sival_int;				// 直接将整型数据传到这里进行收发
         void *sival_ptr;				// 数据存好之后,将该指针指向这里	***********
       };
      
      int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
      			// 信号编号       // 新绑定的信号的行为           // 原有的信号的行为
      
      struct sigaction {   									 //4个参数  回调函数句柄sa_handler、sa_sigaction只能任选其一
         void       (*sa_handler)(int); 						 // 不处理消息
         void       (*sa_sigaction)(int, siginfo_t *, void *); // 信号处理程序,能够接受额外数据和sigqueue配合使用(3个参数 )
         sigset_t   sa_mask;				  	    			 // 处理信号可以阻塞
         int        sa_flags;									 // 指定为SA_SIGINFO表示能够接受数据
       };
      

      高级信号处理可以用信号携带信息。(边敲门边讲话)
      注意两点:①发信号(用什么发?怎么放入消息?) & ②收信号(来信号怎么处理?如何读出信号里的消息?)
      sigaction来信号之后处理函数 读出结构体中的参数即为读消息.(定义结构体sigaction)
      sigqueue发信号

      搜索man手册中的字段:/字段 字段代表要搜的内容

      其中自定义函数handler中的sig_info 结构体

       siginfo_t {
                     //****发送数据进程的pid 
                     pid_t    si_pid;      /* Sending process ID */           
      				
      			   // ****接收的数据存在于这里
      			   ===================
                     sigval_t si_value;    /* Signal value */
                     int      si_int;       /* POSIX.1b signal */
                     ===================
      			   // 还有很多数据没有展示   常用的只有以上三个
      }
      

      请留意两个用于传输数据的变量:sigval_t si_value; /* Signal value / 和 int si_int; / POSIX.1b signal */
      实际上:

      • siginfo_t 结构体中(sigval_t) si_value就是sigqueue函数中传入的第三个参数sigval
      • siginfo_t 结构体中(int) si_int就是从sigqueue函数中传入的第三个参数sigval.sival_int中获得
      • siginfo_t 结构体中si_ptr就是从sigqueue函数中传入的第三个参数sigval.sival_ptr中获得

    案例:
    信号发送进程

    #include <stdio.h>
    #include <signal.h>
    
    int main(int argc, char **argv)				// 传入的第一个参数为信号的编号,第二个为要发的进程的pid
    {
    	int signum;
    	int pid;
    	
    	signum = atoi(argv[1]);					// 发送哪种信号? 信号编号
    	pid = atoi(argv[2]);					// 给哪个进程发消息
    	
    	// 定义要发送的内容
    	int a = 10;
    	union sigval value;
    	// value.sival_int = 100;
    	// strcpy(value.sival_ptr,"vale");
    	value.sival_ptr=&a;						// 这里直接把发送的内容,让指针指向这里
    
    	// 用sigqueue函数发送内容到响应的进程(pid)
    	// int sigqueue(pid_t pid, int sig, const union sigval value);
    	sigqueue(pid,signum,value);
    	
    	printf("%d,done\n",getpid());		   // 表示发送完毕
    	return 0;
    }
    

    信号接收进程

    #include <signal.h>
    #include <stdio.h>
    
    //  int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
    void  handler(int signum , siginfo_t *info, void *context)	// 信号处理函数
    {
    	printf("get signum %d\n",signum);						// 信号编号
    	if(context != NULL){									// 防止没有数据产生段错误
    		printf("from:%d\n",info->si_pid);					// 打印发送进程pid
    //		printf("get data=%d\n",info->si_int);				// 读
    //		printf("get data=%d\n",info->si_value.sival_int);
    		printf("get data=%d\n",*(int *)(info->si_value.sival_ptr));		// 打印数据
    	}
    }
    
    int main()
    {
    	struct sigaction act;
    	printf("pid = %d\n",getpid());
    
    	act.sa_sigaction = handler;								// 设置操作函数
    	act.sa_flags = SA_SIGINFO; 								// 设置能接受数据
    
    	sigaction(SIGUSR1,&act,NULL);							// 绑定siaction
    	while(1);
    	return 0;
    }
    

5.信号量 Semaphore

传送门:进程间通讯介绍IPC,阅读信号量部分
本质是一间房子被锁住,从钥匙箱子拿钥匙去开门,一把锁可以有很多钥匙

  1. pv操作案例
    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/sem.h>
     
    // 联合体,用于semctl初始化
    union semun
    {
        int              val; /*for SETVAL*/   // 相当于钥匙的数量
        struct semid_ds *buf;
        unsigned short  *array;
    };
     
    // 初始化信号量
    //param:value锁有几把钥匙
    int init_sem(int sem_id, int value)
    {
        union semun tmp;
        tmp.val = value;
        // 参数0是操作第一个信号量(从0开始),用SETVAL设置信号量的值,值为tmp
        if(semctl(sem_id, 0, SETVAL, tmp) == -1)  // 初始化信号量,并判断是否成功
        {
            perror("Init Semaphore Error");
            return -1;
        }
        return 0;
    }
     
    // P操作:
    //    若信号量值为1,获取资源并将信号量值-1
    //    若信号量值为0,进程挂起等待
    int sem_p(int sem_id)
    {
        struct sembuf sbuf;
        sbuf.sem_num = 0; /*序号*/
        sbuf.sem_op = -1; /*P操作*/
        sbuf.sem_flg = SEM_UNDO;
     
        if(semop(sem_id, &sbuf, 1) == -1)
        {
            perror("P operation Error");
            return -1;
        }
        return 0;
    }
     
    // V操作:
    //    释放资源并将信号量值+1
    //    如果有进程正在挂起等待,则唤醒它们
    int sem_v(int sem_id)
    {
        struct sembuf sbuf;
        sbuf.sem_num = 0; /*序号*/
        sbuf.sem_op = 1;  /*V操作*/
        sbuf.sem_flg = SEM_UNDO;
     
        if(semop(sem_id, &sbuf, 1) == -1)
        {
            perror("V operation Error");
            return -1;
        }
        return 0;
    }
     
    // 删除信号量集
    int del_sem(int sem_id)
    {
        union semun tmp;
        if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
        {
            perror("Delete Semaphore Error");
            return -1;
        }
        return 0;
    }
     
     
    int main()
    {
        int sem_id;  // 信号量集ID
        key_t key;
        pid_t pid;
     
        // 获取key值
        if((key = ftok(".", 'z')) < 0)
        {
            perror("ftok error");
            exit(1);
        }
     
        // 创建信号量集,其中只有一个信号量(一把锁)
        if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
        {
            perror("semget error");
            exit(1);
        }
     
        // 初始化:初值设为0资源被占用,初始化0把钥匙
        init_sem(sem_id, 0);
     
        if((pid = fork()) == -1)
            perror("Fork Error");
        else if(pid == 0) /*子进程*/
        {
            sleep(2);
            printf("Process child: pid=%d\n", getpid());
            sem_v(sem_id);  /*释放资源*/
        }
        else  /*父进程*/
        {
        	// 这里直接p操作会阻塞,因为初始化的时候没有钥匙,子进程正常运行
            sem_p(sem_id);   /*等待资源*/           
            printf("Process father: pid=%d\n", getpid());
            sem_v(sem_id);   /*释放资源*/
            del_sem(sem_id); /*删除信号量集*/
        }
        return 0;
    }
    

    测试可以多次p 和 v操作,多次v操作锁箱里的钥匙会增加,但是无论如何如果没有钥匙的时候p操作会阻塞

  • 52
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值