alin的学习之路(Linux系统编程:七)
1. 有名管道
命名管道(FIFO)不同于无名管道之处在于它提供了一个路径名与之关联,以 FIFO 的文件形式存在于文件系统中,这样,即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据。
命名管道(FIFO)和无名管道(pipe)有一些特点是相同的,不一样的地方在于:
-
FIFO 在文件系统中作为一个特殊的文件而存在,但 FIFO 中的内容却存放在内存中。
-
当使用 FIFO 的进程退出后,FIFO 文件将继续保存在文件系统中以便以后使用。
-
FIFO 有名字,不相关的进程可以通过打开命名管道进行通信。
1. 通过命令创建有名管道
mkfifo 管道文件名
itcast@ubuntu:~/classcode/7day$ mkfifo fifo
itcast@ubuntu:~/classcode/7day$ ll
total 8
drwxrwxr-x 2 itcast itcast 4096 Jul 21 03:23 ./
drwxrwxr-x 7 itcast itcast 4096 Jul 21 03:15 ../
prw-rw-r-- 1 itcast itcast 0 Jul 21 03:23 fifo|
2. 通过函数创建有名管道
判断文件是否存在
#include <unistd.h>
int access(const char *pathname, int mode);
功能:
判断文件是否存在
参数:
pathname 文件路径
mode 判断的属性
返回值:
成功 0
失败 -1 设置errno
mkfifo 创建有名管道
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
功能:
命名管道的创建。
参数:
pathname : 普通的路径名,也就是创建后 FIFO 的名字。
mode : 文件的权限,与打开普通文件的 open() 函数中的 mode 参数相同。(0666)
返回值:
成功:0 状态码
失败:如果文件已经存在,则会出错且返回 -1。
示例代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
//判断管道是否存在,不存在则创建
int ret = access("fifo",F_OK);
if(-1 == ret)
{
ret = mkfifo("fifo",0664);
if(-1 == ret)
{
perror("mkfifo");
return 1;
}
printf("有名管道创建成功\n");
}
return 0;
}
3. 设置非阻塞
//获取原来的flags
int flags = fcntl(fd[0], F_GETFL);
// 设置新的flags
flag |= O_NONBLOCK;
// flags = flags | O_NONBLOCK;
fcntl(fd[0], F_SETFL, flags);
如果设置成为非阻塞读,那么例如读管道,如果没有数据就会直接返回,不会阻塞等待
4. 有名管道的特性
一个进程以只读方式打开有名管道会阻塞,直到另一个进程以只写方式打开有名管道
一个进程以只写方式打开有名管道会阻塞,直到另一个进程以只读方式打开有名管道
一个进程以读写方式打开有名管道,不会阻塞
读管道:
-
管道中有数据,read返回实际读到的字节数。
-
管道中无数据:
- 管道写端被全部关闭,read返回0 (相当于读到文件结尾)
- 写端没有全部被关闭,read阻塞等待
写管道:
-
管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程终止)
-
管道读端没有全部关闭:
- 管道已满,write阻塞。
- 管道未满,write将数据写入,并返回实际写入的字节数。
5.使用fifo实现原始的聊天
talkA.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define SIZE 128
int main()
{
int fdr,fdw;
char buf[SIZE];
fdr = open("fifo1",O_RDONLY);
if(-1 == fdr)
{
perror("open");
return 1;
}
printf("以只读方式打开管道1\n");
fdw = open("fifo2",O_WRONLY);
if(-1 == fdw)
{
perror("write");
return 1;
}
printf("以只写方式打开管道2\n");
while(1)
{
memset(buf,0,SIZE);
int ret = read(fdr,buf,SIZE);
if(ret <= 0)
{
perror("read");
break;
}
printf("read : %s\n",buf);
memset(buf,0,SIZE);
fgets(buf,SIZE,stdin);
if(buf[strlen(buf)-1] == '\n')
buf[strlen(buf)-1] = '\0';
ret = write(fdw,buf,SIZE);
if(ret <= 0)
{
perror("write");
break;
}
printf("write: %s\n",buf);
}
close(fdr);
close(fdw);
return 0;
}
talkB.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define SIZE 128
int main()
{
int fdr,fdw;
char buf[SIZE];
fdw = open("fifo1",O_WRONLY);
if(-1 == fdw)
{
perror("write");
return 1;
}
printf("以只写方式打开管道1\n");
fdr = open("fifo2",O_RDONLY);
if(-1 == fdr)
{
perror("open");
return 1;
}
printf("以只读方式打开管道2\n");
while(1)
{
memset(buf,0,SIZE);
fgets(buf,SIZE,stdin);
if(buf[strlen(buf)-1] == '\n')
buf[strlen(buf)-1] = '\0';
int ret = write(fdw,buf,SIZE);
if(ret <= 0)
{
perror("write");
break;
}
printf("write: %s\n",buf);
memset(buf,0,SIZE);
ret = read(fdr,buf,SIZE);
if(ret <= 0)
{
perror("read");
break;
}
printf("read : %s\n",buf);
}
close(fdr);
close(fdw);
return 0;
}
缺点:只能一次一端发送一句话
改进:使用多进程,父进程和子进程分别控制读和写,公用一个终端,从而实现了读和写可以在一端多次进行
talkA.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define SIZE 128
int main()
{
pid_t pid;
char buf[SIZE];
int fdr,fdw;
int ret;
pid = fork();
if(-1 == pid)
{
perror("fork");
return 1;
}
else if(0 == pid)
{
//子进程读管道1
fdr = open("fifo1",O_RDONLY);
if(-1 == fdr)
{
perror("open");
exit(1);
}
while(1){
memset(buf,0,SIZE);
ret = read(fdr,buf,SIZE);
if(ret <= 0)
{
perror("read");
exit(1);
}
printf("read : %s\n",buf);
}
close(fdr);
}
else
{
//父进程写管道2
fdw = open("fifo2",O_WRONLY);
if(-1 == fdw)
{
perror("open");
exit(1);
}
while(1)
{
memset(buf,0,SIZE);
fgets(buf,SIZE,stdin);
if(buf[strlen(buf)-1 == '\n' ])
buf[strlen(buf)-1] = '\0';
ret = write(fdw,buf,SIZE);
if(-1 == ret)
{
perror("write");
exit(1);
}
}
close(fdw);
}
return 0;
}
talkB.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define SIZE 128
int main()
{
pid_t pid;
char buf[SIZE];
int fdr,fdw;
int ret;
pid = fork();
if(-1 == pid)
{
perror("fork");
return 1;
}
else if(0 == pid)
{
//子进程写管道1
fdw = open("fifo1",O_WRONLY);
if(-1 == fdw)
{
perror("open");
exit(1);
}
while(1)
{
memset(buf,0,SIZE);
fgets(buf,SIZE,stdin);
if(buf[strlen(buf)-1 == '\n' ])
buf[strlen(buf)-1] = '\0';
ret = write(fdw,buf,SIZE);
if(-1 == ret)
{
perror("write");
exit(1);
}
}
close(fdw);
}
else
{
//父进程读管道2
fdr = open("fifo2",O_RDONLY);
if(-1 == fdr)
{
perror("open");
exit(1);
}
while(1)
{
memset(buf,0,SIZE);
ret = read(fdr,buf,SIZE);
if(ret <= 0)
{
perror("read");
exit(1);
}
printf("read : %s\n",buf);
}
close(fdr);
}
return 0;
}
2. 共享存储映射
1. 存储映射函数
-
mmap函数
#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 功能: 一个文件或者其它对象映射进内存 参数: addr : 指定映射的起始地址, 通常设为NULL, 由系统指定 length:映射到内存的文件长度 prot: 映射区的保护方式, 最常用的 : a) 读:PROT_READ b) 写:PROT_WRITE c) 读写:PROT_READ | PROT_WRITE flags: 映射区的特性, 可以是 a) MAP_SHARED : 写入映射区的数据会复制回文件, 且允许其他映射该文件的进程共享。 b) MAP_PRIVATE : 对映射区的写入操作会产生一个映射区的复制(copy - on - write), 对此区域所做的修改不会写回原文件。 fd:由open返回的文件描述符, 代表要映射的文件。 offset:以文件开始处的偏移量, 必须是4k的整数倍, 通常为0, 表示从文件头开始映射 返回值: 成功:返回创建的映射区首地址 失败:MAP_FAILED宏
关于mmap函数的使用总结:
-
第一个参数写成NULL
-
第二个参数要映射的文件大小 > 0
-
第三个参数:PROT_READ 、PROT_WRITE
-
第四个参数:MAP_SHARED 或者 MAP_PRIVATE
-
第五个参数:打开的文件对应的文件描述符
-
第六个参数:4k的整数倍,通常为0
-
-
munmap函数
#include <sys/mman.h> int munmap(void *addr, size_t length); 功能: 释放内存映射区 参数: addr:使用mmap函数创建的映射区的首地址 length:映射区的大小 返回值: 成功:0 失败:-1
2. 注意事项
-
创建映射区的过程中,隐含着一次对映射文件的读操作。
-
当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护)。而MAP_PRIVATE则无所谓,因为mmap中的权限是对内存的限制。
-
映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭。
-
特别注意,当映射文件大小为0时,不能创建映射区。所以,用于映射的文件必须要有实际大小。mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。
-
munmap传入的地址一定是mmap的返回地址。坚决杜绝指针++操作。
-
如果文件偏移量必须为4K的整数倍。
-
mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。
3. 通过存储映射进行进程通信
read.c
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#define SIZE 128
int main()
{
int fd;
char buf[SIZE];
void *addr = NULL;
fd = open("txt",O_RDWR);
if(-1 == fd)
{
perror("open");
return 1;
}
addr = mmap(addr,1024,PROT_READ,MAP_SHARED,fd,0);
if(MAP_FAILED == addr)
{
perror("mmap");
return 1;
}
close(fd);
while(1)
{
memset(buf,0,SIZE);
memcpy(buf,addr,SIZE);
printf("read:%s\n",buf);
sleep(1);
}
munmap(addr,1024);
return 0;
}
write.c
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#define SIZE 128
int main()
{
int fd;
char buf[SIZE];
int i = 0;
void *addr = NULL;
fd = open("txt",O_RDWR);
if(-1 == fd)
{
perror("open");
return 1;
}
addr = mmap(addr,1024,PROT_WRITE,MAP_SHARED,fd,0);
if(MAP_FAILED == addr)
{
perror("mmap");
return 1;
}
close(fd);
while(1)
{
memset(buf,0,SIZE);
sprintf(buf,"hello itcast %d",i++);
memcpy(addr,buf,SIZE);
printf("write %d\n",i);
sleep(1);
}
munmap(addr,1024);
return 0;
}
4. 共享存储映射与多进程
父进程读,子进程写,注意addr是void*,使用需要做强制类型转换
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int fd;
void* addr = NULL;
fd = open("txt",O_RDWR);
if(-1 == fd)
{
perror("open");
return 1;
}
addr = mmap(addr,1024,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(MAP_FAILED == addr)
{
perror("mmap");
return 1;
}
close(fd);
pid = fork();
if(-1 == pid)
{
perror("fork");
return 1;
}
else if(0 == pid)
{
//子进程写
memcpy(addr,"hello itcast",sizeof("hello itcast")+1);
munmap(addr,1024);
}
else
{
wait(NULL);
printf("addr = %s\n",(char*) addr);
}
return 0;
}
3. 一些小的point
1. fpathconf 用于输出文件配置信息
可使用fpathconf来获取管道的大小
#include <stdio.h>
#include <unistd.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if(-1 == ret)
{
perror("pipi");
return 1;
}
printf("fd[0] = %d,fd[1] = %d, pipeSize = %ld\n",fd[0],fd[1],fpathconf(fd[0],_PC_PIPE_BUF));
return 0;
}
2. ulimit -a
ulimit -a 可以获取一些系统设定内存的大小
itcast@ubuntu:~/classcode/7day$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 3472
max locked memory (kbytes, -l) 65536
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 3472
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
3. 内存四区模型
以32位为例,注意:
- 堆的大小为8M
- const int a = 10;放在栈区