嵌入式Linux _ Unix进程间的通信方式

一、进程间通信方式介绍(了解)

1、进程间通信介绍

  • 早期UNIX进程间通信方式;

        无名管道(pipe)

        有名管道(fifo)

        信号(signal)

       System  V  IPC(进程间通信缩写) ----- 三种:

             共享内存(share  memory)

             消息队列(message queue)

              信号灯集(semaphore set)

      套接字(socket)

二、无名管道

1、无名管道特点(理解)

  • 无名管道的数据是存放在内存中的,无名管道中的数据一旦读走,就不存在了;
  • 只能用于有亲缘关系(父子进程,兄弟进程,祖孙进程)的进程间;因为 无名管道创建好后虽然返回两个文件描述符,但其在文件系统中是不可见的,他仅在内存中存在,并且无名管道是某一个进程创建的,其他的进程想要使用该管道,只能通过继承的方式得到该管道进行使用;
  • 无名管道是单工的通信模式,具有固定的读端和写端;实现读写双向的话需要用两个无名管道;
  • 父进程创建无名管道,子进程继承无名管道;

                                      

注:无名管道首先在内核中被创建,管道的一端固定为写,一端固定为读;

                       

  • 使用两个无名通道 可以 实现 两个进程 间的双向通信;

2、无名管道创建(熟练)

  • 无名管道创建  —  pipe

        #include  <unistd.h>

        int  pipe(int pfd[2]);

       — 返回时返回0,失败时返回EOF;

       — pfd包含两个元素的整型数组,用来保存文件描述符;

       — 其中 pfd[0] 用于读管道;pfd[1]用于写管道;

示例:子进程1 和子进程2 分别往管道中写入字符串;父进程读管道内容并打印;

  • 一定要写创建无名管道,在创建子进程;因为子进程要继承父进程的无名管道;

                           

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string.h>

int main(int argc, const char *argv[])
{
	int pfd[2];
	char buf[32];
	pid_t pid1,pid2;
	
	if(pipe(pfd)<0)
	{
		perror("pipe");
		exit(-1);
	}
	if((pid1 = fork())<0)
	{
		perror("fork");
		exit(-1);
	}else if(pid1 == 0)//子进程1
	{
		strcpy(buf,"I'm process 1");
		write(pfd[1],buf,32);
		exit(0);
	}else   //父进程
	{
		if((pid2 = fork()) < 0)
		{
			perror("fork");
			exit(-1);
		}else if(pid2 == 0) //子进程2
		{
			sleep(1);
			strcpy(buf,"I'm process 2");
			write(pfd[1],buf,32);
		}else    //父进程 
		{
			wait(NULL);//回收子进程
			read(pfd[0],buf,32);//从管道读端读出 数据
			printf("%s \n",buf);//打印 读出来的数据
			wait(NULL);
			read(pfd[0],buf,32);
			printf("%s \n",buf);
		}
	}
	return 0;
}
  • 加一个睡眠,有意识的 让子进程1先去写管道,1秒后子进程2去写管道;
  • wait  等待回收子进程;
  • 无名管道与普通文件是不一样的;无名管道不会支持定位操作;

3、无名管道 读特性 (熟练)

  • 读无名管道
    1. 写端存在:创建无名管道后会返回两个文件描述符;一个读一个写;
      1. 当写端存在时,读管道会出现那些情况:
        1. 有数据:read返回实际读取的 字节数;
        2. 无数据:进程阻塞;(读阻塞)
      2. 当写端不存在时
        1. 有数据      read返回实际读取的字节数;
        2. 无数据      read返回0;

4、无名管道 写特性 (熟练)

  • 写无名管道
    1. 读端存在
      1. 当读端存在时,写管道会出现哪些情况:
        1. 有空间  write会返回实际写入的数据;
        2. 空间不足
          1. 空间还有部分,都不足与写一次:有多少空间先写多少空间,剩下的数据在管道空余后再写,此时进程会阻塞;
          2. 空间一点部分都没有:直接阻塞;

