管道和共享内存

一,无名管道

特点:

  • 其本质是一个伪文件(实为内核缓冲区)
  • 由两个文件描述符引用,一个表示读端(fd[0]),一个表示写端(fd[1])。
  • 规定数据从管道的写端流入管道,从读端流出。

原理:

  • 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现

局限性:

  • ① 数据自己读不能自己写。
  • ② 数据一旦被读走,便不在管道中存在,不可反复读取。
  • ③ 由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。
  • ④ 只能在有公共祖先的进程间使用管道。

pipe函数(在fork函数之前调用)

#include <unistd.h>

int pipe(int pipefd[2]);

参数:pipefd[2]传出参数

返回值:成功:0;失败:-1,设置errno。会返回两个文件描述符

规定:fd[0] → r; fd[1] → w

必须在fork之前调用

 

无名管道阻塞问题(默认情况是阻塞模式)

①读管道:

1,管道里有数据,则read返回读取实际数据

2,管道里没有数据:

        (1)写端关闭,则read返回0,

       (2)待写端没有关闭,则read阻塞等。(最开始的情况)

②写管道:

1,管道读端全部关闭,产生SIGPIPE信号,默认是终止进程

2,管道读端没有全部关闭

     (1)管道没有空间可写,则阻塞等待,直到有空间为止

     (2)管道有空间可写,则write写入,并返回实际个数

设为非阻塞后,之前阻塞的都不会阻塞而是返回一个EAGAIN错误

管道缓冲区大小

可以使用ulimit –a 命令来查看当前系统中创建管道文件所对应的内核缓冲区大小

pipe size            (512 bytes, -p) 8 4kb大小

 

管道的优劣

优点:简单,相比信号,套接字实现进程间通信,简单很多。

缺点:1. 只能单向通信,双向通信需建立两个管道。

           2. 只能用于父子、兄弟进程(有共同祖先)间通信。

二,有名管道(FIFO)

FIFO常被称为命名管道,以区分管道(pipe)。管道(pipe)只能用于“有血缘关系”的进程间。但通过FIFO,不相关的进程也能交换数据。

FIFO是Linux基础文件类型中的一种。但,FIFO文件在磁盘上没有数据块,仅仅用来标识内核中一条通道。各进程可以打开这个文件进行read/write,实际上是在读写内核通道,这样就实现了进程间通信。

创建方式:

1,命令:mkfifo 管道名

2,mkfifo函数

#include <sys/types.h>

#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

成功:0; 失败:-1

一旦使用mkfifo创建了一个FIFO,就可以使用open打开它,常见的文件I/O函数都可用于fifo。如:close、read、write、unlink等。

mode:类似于open的第二个参数

mkfifo隐含指定了(O_CREAT|O_EXCL)也就是说,它要么创建一个新的fifo,要么返回一个EEXIST错误(如果所指定名字的FIFO已经存在)。要打开一个已存在的FIFO或创建一个新的FIFO,应该调用mkfifo,再检查它是否返回EEXIST错误,若返回错误就调用open。

 

三,存储映射I/O 

存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。不需要用read和write,而是指针的形式操作缓冲区。这个缓冲区进程地址空间的内核区域。

void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);

返回:成功:返回创建的映射区首地址;失败:MAP_FAILED宏

参数:

addr: 建立映射区的首地址,由Linux内核指定。使用时,直接传递NULL

length: 欲创建映射区的大小

prot: 映射区权限PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE

flags: 标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区)

            MAP_SHARED:  会将映射区所做的操作反映到物理设备(磁盘)上。

            MAP_PRIVATE: 映射区所做的修改不会反映到物理设备。

fd: 用来建立映射区的文件描述符

offset: 映射文件的偏移(4k的整数倍)

 

 

三个作用:

①使用普通文件以提供内存映射I/O

②使用特殊文件以提供匿名映射

③使用shm_open以提供无亲缘关系进程间的Posix共享内存。

 

munmap函数

#include <sys/mman.h>

int munmap(void *addr, size_t length);

成功:0; 失败:-1

功能:从某个进程的地址空间删除一个映射关系。

 

msync函数

#include <sys/mman.h>

int msync(void *addr, size_t length, int flags);

addr:映射区的起始地址

