目录
进程不共享内存
匿名管道
通过匿名管道实现通讯
有名管道
库函数mkfifo()
案例
进程不共享内存
不同进程之间内存是不共享的。是相互独立的。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int num = 0;
int main(int argc, char const *argv[])
{
__pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
num = 100;
printf("子进程中,num的值:%d\n", num);
}
else {
sleep(1);
printf("父进程中,num的值:%d\n", num);
}
}
匿名管道
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc, char const *argv[])
{
fopen("/opt","a+");
printf("出问题%d\n", errno);
perror("fopen");
return 0;
}
当系统调用或库函数发生错误时,通常会通过设置全局变量errno来指示错误的具体原因。errno是在C语言(及其在Unix、Linux系统下的应用)中用来存储错误号的一个全局变量。每当系统调用或某些库函数遇到错误,无法正常完成操作时,它会将一个错误代码存储到errno中。这个错误代码提供了失败的具体原因,程序可以通过检查errno的值来确定发生了什么错误,并据此进行相应的错误处理。
errno定义在头文件<errno.h>中,引入该文件即可调用全局变量errno。
perror函数用于将errno当前值对应的错误描述以人类可读的形式输出到标准错误输出(stderr)。
参数s:指向一个字符串的指针,如果s不是空指针且指向的不是\0字符,则perror会在s后添加一个冒号和空格作为前缀,输出错误信息,否则不输出前缀,直接输出错误信息。
通过匿名管道实现通讯
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
// 管道实现进程间通讯
int main(int argc, char const *argv[])
{
// 将程序传进来的第一个命令行参数,通过管道传输给子进程
int pipefd[2];
__pid_t cpid;
if (pipe(pipefd) == -1) {
perror("创建管道失败");
exit(EXIT_FAILURE);
}
if (argc != 2) {
printf("参数太少了\n");
exit(EXIT_FAILURE);
}
cpid = fork();
if (cpid == -1) {
perror("创建进程失败");
exit(EXIT_FAILURE);
}
if (cpid == 0) {
// 子进程,读取数据
close(pipefd[1]); // 关闭写端
// printf("子进程读取的数据:\n");
char* str = "子进程读取的数据:";
write(STDOUT_FILENO, str, strlen(str));
char buf;
while (read(pipefd[0], &buf, 1) > 0) {
write(STDOUT_FILENO, &buf, 1);
}
write(STDOUT_FILENO, "\n", 1);
close(pipefd[0]); // 关闭读端
exit(EXIT_SUCCESS);
}
else {
// 父进程,写入数据
close(pipefd[0]); // 关闭读端
printf("进程%d写入数据:%s\n", getpid(),argv[1]);
write(pipefd[1], argv[1], strlen(argv[1]));
close(pipefd[1]); // 关闭写端
waitpid(cpid, NULL, 0);
exit(EXIT_SUCCESS);
}
return 0;
}
有名管道
上文介绍的Pipe是匿名管道,只能在有父子关系的进程间使用,某些场景下并不能满足需求。与匿名管道相对的是有名管道,在Linux中称为FIFO,即First In First Out,先进先出队列。
FIFO和Pipe一样,提供了双向进程间通信渠道。但要注意的是,无论是有名管道还是匿名管道,同一条管道只应用于单向通信,否则可能出现通信混乱(进程读到自己发的数据)。
有名管道可以用于任何进程之间的通信。
库函数mkfifo()
执行man 3 mkfifo查看文件说明。
#include <sys/types.h>
#include <sys/stat.h>
/**
* @brief 用于创建有名管道。该函数可以创建一个路径为pathname的FIFO专用文件,mode指定了FIFO的权限,FIFO的权限和它绑定的文件是一致的。FIFO和pipe唯一的区别在于创建方式的差异。一旦创建了FIFO专用文件,任何进程都可以像操作文件一样打开FIFO,执行读写操作。
*
* @param pathname 有名管道绑定的文件路径
* @param mode 有名管道绑定文件的权限
* @return int
*/
int mkfifo(const char *pathname, mode_t mode);
案例
发送端:
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, char const *argv[])
{
// 每次用完最好要释放掉
char* pipe_path = "/tmp/mtfifo";
int fifo = mkfifo(pipe_path, 0666);
if (fifo == -1) {
perror("mkfifo");
if (errno != 17) {
exit(EXIT_FAILURE);
}
}
// 对有名管道创建一个文件描述符fd
int fd = open(pipe_path, O_WRONLY); //只能写
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
char buf[100];
ssize_t readnum;
// 读取到控制台的数据写入管道中
while ((readnum = read(STDIN_FILENO, buf, sizeof(buf))) > 0)
{
write(fd, buf, readnum);
}
if (readnum < 0) {
perror("read");
close(fd);
exit(EXIT_FAILURE);
}
printf("写入完成\n");
close(fd);
// 释放管道
if (unlink(pipe_path) == -1) {
perror("unlink");
}
return 0;
}
接收端:
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, char const *argv[])
{
char* pipe_path = "/tmp/mtfifo";
// if (unlink(pipe_path) == -1) {
// perror("unlink");
// // 如果删除失败,可能是因为管道不存在,所以忽略错误
// }
int fifo = mkfifo(pipe_path, 0666);
if (fifo == -1) {
perror("mkfifo");
if (errno != 17) {
exit(EXIT_FAILURE);
}
}
// 对有名管道创建一个文件描述符fd
int fd = open(pipe_path, O_RDONLY); //只能读
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
char buf[100];
ssize_t readnum;
// 读取管道信息,放到控制台
while ((readnum=read(fd,buf,sizeof(buf))) > 0)
{
write(STDIN_FILENO, buf, readnum);
}
if (readnum < 0) {
perror("read");
close(fd);
exit(EXIT_FAILURE);
}
printf("读端接收管道数据完成\n");
close(fd);
return 0;
}
注意
调用open()打开有名管道时,flags设置为O_WRONLY则当前进程用于向有名管道写入数据,设置为O_RDONLY则当前进程用于从有名管道读取数据。设置为O_RDWR从技术上是可行的,但正如上文提到的,此时管道既读又写很可能导致一个进程读取到自己发送的数据,通信出现混乱。因此,打开有名管道时,flags只应为O_WRONLY或O_RDONLY。
内核为每个被进程打开的FIFO专用文件维护一个管道对象。当进程通过FIFO交换数据时,内核会在内部传递所有数据,不会将其写入文件系统。因此,/tmp/myfifo文件大小始终为0。