5、无名管道 ----  思考

  • 如何获取无名管道的大小?
    1. 循环写入管道,直到阻塞
    2. 统计循环次数;

       在Linux中,无名管道的缺省 大小为 64K Byte.

  • 无名管道读端不存在时,称为管道断裂。对于没有读端的无名管道,系统不允许其写入,因为该无名管道中的数据不会被读走;
  • 如何验证管道断裂(进程被信号结束)?

            子进程写管道

            父进程回收

小结:无名管道 与 有名管道的 特性都是一样的;

二、有名管道

无名管道的缺点:只能用于有亲属之间的进程。

                           使用时是单工的,只能实现一端读一端写的方式;

1、有名管道特点(了解)

  • 有名管道创建后在系统中会看到一个实际的文件(Linux系统七的文件之一(P)),有名称,有路径,对用一个实际的管道文件;无名管道创建后存在于内存中,没有实际的文件,没有实际的路径,只能通过继承的方式 得到;
  • 对应管道文件(p),可用于任意进程之间的通信;
  • 打开管道时可指定读写方式;(指定读方式,返回的文件描述符只能用于读操作,指定读写方式,返回的文件描述符,即可用于读,也可用于写)
  • 通过文件IO操纵,但是内容还是存放在内存中的;到管道(无名/有名)的读端与写端都关闭时,那在内存中的空间都会被自动释放;
  • 管道文件大小永远是0;
  • 当进程打开一个有名管道时,当管道只有读端或者写端时,进程会被阻塞,只有当读端与写端同时存在时,管道才是有意义的;
  • 同一个系统下,有两个三个执行文件,可同时操作一个管道文件;

2、有名管道创建(熟练)

  • 有名管道的创建  —  mkfifo

         #include  <unistd.h>

         #include  <fcntl.h>

            int mkfifo(const char *path);

         — 成功时返回0,失败时返回EOF;

         — path 创建的管道文件路径(相对路径,为空时默认 在当前位置 建立管道文件)

         — mode 管道文件的权限,如0666(只有读写的权限 八进制)

3、有名管道读写(熟练)

  • 管道如果存在的话,创建时会创建失败;
  • 当管道读端 、写端只有一项单独存在时,进程在读、写该管道时会被阻塞,只有两端同时存在时,才能一起操作;

4、有名管道读写 — 示例

进程A :循环从键盘输入并写入有名管道myfifo,输入quit时退出

进程B :循环统计进程A每次写入myfifo的字符串的长度;

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



int main(int argc, const char *argv[])
{
	if(mkfifo("myfifo",0666) < 0)
	{
		perror("mkfifo");
		exit(-1);
	}
	return 0;
}

进程A:

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

#define N 32
int main(int argc, const char *argv[])
{
	char buf[N];
	int pfd;

	if((pfd = open("myfifo",O_WRONLY))<0)
	{
		perror("open");
		exit(-1);
	}
	while(1)
	{
		fgets(buf,N,stdin);
		if(strcmp(buf,"quit\n") == 0)break;
		write(pfd,buf,N);
	}
	close(pfd);

	return 0;
}

进程B

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

#define N 32
int main(int argc, const char *argv[])
{
	char buf[N];
	int pfd;


	if((pfd = open("myfifo",O_RDONLY))<0)
	{
		perror("open");
		exit(-1);
	}
	while(read(pfd,buf,N) > 0)
	{
		printf("the length of string is %d \n",strlen(buf));
	}
	close(pfd);

	return 0;
}

三、信号机制

  • 信号是软件层次对中断机制的一种模拟,是一种异步通信方式。
  • 如果收到信号,程序会中止,处理收到的信号,处理完成后,继续执行后面的操作;
  • Linux内核通过信号通知用户进程,不同的信号代表不同的事件;
  • Linux对早期的Unix信号机制进行了扩展;前31种是早期Unix信号,没有使用排队机制,也称为不稳定机制;
  • 进程对信号有不同的响应方式;
    1. 缺省方式:对于每种信号都有默认的缺省的处理方法;
    2. 忽略信号:收到信号后不处理;
    3. 捕捉信号(注册信号):系统收到信号后,会执行事先封装好的程序;