length:长度

flags:

MS_ASYNC:执行异步写

MS_SYNC:执行同步写

MS_INVALIDATE:使高速缓存的数据失效

成功0,失败-1.

功能:确信文件内容和映射区数据同步

 

 

ftruncate函数

#include <unistd.h>

#include <sys/types.h>

int ftruncate(int fd, off_t length);

返回值,成功返回0,失败返回-1

功能:设置文件或者ipc对象大小

非匿名映射一定要通过文件或者对象名指定大小。

 

unlink函数

#include <unistd.h>

int unlink(const char *path);

功能:具备删除文件的条件,进程结束后就会删除文件。

如果该文件名为最后连接点,但有其他进程打开了此文件,则在所有关于此文件的文件描述词皆关闭后才会删除。如果有多个硬连接 则数目减1。如果参数pathname为一符号连接,则此连接会被删除。

 

注意事项:

1,可以用int ftruncate(int fd, off_t length);来指定文件大小,也就是映射区大小

2,创建映射区的过程中,隐含着一次对映射文件的读操作

3,当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护)。而MAP_PRIVATE则无所谓,因为mmap中的权限是对内存的限制。

4,映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭(无论有没有血缘关系)。

5, 特别注意,当映射文件大小为0时,不能创建映射区。所以:用于映射的文件必须要有实际大小!! mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。

6,munmap传入的地址一定是mmap的返回地址。坚决杜绝指针++操作

7, 如果文件偏移量必须为4K的整数倍

8,mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作

 

mmap父子进程通信

通过内存映射文件。open打开文件,mmap映射的方式。

flag的含义有点变化

MAP_PRIVATE:  (私有映射)  父子进程各自独占映射区;

MAP_SHARED:  (共享映射)  父子进程共享映射区;

 

结论:父子进程共享:1. 打开的文件  2. mmap建立的映射区(但必须要使用MAP_SHARED)

 

匿名映射

无需用文件来完成映射区建立。

1,4.4BSD

flag要使用使用MAP_ANONYMOUS (或MAP_ANON)

int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);

大小可以任意指定,文件描述符用-1代替

2,SVR4

① fd = open("/dev/zero", O_RDWR);

② p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);

 

匿名映射主要用于父子间的进程通信。不需要用文件来完成映射区的建立

 

四,posix共享内存

posix有两种在无亲缘关系进程间使用共享内存的方法

1,内存映射文件

2,共享内存区对象

 

共享内存区对象两个步骤:

1,指定一个名字参数调用shm_open,以创建一个新的共享内存区对象或打开一个已存在的共享内存区对象

2,调用mmap把这个共享内存区映射到调用进程的地址空间

 

shm_open函数

#include <sys/mman.h>

#include <sys/stat.h> /* For mode constants */

#include <fcntl.h> /* For O_* constants */

int shm_open(const char *name, int oflag, mode_t mode);

name:对象名,创建成功后会在/dev/shm在生成

oflag:和open函数一样

mode:和open函数一样,不需要时给0

返回值:成功返回一个非负描述符,出错则为-1

功能:创建或打开一个共享内存对象

 

shm_unlink函数

#include <sys/mman.h>

#include <sys/stat.h> /* For mode constants */

#include <fcntl.h> /* For O_* constants */

int shm_unlink(const char *name);

name:共享内存对象名

返回值:成功0,失败-1

功能:和unlink类似,可以删除共享内存区

使用这几个函数时链接时需要 -lrt

 

五,System V共享内存

对于每个System  V共享内存区,内核会维护一个结构体

struct shmid_ds {

struct ipc_perm shm_perm; /* Ownership and permissions */

size_t shm_segsz; /* Size of segment (bytes) */

time_t shm_atime; /* Last attach time */

time_t shm_dtime; /* Last detach time */

time_t shm_ctime; /* Last change time */

pid_t shm_cpid; /* PID of creator */

pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */

shmatt_t shm_nattch; /* No. of current attaches */

...

};

1,ftok函数

#include <sys/types.h>

#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);

pathname:路径名

proj_id:为0整不数

返回值:成功返回IPC键,失败返回-1

2,shmget函数

#include <sys/ipc.h>

#include <sys/shm.h>

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

