Linux编程学习笔记:进程间通信(管道、信号、共享内存)

一、概述

    目的

    1、数据传输

    2、资源共享

    3、通知事件

    4、进程控制

    Linux进程间通信(IPC)由几步发展而来:UNIX进程间通信、基于System V进程间通信、POSIX进程间通信

    POSIX

        可移植操作系统接口,最初是为了提高UNIX环境下应用程序的可移植性,然而POSIX并不局限于LINUX,其他许多操作系统,例如DEC OpenVMS和Windows都支持POSIX标准

    System V

        也被称为AT&T System V,是Unix操作系统众多版本中的一支

    通信方式

        管道(pipe)和有名管道(FIFO)

        信号(signal)

        消息队列

        共享内存

        信号量

        套接字

二、管道通信

    特点:单向、先进先出,一个进程的输出与一个进程的输入连接在一起,一个进程(写进程)在管道尾部写入数据,另一个进程(读进程)在管道头部读出数据

    无名管道、有名管道:前者用于父进程和子进程间的通信,后者可以用于同一系统中任意两个进程间的通信

    无名管道

        创建:int pipe(int fledis[2]);

        当一个管道建立时,它会创建两个文件描述符:filedis[0]用于读管道。filedis[1]用于写管道

        关闭:使用close

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

int main()
{
	int pipe_fd[2];
	if(pipe(pipe_fd)<0)
	{
		printf("pipe create error\n");
		return -1;
	}
	else
		printf("pipe create success\n");
	close(pipe_fd[0]);
	close(pipe_fd[1]);
}

        运行结果

[gyy@localhost process_com]$ gcc pipe_creat.c -o pipe_creat

[gyy@localhost process_com]$ ./pipe_creat

pipe create success

    读写:管道用于不同进程间通信,通常会先创建一个管道,再通过fork函数创建一个子进程,该子进程会继承父进程所创建的管道(必须在调用fork之前调用pipe(),否则子进程将不会继承文件描述符)

#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

int main()
{
	int pipe_fd[2];
	pid_t pid;
	char buf_r[100];
	char *p_wbuf;
	int r_num;
	
	memset(buf_r,0,sizeof(buf_r));
	
	//创建管道
	if(pipe(pipe_fd)<0)
	{
		printf("pipe creat error!\n");
		return -1;
	}
	//创建子进程
	if((pid=fork())==0)
	{
		printf("\n");
		close(pipe_fd[1]);
		sleep(2);
		if((r_num=read(pipe_fd[0],buf_r,100))>0)
		{
			printf("%d numbers read from the pipe is: %s\n",r_num,buf_r);
		}	
		close(pipe_fd[0]);
		exit(0);
	}
	else if(pid>0)
	{
		close(pipe_fd[0]);
		if(write(pipe_fd[1],"Hello",5)!=-1)
			printf("parent write1 hello!\n");
		if(write(pipe_fd[1],"Pipe",5)!=-1)
			printf("parent write2 Pipe!\n");
		close(pipe_fd[1]);
		sleep(3);
		waitpid(pid,NULL,0);
		exit(0);	
	}
}

        运行结果:首先创建管道,然后创建子进程,子进程会继承父进程的管道,子进程首先关闭写等待2秒,然后读出管道中的信息并打印,父进程关闭读然后依次写入“Hello”“Pipe”,然后睡眠3秒

[gyy@localhost process_com]$ gcc pipe_rw.c -o pipe_rw

[gyy@localhost process_com]$ ./pipe_rw

parent write1 hello!

parent write2 Pipe!

10 numbers read from the pipe is: HelloPipe

 

    命名管道(FIFO)

        不相关的进程也能交换数据,实质上就是文件

        创建:int mkfifo(const char *pathname,mode_t mode)

            pathname:FIFO文件名

            mode:属性,见文件操作

        当打开FIFO时,非阻塞标志(O_NONBLOCK )将对以后产生如下影响

            1、没有使用O_NONBLOCK:访问要求无法满足时进程将阻塞

            2、使用O_NONBLOCK:访问要求无法满足是不阻塞,立刻出错返回,errno是ENXIO

        fifo_write.c

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FIFO_SERVER "/tmp/myfifo"