1、常用信号

2、信号发送命令

  • 信号的相关命令  Kill/killall
  • kill [-signal] pid

 

       —  默认发送SIGTERM

       —  -sig可指定信号  ----  可以给所有的进程发信号

       —  pid 指定发送对象;

例子:

                   kill  -9 6437  向进程6437 发送类型为9的信号

                   kill  -9 -8126  向8126进程组发送类型为9的信号

                   kill  -9 -1     向除了系统中init与当前进程,所有的进程发送类型为9的信号;

  • killall [-u  user | prog]
    1. prog  指定进程名;
    2. user  指定用户名;
  • 如果不是超级用户,只能结束/停止自己创建的进程;

SIGHUP/SIGINT/SIGKILL/SIGSTOP

killall     :默认发送a.out  向系统所有运行a.out的进程发送信号;

killall    -u  linux : 向系统中linux用户创建的所有进程发送 15信号;

注: 当使用超级用户时,最好指定进程 发送信号,不然容易使得不该结束的进程提前结束,造         成更严重的问题;

3、信号发送

  • 信号发送 — kill / raise

         #include  <unistd.h>

         #include  <signal.h>

          int kill(pid_t pid,int  sig);

          int raise(int  sig);   —  向当前进程发送 信号量;

  —  成功时返回0,失败时返回EOF;

  —  pid  接收进程的进程号; 0 代表同组进程;-1代表所有进程;

  —  sig信号类型;

4、创建定时器

  • 信号相关函数  ---  alarm/pause

         int  alarm(unsigned  int  seconds);

     —  成功时返回上个定时器的剩余时间,失败时返回EOF;

     —  在Linunx中,一个程序中只有一个定时器;使用该函数后,该进程上一次的定时器失效了,重新赋值计算;

     —  seconds  定时器的时间;

     —  一个进程中只能设定一个定时器,时间到时产生SIGALRM

  • int  pause (void );   ------  让进程睡眠

     — 进程一直阻塞,直到被信号中断;

     — 被信号中断后返回 -1 ,errno 为EINTR;

示例:

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

int main()
{
    alarm(3);
    pause();
    printf(“I hava waken  up ! \n”);
    reurn 0;
}

$ ./a.out

Alarm clock

重要:alarm 经常用于实现超时检测;

5、设置信号响应方式  -  signal

  •        将信号与 信号处理函数关联起来;

       #include  <unistd.h>

       #include  <signal.h>

         void (*signal(int signo , void (*handler)(ing)))(int);  --- 返回值是一个函数指针;

    — 成功时返回原先的信号处理函数,失败时返回SIG_ERR;

    — signo 要设置的信号类型

    — handler指定的信号处理器函数:SIG_DFL代表缺省方式;

    — SIG_IGN代表 忽略信号;

    — 该函数将信号与信号处理函数相关联起来,进程接收到这个信号,然后去执行这个指定的信号处理函数;

6、信号捕捉

  • 如何去捕捉一个信号的示例:

         不同的信号可以指定相同的处理函数;

          在函数处理函数中,判断收到该信号是哪个信号;

void handler(int  signo)
{
    if(signo == SIGINT)
    {
        printf(“I have  got  SIGINT !\n”);
    }
    if(signo == SIGQUIT)
    {
         printf(“I have  got  SIGQUIT!\n”);
    }
}

int  main()
{
    signal(SIGINT,handler);
    signal(SIGQUIT,handler);
    while(1)pause;
    return 0;
}
  • signal 一旦设置了信号与信号处理函数的关联,将会持续有效;

7、小结

         kill/raise  :  任何进程发送信号/只能对当前进程发送信号;

         alarm    :设置定时器

         signal    : 设置信号的响应方式;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值