key:可以是ftok返回值,也可以指定IPC_PRIVATE

size:创建新的共享内存时,必须指定不为0的size,如果是访问就为0

shmflg:和open函数有点不同以IPC_xxx.逻辑差不多,还包括权限值

IPC_xxx.:只限于IPC_CREAT,IPC_EXCL.权限:SHM_W,SHM_R等

注意不存在PIC_RDONLY,PIC_WRONLY,

返回值:成功返回一个标识符,失败-1

功能:创建或访问一个共享内存区

3,shmat函数

#include <sys/types.h>

#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);

shmid:共享内存标示符

shmaddr:传NULL,由系统替调用者选择地址

shmflg:一般为0,SHM_RDONLY只读,SHM_WRONLY只写

返回值:成功为映射区的起始地址,失败-1

功能:附接到调用进程的地址空间。(和mmap功能3相似)

默认情况下(0)只要调用进程具有某个共享内存的读写权限,它附接该内存区后就能同时读写该内存区。,shmflg也可以指定SHM_RDONLY。(亲测SHM_R不会报错)

4,shmdt函数

#include <sys/types.h>

#include <sys/shm.h>

int shmdt(const void *shmaddr);

shmaddr:地址

返回值:成功 返回0 失败 -1

功能:进程使用完某个共享内存时,用shmdt函数断接这个内存区。但不是删除

其实当一个进程终止时,它当前附接的所有共享内存都自动断接掉。

5,shmctl函数

#include <sys/ipc.h>

#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

shmid:共享内存区描述符

cmd:

IPC_STAT:向调用者返回所指定共享内存区当前的shmid_ds结构

IPC_SET :设置struct shmid_ds中的ipc_perm.uid,ipc_perm.gid,

ipc_perm.mode

IPC_RMID :从系统中删除由shmid标识的共享内存区并拆除它

buf:传出参数。

功能:对一个共享内存区的多种操作

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
31. 在Linux系统下,发送信号的命令是kill。在C语言中,可以使用kill函数来发送信号。 32. pid_t fork() 的返回值为新进程的进程ID,如果返回值为0,则表示当前进程为子进程;如果返回值大于0,则表示当前进程为父进程,返回值即为子进程的进程ID;如果返回值为-1,则表示创建子进程失败。 33. 系统调用是通过操作系统提供的接口直接调用内核功能的一种编程方式,而库函数则是在用户空间中运行的函数库,通过调用库函数来实现对系统功能的访问。 34. C/S架构指的是客户端/服务器架构,其中客户端和服务器通过网络进行通信,客户端向服务器发起请求,服务器处理请求并返回结果给客户端。 35. sprintf函数是C语言中的字符串格式化函数,用于将格式化后的字符串输出到指定的缓冲区中。 36. 进程的创建可以通过fork函数来实现,进程间通信的机制包括管道共享内存、消息队列、信号量等。 37. 线程的创建可以通过pthread_create函数来实现,线程间通信的机制包括互斥锁、条件变量、信号量等。 38. 如果a可能为负数,则应该定义为有符号类型。 39. 可以使用wait或waitpid函数来让父进程等待子进程结束并获取子进程的退出状态。 40. 使用管道共享内存时,需要先创建相应的管道共享内存,然后通过读写操作来进行进程间通信。 41. 进程关闭后,管道共享内存仍然存在,但是不能再进行读写操作。 42. 僵尸进程是指已经结束但是父进程还没有处理其退出状态的进程。在父进程没有使用wait或waitpid等函数来处理子进程退出状态时,子进程就会变成僵尸进程。 43. 解决僵尸进程的方法包括使用wait或waitpid函数等待子进程退出并获取其退出状态,或者使用信号处理函数来处理SIGCHLD信号。 44. 进程是系统中正在运行的程序的实例,拥有独立的内存空间和资源。线程是进程中的执行单元,多个线程共享同一进程的资源,可以提高程序的并发性和效率。 45. 进程间的通信方式包括管道共享内存、消息队列、信号量等。 46. 同步是指协调多个进程或线程的执行顺序,保证它们按照一定的顺序执行。互斥是指在同一时间只有一个进程或线程可以访问共享资源,其他进程或线程需要等待该进程或线程释放资源后才能访问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值