Linux多进程开发IV
1.共享内存。允许两个或多个进程共享物理内存的同一块区域(段)。
-
共享内存是进程用户空间的一部分,因此这种IPC机制需要更少的内核处理。一个进程将数据复制到共享内存中,那么这部分数据就会对其他所有共享同一个段的进程可用。
-
与管道等要求发送进程将数据从用户空间的缓冲区复制到内核内存和接收进程将数据从内核内存复制进用户空间的缓冲区的做法相比,这种IPC技术速度更快。
2.共享内存的使用步骤。
-
调用shmget()创建一个新共享内存段或取得一个已有的共享内存段的标识符。返回共享内存标识符。
-
使用shmat()附上共享内存,即使该段成为调用进程的虚拟内存的一部分。
-
此刻在程序中可以像对待其他可用内存那样对待该共享内存段。可以使用shmat()调用返回的addr值(指向进程的虚拟地址空间中该共享内存段的起点的指针)来引用共享内存。
-
调用shmdt()分离共享内存段。
-
调用shctl()删除共享内存段。只有当当前所有附加内存段的进程都与之分离之后内存段才会销毁。只有一个进程需要执行这一步。
3.相关函数。
#include <sys/ipc.h> #include <sys/shm.h> int shmget(ket_t key, size_t size, int shmflg); - 作用:创建一个新的共享内存段,或者获取一个既有的共享内存段的标识 - key: 标识共享内存,16进制,非0 - size: 共享内存大小 - shmflg: 共享内存的属性,访问属性,附加属性:创建/判断共享内存是否存在,IPC_CREAT 创建;IPC_EXCL 判断共享内存是否存在,与IPC_CREAT一起使用;e.g.: IPC_CREAT | IPC_EXCL | 0664 - 返回值:成功返回>0 表示共享内存的引用的ID,失败返回-1并设置errno void *shmat(int shmid, const void *shmadd, int shmflg); - 作用:和当前进程进行关联 - shmid: 共享内存的标识,由shmget返回值获取 - shaddr: 申请的共享内存起始地址,指定NULL,由内核指定 - shmflg: 对共享内存的操作,SHM_EXEC 执行;SHM_RDONLY 只读;0 读写权限 - 返回值:成功返回共享内存的首地址,失败返回(void*)-1 int shmdt(const void *shaddr); - 作用:解除当前进程和共享内存的关联 - shmaddr: 共享内存的首地址 - 返回值:成功返回0,失败返回-1 int shmctl(int shmid, int cmd, struct shmid_ds *buf); - 作用:对共享内存进行操作。删除共享内存,共享内存要删除才会消失,创建共享内存的进程被销毁对共享内存没有影响 - shmid: 共享内存的id - cmd: 要做到操作,IPC_STAT: 获取共享内存的当前状态;IPC_SET:设置共享内存的状态;IPC_RMID: 标记共享内存被销毁 - buf: 需要设置或者获取的共享内存的属性信息,IPC_STAT: buf存储数据;IPC_SET:buf中需要初始化数据,设置到内核中;IPC_RMID:没有用,NULL #include <sys/typre.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id); - 作用:根据指定的路径名,和int值,生成一个共享内存的key - pathname: 制定一个存在的路径 - proj_id: int类型,系统调用只会使用其中的1个字节,0-255,一般指定一个字符'a'
4.共享内存相关问题。
-
操作系统如何知道共享内存被多少进程关联?
- 共享内存维护了一个结构体struct shmid_ds,这个结构体有一个成员 shm_nattch 记录里关联进程的个数。
-
可不可以对共享内存多次删除? shmctl
- 可以,因为shmctl只是标记删除共享内存,不是直接删除。只有当和共享内存关联的进程数为0时才会被真正删除。当共享内存的key位0时,表示共享内存被标记删除。
5.共享内存和内存映射的区别。
-
共享内存可以直接创建,内存映射需要磁盘文件(匿名映射除外)
-
共享内存效率更高
-
共享内存:所有进程操作的是同一块共享内存。内存映射:每个进程在自己的虚拟地址空间中有一个独立的内存
-
数据安全:
-
进程突然退出:共享内存还存在,内存映射区消失
-
运行进程的电脑死机或宕机:数据存在共享内存中的没有了;内存映射区的数据,由于磁盘文件中的数据还在,所有内存映射区的数据还存在
-
生命周期:
-
共享内存:进程退出,共享内存还在,标记删除(所有关联的进程数为0才会删除)或者关机。如果一个进程退出,会自动和共享内存取消关联。
-
内存映射区:进程退出,内存映射区销毁
6.共享内存操作命令。
ipcs -a 打印所有进程通信方式 -m 打印使用共享内存进行进程通信的信息 -q 打印使用消息队列进行进程通信的信息 -s 打印使用信号进行进程通信的信息 ipcrm -M shmkey 使用shmkey移除创建的共享内存段 ipcrm -m shmid 使用shmid移除标识的共享内存段 ipcrm -Q msgkey 使用msgkey移除创建的消息队列 ipcrm -q msgid 使用msgkey移除标识的消息队列 ipcrm -S semkey 使用semkey移除创建的信号 ipcrm -s semid 使用emkey移除标识的信号
7.控制终端。
-
用户通过终端登录系统后得到shell进程,这个终端就是该shell进程的控制终端。
-
控制终端是保存在PCB中的信息,由该shell进程启动的其它进城的控制终端也是该终端。
-
默认情况下,每个进程的标准输入、输出、错误都指向控制终端。进程从标准输入读就是读用户键盘输入,进程往标准输出写就是输出到显示器上。
-
在控制终端输入特殊的控制键可以给前台进程发送信号。无法给后台进程发送信号(& 结尾)。
$ tty 查看控制终端的设备 $ each $$ 查看当前终端的进程号
8.进程组。
-
进程组是一组相关进程的集合。
-
进程组标识符(PGID)。
-
进程组拥有一个生命周期,开始时间是首进程创建组的时刻,结束时间是最后一个成员进程退出组的时刻。进程组首进程无需是最后一个离开进程组的成员。
9.会话。
-
会话是一组相关进程组的集合。会话首进程是创建该新会话的进程,其进程ID成为会话ID。新进程会继承其父进程的会话ID。
-
一个会话中的所有进程共享单个控制终端。控制终端在会话首进程首次打开终端设备室被建立,一个终端最多会成为一个会话的控制终端。
-
任一时刻,会话中其中一个进程组成为终端的前台进程组,其他进程组会成为后台进程组。只有前台进程组中的进程才能从控制终端读取数据。
-
控制终端连接建立以后,会话首进程成为该终端的控制进程。
10.进程组、会话、控制终端之间的关系。
find / 2 > /dev/null | wc -l & sort < longlist | uniq -c
11.进程组、会话操作函数。
pid_t getpgrp(void); pid_t getpgid(pid_t pid); int setpgid(pid_t pid, pid_t pgid); pid_t getsid(pid_t pid); pid_t setsid(void);
12.守护进程(Daemon Process 精灵进程)。
-
Linux中的后台服务进程。生存期较长,通常独立于控制终端并且周期性执行某种任务或等待处理某些发生的事件。以d结尾。
-
特点:
-
生命周期很长,守护进程会在系统启动时被创建并一直运行直至系统关闭。
-
在后台运行并且不拥有控制终端。没有控制终端确保了内核永远不会为守护进程自动生成任何控制信号和终端信号。
-
e.g.: Linux的大多数服务器是守护进程实现的。Internet服务器 inetd,Web服务器 httpd 等。
13.守护进程的创建步骤。
-
执行一个fork(),父进程退出,子进程继续执行。(必须)
-
子进程调用setid()开启一个新的会话。(必须)
-
使用umask(0)清除进程的umask以确保守护进程创建文件和 目录时拥有权限。
-
修改进程的当前工作目录,改为根目录(/)。
-
关闭守护进程从父进程继承而来的所有打开的文件描述符。
-
关闭文件描述符0,1,2之后,守护进程打开/dev/null 并使用dup2()使所有这些描述符指向这个设备。
-
核心业务逻辑。(必须)