main(int argc,char **argv)
{
	int fd;
	char w_buf[100];
	int nwrite;
	//打开FIFO
	fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);
	if(argc==1)
	{
		printf("Please send something\n");
		exit(-1);
	}
	if(fd==-1)
		printf("OPEN ERROR\n");
	strcpy(w_buf,argv[1]);
	printf("%s\n",w_buf);
	if((nwrite=write(fd,w_buf,100))==-1)
	{
		if(errno==EAGAIN)
			printf("The FIFO has not been read yet.Please try later\n");
	}
	else
		printf("write %s to the FIFO\n",w_buf);
	
} 

        fifo_read.c

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#define FIFO "/tmp/myfifo"

main(int argc,char ** argv)
{
	char buf_r[100];
	int fd;
	int nread;
	//创建FIFO
	if(mkfifo(FIFO,O_CREAT|O_EXCL)<0&&(errno!=EEXIST))
		printf("cannot create fifoserver\n");
	printf("Preparing for reading bytes...\n");
	
	memset(buf_r,0,sizeof(buf_r));
	//打开FIFO
	fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);
	if(fd==-1)
	{
		perror("open");
	}
	while(1)
	{
		memset(buf_r,0,sizeof(buf_r));
		if((nread=read(fd,buf_r,100))==-1)
		{
			if(errno=read(fd,buf_r,100)==-1)
			{
				if(errno==EAGAIN)
					printf("no date yet\n");	
			}
		}
		printf("read %s from FIFO\n",buf_r);
		sleep(1);
	}
	//暂停等待信号
	pause();
	//删除文件
	unlink(FIFO);
}

        运行结果

[root@localhost process_com]# gcc fifo_write.c -o fifo_write

[root@localhost process_com]# gcc fifo_read.c -o fifo_read

fifo_read.c

[root@localhost process_com]# ./fifo_read

Preparing for reading bytes...

read  from FIFO

read  from FIFO

read  from FIFO

read  from FIFO

read  from FIFO

read  from FIFO

read FIFO_TEST from FIFO

read  from FIFO

read  from FIFO

read  from FIFO

read  from FIFO

read  from FIFO

read  from FIFO

^C

fifo_write.c

[root@localhost process_com]# ./fifo_write FIFO_TEST

FIFO_TEST

write FIFO_TEST to the FIFO

        注意要两个进程同时运行

三、信号

    信号(signal)机制是UNIX系统中最为古老的进程间通信机制

    硬件软件都能产生信号

    进程用kill函数将信号发送给另一个进程,用户可用kill命令将信号发送给其他进程

    几种常见的信号

        

    信号处理

    1、忽略信号

        大多数信号都按照这种方式处理,但是SIGKILL、SIGSTOP不能忽略

    2、执行用户希望的动作

        通知内核在某种信号发生时调用一个用户函数

    3、执行系统默认动作

       大多数信号默认动作是结束进程

    信号发送

        kill和raise

        kill既可以向自身发送信号,也可以向其他进程发送信号,raise函数是向进程自身发送信号

        int kill(pid_t pid,int signo)

        int raise(int signo)

        alarm函数用于指定一段时间后产生一个SIGALRM信号,如果不捕捉此信号,默认动作是终止该进程

        unsigned int alarm(usigned int seconds)

        每个进程只能有一个闹钟时间,如果在调用alarm时已设置过alarm,则时间被替代

        设置闹钟时间为0则取消以前的闹钟

    Pause

        pause函数使进程挂起直至捕获到一个信号
        int pause(void)

    信号的处理

        方法:使用简单的signal函数,使用信号集函数组

    信号响应函数signal

        void (*signal (int signo,viod (*func)(int)))(int)

        

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


void my_func(int sign_no)
{
	if(sign_no==SIGINT)
		printf("I have get SIGINT\n");
	else if(sign_no==SIGQUIT)
		printf("I have get SIGQUIT\n");
}

