目录
任务间的通信与同步(7种)
管道 信号 信号量 互斥锁 消息队列 共享内存 socket套接字
1. 认识无名管道
管道相关的关键概念
管道是Linux支持的最初Unix IPC形式之一,具有以下特点:
(1)管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
(2)只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
(3)单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。
(4)数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
管道的创建:
#include <unistd.h>
int pipe(int fd[2])
//Return 0 on success,or -1 on error
该函数创建的管道的两端处于一个进程中间,在实际应用中没有太大意义,因此,一个进程在由pipe()创建管道后,一般再fork一个子进程,然后通过管道实现父子进程间的通信(因此也不难推出,只要两个进程中存在亲缘关系,这里的亲缘关系指的是具有共同的祖先,都可以采用管道方式来进行通信)。
管道的读写规则:
管道两端可分别用描述字 fd[0] 以及 fd[1] 来描述,需要注意的是,管道的两端是固定了任务的。即一端只能用于读,由描述字 fd[0] 表示,称其为管道读端;另一端则只能用于写,由描述字fd[1] 来表示,称其为管道写端。如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生。 一般文件的I/O函数都可以用于管道,如close、read、write等等。
从管道中读取数据:
(1)如果管道的写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0;
(2)当管道的写端存在时,如果请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数,如果请求的字节数目不大于PIPE_BUF,则返回管道中现有数据字节数(此时,管道中数据量小于请求的数据量);或者返回请求的字节数(此时,管道中数据量不小于请求的数据量)。
注:(PIPE_BUF在 include/linux/limits.h 中定义,不同的内核版本可能会有所不同。Posix.1要求PIPE_BUF至少为 512字节,red hat 7.2中为4096)。
向管道中写入数据:
(1)向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。
(2)管道中的读端关闭,此时向管道写入数据的进程将收到内核传来的SIFPIPE信号,应用程序可以处理该信号,也可以忽略(默认动作则是应用程序终止)。
当管道中父进程没有写入数据时,而子进程使用read读管道中的数据时,read将一直阻塞,等待管道中写入数据
NAME : pipe , pipe2 ----- creat pipe
pip1.c
/*
parent process : write pipe
child process : read pipe
*/
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main(void)
{
int fd[2];
int pid;
if(pipe(fd) == -1) //创建管道,创建错误返回-1,正确返回0
perror("pipe");
pid = fork(); // 创建进程
if(pid > 0) // 父进程 fd[0] 读 fd[1] 写
{
close(fd[0]); //关闭管道读端
sleep(5);
write(fd[1], "ab", 2); //写管道写数据(2为写入字符为两个字节大小)
while(1);
}
else if(pid == 0) // 子进程复制了管道
{
char ch[2]; // 用于接收数据
printf("child process is waiting for data: \n");
close(fd[1]); // 关闭管道写端
read(fd[0], ch, 2); // 存放到ch,2个字节大小
printf("read from pipe: %s\n", ch);
}
return 0;
}
读函数read
ssize_t read ( int fd, void * buf, size_t nbyte)
read函数是负责从fd中读取内容. 成功时,read返回实际所读的字节数,如果返回的值是0,表示已经读到文件的结束了.
小于0表示出现了错误. 如果错误为EINTR说明读是由中断引起的, 如果是ECONNREST表示网络连接出了问题.
写函数write
ssize_t write ( int fd, const void * buf, size_t nbytes)
write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数.失败时返回-1. 并设置errno变量. 在网络程序中,当我们向套接字文件描述符写时有俩种可能.
1)write的返回值大于0,表示写了部分或者是全部的数据.
2)返回的值小于0,此时出现了错误.我们要根据错误类型来处理. 如果错误为EINTR表示在写的时候出现了中断错误.
如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接).
2. 无名管道大小测试
管道的容量是有限的:管道的存储能力为65536字节
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
int fd[2];
if(pipe(fd) == -1)
perror("pipe");
pid = fork(); // 创建子进程
if(pid == 0) // child process : write to the pipe
{ // 子进程不断写入管道*
char ch = '*';
int n = 0;
close(fd[0]); // 关闭读端
while(1)
{ // 地址
write(fd[1], &ch, 1);
printf("count = %d\n", ++n);
}
}
else if(pid > 0) //parent process : wait until child process is over
{
waitpid(pid, NULL, 0); // 父进程等待子进程结束
}
}
结果:。。。count = 65536 (字节)
3. 无名管道练习
子进程把键盘输入的数据写入管道,父进程读取数据
pip_w&r_.c
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
int fd[2];
if(pipe(fd) == -1)
perror("pipe");
pid = fork(); //创建子进程
if(pid == 0) //child process : write to the pipe
{
char tmp[100]; // 用于存储数据
close(fd[0]);
while(1)
{
printf("parent processs is waiting for you message\n");
scanf("%s", tmp) // 等待键盘输入数据
write(fd[1], tmp, sizeof(tmp)); // 将数据写入管道
}
}
else if(pid > 0) //parent process : 读取子进程的输入
{
char tmp[100];
close(fd[1]);
while(1) // 只要管道有内容就会读取
{
read(fd[0], tmp, sizeof(tmp)); //读取
printf("read from pipe : %s\n", tmp); // 输出
}
}
}
4. 无名管道_两个管道双向传输
two_pip.c
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/wait.h>
#include <string.h>
#include <ctype.h>
int main(void)
{
pid_t pid;
int fd[2];
int fd2[2]; // 两个描述符
// 判断是否成功
if(pipe(fd) == -1)
perror("pipe");
if(pipe(fd2) == -1)
perror("pipe");
pid = fork(); //创建子进程
if(pid == 0) //child process :
{
char tmp[100];
close(fd[1]); //关闭fd管道的写
close(fd2[0]); //关闭fd管道的读
while(1)
{
memset(tmp, '\0', sizeof(tmp)); // 对tmp做清空处理
read(fd[0], tmp, sizeof(tmp)); // 子进程等待去读管道
for( i = 0; i < sizeof(tmp); i++) // 有数据时,read由阻塞变为非阻塞
tmp[i] = toupper(tmp[i]); // 将所有的字符变为大写toupper
write(fd2[1], tmp, sizeof(tmp)); // 写入管道fd2
}
}
else if(pid > 0) //parent process : 读取子进程的输入
{
char tmp[100];
close(fd[0]);
close(fd[1]);
while(1)
{
memset(tmp, '\0', sizeof(tmp));
gets(tmp); //父进程由键盘输出
write(fd[1], tmp, sizeof(tmp)); //向子进程写入小写
memset(tmp, '\0', sizeof(tmp));
read(fd2[0], tmp, sizeof(tmp)); //读取子进程传过来的大写
printf("after change : %s \n", tmp)
}
}
}
memset 函数
代码示例
#include <stdio.h>
#include <string.h>
int main ()
{
char str[50];
strcpy(str,"This is string.h library function");
puts(str);
memset(str,'$',7);
puts(str);
return(0);
}
// This is string.h library function
// $$$$$$$ string.h library function
5. 有名(命名)管道
- 匿名管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道(FIFO),也叫命名管道、FIFO 文件。
- 有名管道(FIFO)不同于匿名管道之处在于它提供了一个路径名与之关联,以 FIFO 的文件形式存在于文件系统中,并且其打开方式与打开一个普通文件是一样的,这样即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据。
- 一旦打开了 FIFO,就能在它上面使用与操作匿名管道和其他文件的系统调用一样的 I/O 系统调用了(如read()、write() 和 close())。与管道一样,FIFO 也有一个写入端和读取端,并且从管道中读取数据的顺序与写入的顺序是一样的。FIFO 的名称也由此而来:先入先出。
- 有名管道(FIFO) 和匿名管道(pipe)有一些特点是相同的,不一样的地方在于:
1. FIFO 在文件系统中作为一个特殊文件存在,但 FIFO 中的内容却存放在内存中。
2. 当使用 FIFO 的进程退出后,FIFO 文件将继续保存在文件系统中以便以后使用。
3. FIFO 有名字,不相关的进程可以通过打开有名管道进行通信。
非亲缘关系进程之间进行管道通信
NAME : mkfifo , mkfifoat - make a FIFO(先入先出) special file(a named pipe)
模式(model):写权限4,读权限2,可执行权限1 (主要读写)
函数功能:创建一个FIFO文件,用于进程之间的通信。pathname就是路径名,mode是该文件的权限。返回值表示是否成功,返回0表示成功,返回-1表示失败,失败原因在errno中。(建立FIFO的时候,要求不存在这样的FIFO)。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
// 函数功能:创建一个FIFO文件,用于进程之间的通信。pathname就是路径名,mode是该文件的权限。返回值表示是否成功,返回0表示成功,返回-1表示失败,失败原因在errno中。(建立FIFO的时候,要求不存在这样的FIFO)。
read_named_pipe.c
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(void)
{
int ret;
int fd;
char buf[100];
ret = mkfifo("my_fifo", 666); // 创建文件,权限
if(ret != 0)
perror("mkfifo");
printf("prepare reading from the pip :\n");
fd = open("my_fifo", 0_RDWR);
if(fd == -1)
perror("open");
while(1)
{
memset(buf, '\0', sizeof(buf));
read(fd, buf, sizeof(buf));
printf("read from named pipe : %s\n", buf);
sleep(1);
}
return 0;
}
编译 :gcc read_named_pipe.c -o read
重新开一个终端:
write_named_pipe.c
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int fd;
char buf[100];
fd = open("my_fifo", 0_WRONLY);
if(fd == -1)
perror("open");
if(argc == 1)
{
printf("please send something to the named pipe :\n");
exit(EXIT_FAILURE);
}
strcpy(buf, argv[1]);
write(fd, buf, sizeof(buf));
printf("write to the pipe : %s\n", buf);
return 0;
}
编译:gcc write_named_pipe.c -o write
./write hello morning
总结
有名管道 (可以在任意两个进程之间通信)
无名管道(只能在父子进程之间进行通信)(有一个读端和一个写端,缺一不可)
管道写端关闭,则读端返回值为0;
管道读端关闭,则写端会产生异常(会收到信号SIGPIPE)
管道为空, 那么读会阻塞
管道写满, 那么写会阻塞
1.有名管道和无名管道的区别:有名管道可以在任意两个进程之间通信;无名管道只能在父子进程之间进行通信
2.写入管道的数据在哪里? 在内存中(不是在磁盘上)
3.管道的通信方式: 半双工(数据可以从a到b,也可以从b到a,但是某一时刻只能选择其中一个)