int main()
{
	printf("Waiting for signal SIGINT or SIGQUIT\n");
	//注册新号处理函数
	signal(SIGINT,my_func);
	signal(SIGQUIT,my_func);

	pause();
	exit(0);
}

    运行结果

[root@localhost process_com]# gcc mysignal.c -o mysignal
[root@localhost process_com]# ./mysignal 
Waiting for signal SIGINT or SIGQUIT
I have get SIGQUIT
[root@localhost process_com]# ps au | grep ./mysignal
root       7563  0.0  0.0   4212   356 pts/1    S+   17:08   0:00 ./mysignal
[root@localhost process_com]# kill -s SIGQUIT 7563
[root@localhost process_com]# ps au | grep ./mysignal
root       7566  0.0  0.0   4212   356 pts/1    S+   17:09   0:00 ./mysignal
[root@localhost process_com]# kill -s SIGINT 7566
[root@localhost process_com]# ./mysignal 
Waiting for signal SIGINT or SIGQUIT
I have get SIGINT

    代码分析:my_func为信号的处理函数,参数为某一个信号,可以根据信号的不同有不同的打印值,主程序为SIGINT和SIGQUIT注册,然后pause等待信号。这是打开另外一个中断,先查阅进程PID,然后用kill指令发送信号,然后程序进程就会捕获信号。

四、共享内存

    被多个进程共享的一部分物理内存,共享内存是进程间共享数据的一种最快的方式,一个进程向共享内存区域写了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容

    

    

    两个步骤:

    1、创建共享内存,使用shmget函数

    2、映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数

    创建

        int shmget(key_t key,int size,int shmflg)

        key标识共享内存的键值:O/IPC_PRIVATEO当key的取值为IPC_PRIVATE,则函数shmget()将创建一块新的共享内存;如果key的取值为0,而参数shmflg中又设置 IPC_PRIVATE这个标志,则同样会创建一块新的共享内存。

        返回值:如果成功,返回共享内存标识符;如果失败,返回-1。

    映射

        key标识共享内存的键值:O/IPC_PRIVATEO当key的取值为IPC_PRIVATE,则函数shmget()将创建一块新的共享内存;如果key的取值为0,而参数shmflg中又设置 IPC_PRIVATE这个标志,则同样会创建一块新的共享内存。返回值:如果成功,返回共享内存标识符;如果失败,返回-1。

        int shmat(int shmid,char *shmaddr,int flag)

        shmid:shmget函数返回的共享存储标识符

        flag:决定以什么方式来确定映射的地址(通常为0)

        shmaddr写零系统自动指定

        返回值:如果成功,则返回共享内存映射到进程中的地址,失败返回-1

    脱离

        int shmdt(char * shmaddr)

        不在需要共享内存使需要把它从进程地址空间中脱离

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

#define PERM S_IRUSR|S_IWUSR

int main(int argc,char **argv)
{
	int shmid;
	char *p_addr,*c_addr;
	if(argc!=2)
	{
		fprintf(stderr,"Usage:%s\n\a",argv[0]);
		exit(1);
	}
	if((shmid=shmget(IPC_PRIVATE,1024,PERM))==-1)
	{
		fprintf(stderr,"Create Share Memory Error: %s\n\a",strerror(errno));
		exit(1);
	}
	//创建子进程
	if(fork())//父进程写
	{
		p_addr=shmat(shmid,0,0);
		memset(p_addr,'\0',1024);
		strncpy(p_addr,argv[1],1024);
		wait(NULL);//释放资源,不关心终止情况
		exit(0);
	}
	else	//子进程读
	{
		sleep(1);
		c_addr=shmat(shmid,0,0);
		printf("Client get: %s\n",c_addr);
		exit(0);
	}
}

    运行结果

[root@localhost process_com]# gcc shmem.c -o shmem
[root@localhost process_com]# ./shmem SHMEM_Test
Client get: SHMEM_Test

    代码分析:创建共享内存,创建子进程后父进程在映射后给共享内存区域写‘\0’(结束符),然后把参数写入共享内存区域,子进程先休眠1秒,等待父进程写完毕,然后映射共享内存,读出其中的数据并打印。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值