注册域名网站备案,wordpress 地理位置签到,流量套餐,网站建设资费------------------------本文为学习进程记录的学习笔记#xff0c;如有问题欢迎指正 --------------------------
目录
1.定义
2.进程的种类
2.进程的内存布局
3.进程控制块#xff08;PCB#xff09;
4.进程源语
fork()
写时复制
exec()
execl函数
wait() #进…------------------------本文为学习进程记录的学习笔记如有问题欢迎指正 --------------------------
目录
1.定义
2.进程的种类
2.进程的内存布局
3.进程控制块PCB
4.进程源语
fork()
写时复制
exec()
execl函数
wait() #进程回收
waitpid()函数
5.僵尸进程
僵尸进程产生原因
僵尸进程危害
如何回收僵尸进程
6.孤儿进程 Orphan
孤儿进程产生原因
孤儿进程危害
孤儿进程预防
孤儿进程的检测与处理
7.Deamon守护进程
9.进程间通信方法
进程间通信的应用场景
管道
PIPE匿名管道
管道的销毁和释放
匿名管道的优缺点
匿名管道使用时的几种特殊情况具有普遍意义
FIFO命名管道
匿名管道的特殊情况适用于命名管道
匿名管道和命名管道的区别
MMAP 文件映射
分类
函数原型
mmap的权限问题
read与mmap的区别
优缺点
信号
信号量
消息队列
套接字
总结 1.定义 狭义定义进程是正在运行的程序的实例
广义定义进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元在传统的操作系统中进程既是基本的分配单元也是基本的执行单元。
2.进程的种类
1.用户进程也称为应用进程是由用户启动的进程来执行应用程序。用户进程是在用户空间中运行的并且可以与其他用户进程进行通信。
2.系统进程也称为内核进程是由操作系统内核启动和管理的进程。
3.守护进程是在后台运行的进程独立于用户会话的一种特殊进程。守护进程通常在系统启动时启动并持续运行提供系统服务或执行特定的任务。
4.孤儿进程当父进程意外终止或退出时子进程可能成为孤儿进程。孤儿进程将由操作系统接管并由操作系统中的一个特殊进程通常是init进程接收和回收。
5.僵尸进程当子进程完成执行终止后但父进程尚未调用wait()或waitpid()来获取子进程的退出状态码时子进程将成为僵尸进程。僵尸进程仍保留在进程表中但不再执行任何任务。
2.进程的内存布局

各个分段的含义 文本段Text也称为代码段。进程启动时会将程序的代码加载到物理内存中文本段映射到这片物理内存。 数据段Data包含程序显式初始化的全局变量和静态变量即已初始化且初值不为0的全局变量(也包括静态全局变量)和静态局部变量 未初始化数据BSS未初始化的全局变量和静态变量 栈Stack存储局部、临时变量函数调用时存储函数的返回指针用于控制函数的调用和返回。在程序块开始时自动分配内存,结束时自动释放内存其操作方式类似于数据结构中的栈。 堆Heap存储动态内存分配,需要程序员手工分配,手工释放.注意它与数据结构中的堆是两回事分配方式类似于链表。 栈的内存地址向下增长堆得内存地址向上增长。 内存映射段Memory Mapping在栈与堆之间有一个内存映射端。内核通过这一段将文件的内容直接映射到内存进程可以通过 mmap 系统调用请求这种映射。内存映射是一种方便高效的文件 I/O 方式所以它也被用来加载动态库。 内核段Kernel这部分是操作系统内核运行时所占用内存在各进程虚拟地址空间中的映射。所有进程都有且映射地址相同因为都映射到内核使用的内存。这段内存只有内核能访问用户进程无法访问到该段落。
3.进程控制块PCB
每个进程在内核中都有一个进程控制块PCB来维护进程相关的信息。Linux内核的进程控制块是task_struct结构体。 struct task_struct { volatile long state; //说明了该进程是否可以执行还是可中断等信息 unsigned long flags; // flags 是进程号在调用fork()时给出 int sigpending; // 进程上是否有待处理的信号 mm_segment_t addr_limit; //进程地址空间,区分内核进程与普通进程在内存存放的位置不同 //0-0xBFFFFFFF for user-thead //0-0xFFFFFFFF for kernel-thread //调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度 volatile long need_resched; int lock_depth; //锁深度 long nice; //进程的基本时间片 //进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR, 分时进程:SCHED_OTHER unsigned long policy; struct mm_struct *mm; //进程内存管理信息 int processor; //若进程不在任何CPU上运行, cpus_runnable 的值是0否则是1 这个值在运行队列被锁时更新 unsigned long cpus_runnable, cpus_allowed; struct list_head run_list; //指向运行队列的指针 unsigned long sleep_time; //进程的睡眠时间 //用于将系统中所有的进程连成一个双向循环链表, 其根是init_task struct task_struct *next_task, *prev_task; struct mm_struct *active_mm; struct list_head local_pages; //指向本地页面 unsigned int allocation_order, nr_local_pages; struct linux_binfmt *binfmt; //进程所运行的可执行文件的格式 int exit_code, exit_signal; int pdeath_signal; //父进程终止时向子进程发送的信号 unsigned long personality; //Linux可以运行由其他UNIX操作系统生成的符合iBCS2标准的程序 int did_exec:1; pid_t pid; //进程标识符,用来代表一个进程 pid_t pgrp; //进程组标识,表示进程所属的进程组 pid_t tty_old_pgrp; //进程控制终端所在的组标识 pid_t session; //进程的会话标识 pid_t tgid; int leader; //表示进程是否为会话主管 struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr; struct list_head thread_group; //线程链表 struct task_struct *pidhash_next; //用于将进程链入HASH表 struct task_struct **pidhash_pprev; wait_queue_head_t wait_chldexit; //供wait4()使用 struct completion *vfork_done; //供vfork() 使用 unsigned long rt_priority; //实时优先级用它计算实时进程调度时的weight值 //it_real_valueit_real_incr用于REAL定时器单位为jiffies, 系统根据it_real_value //设置定时器的第一个终止时间. 在定时器到期时向进程发送SIGALRM信号同时根据 //it_real_incr重置终止时间it_prof_valueit_prof_incr用于Profile定时器单位为jiffies。 //当进程运行时不管在何种状态下每个tick都使it_prof_value值减一当减到0时向进程发送 //信号SIGPROF并根据it_prof_incr重置时间. //it_virt_valueit_virt_value用于Virtual定时器单位为jiffies。当进程运行时不管在何种 //状态下每个tick都使it_virt_value值减一当减到0时向进程发送信号SIGVTALRM根据 //it_virt_incr重置初值。 unsigned long it_real_value, it_prof_value, it_virt_value; unsigned long it_real_incr, it_prof_incr, it_virt_value; struct timer_list real_timer; //指向实时定时器的指针 struct tms times; //记录进程消耗的时间 unsigned long start_time; //进程创建的时间 //记录进程在每个CPU上所消耗的用户态时间和核心态时间 long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS]; //内存缺页和交换信息: //min_flt, maj_flt累计进程的次缺页数Copy on Write页和匿名页和主缺页数从映射文件或交换 //设备读入的页面数 nswap记录进程累计换出的页面数即写到交换设备上的页面数。 //cmin_flt, cmaj_flt, cnswap记录本进程为祖先的所有子孙进程的累计次缺页数主缺页数和换出页面数。 //在父进程回收终止的子进程时父进程会将子进程的这些信息累计到自己结构的这些域中 unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap; int swappable:1; //表示进程的虚拟地址空间是否允许换出 //进程认证信息 //uid,gid为运行该进程的用户的用户标识符和组标识符通常是进程创建者的uidgid //euidegid为有效uid,gid //fsuidfsgid为文件系统uid,gid这两个ID号通常与有效uid,gid相等在检查对于文件 //系统的访问权限时使用他们。 //suidsgid为备份uid,gid uid_t uid,euid,suid,fsuid; gid_t gid,egid,sgid,fsgid; int ngroups; //记录进程在多少个用户组中 gid_t groups[NGROUPS]; //记录进程所在的组 //进程的权能分别是有效位集合继承位集合允许位集合 kernel_cap_t cap_effective, cap_inheritable, cap_permitted; int keep_capabilities:1; struct user_struct *user; struct rlimit rlim[RLIM_NLIMITS]; //与进程相关的资源限制信息 unsigned short used_math; //是否使用FPU char comm[16]; //进程正在运行的可执行文件名 int link_count, total_link_ count; //文件系统信息 //NULL if no tty 进程所在的控制终端如果不需要控制终端则该指针为空 struct tty_struct *tty; unsigned int locks; //进程间通信信息 struct sem_undo *semundo; //进程在信号灯上的所有undo操作 struct sem_queue *semsleeping; //当进程因为信号灯操作而挂起时他在该队列中记录等待的操作 //进程的CPU状态切换时要保存到停止进程的task_struct中 struct thread_struct thread; struct fs_struct *fs; //文件系统信息 struct files_struct *files; //打开文件信息 spinlock_t sigmask_lock; //信号处理函数 struct signal_struct *sig; //信号处理函数 sigset_t blocked; //进程当前要阻塞的信号每个信号对应一位 struct sigpending pending; //进程上是否有待处理的信号 unsigned long sas_ss_sp; size_t sas_ss_size; int (*notifier)(void *priv); void *notifier_data; sigset_t *notifier_mask; u32 parent_exec_id; u32 self_exec_id; spinlock_t alloc_lock; void *journal_info; }; 4.进程源语 fork() #进程创建 exec() #进程功能重载 wait() #进程回收 waitpid() #进程回收,wait函数的升级版 fork() 开发者调用fork后内核创建子进程而后使用读时共享写时复制的方式解决继承困境。 读共享采用映射技术将父进程的用户空间映射给子进程子进程通过映射可以读访问父进程的资源写的时候采用clone技术.
写时复制 在写时复制中当多个进程或线程需要访问相同的数据时不会立即进行数据的复制而是共享同一份数据副本。只有当某个进程或线程试图修改数据时才会进行数据的复制以确保修改操作不会影响其他进程或线程的访问。 具体来说当多个进程或线程共享同一份数据时它们实际上共享的是指向数据副本的指针。当某个进程或线程试图修改数据时操作系统会在内存中为该进程或线程创建一个新的副本然后将修改操作应用于新的副本而不会影响其他进程或线程。这种延迟复制的方式可以节省内存和时间开销特别适用于需要频繁复制数据的场景。 写时复制技术常用于操作系统的进程管理和文件系统中以提高系统的性能和资源利用率。例如当一个进程创建子进程时子进程会与父进程共享相同的内存空间只有在需要修改数据时才会进行复制。这样可以避免不必要的数据复制提高进程的创建和执行效率。 写时复制只适用于读多写少的场景。如果多个进程或线程频繁地修改数据写时复制的效果可能会受到影响因为频繁的数据复制操作会增加系统的开销。
exec()
exec系列函数execl、execlp、execle、execv、execvp
以上exec系列函数区别
1带l 的exec函数execl,execlp,execle表示后边的参数以可变参数的形式给出且都以一个空指针结束。
2带 p 的exec函数execlp,execvp表示第一个参数path不用输入完整路径只有给出命令名即可它会在环境变量PATH当中查找命令
3不带 l 的exec函数execv,execvp表示命令所需的参数以char *arg[]形式给出且arg最后一个元素必须是NULL
4带 e 的exec函数execle表示将环境变量传递给需要替换的进程
execl函数
用这个函数可以把当前进程替换为一个新进程且新进程与原进程有相同的PID。 execl可以以极小的代价使用系统现有的命令或程序功能 如果重载服务器可以避免服务器过于臃肿插件模块的更新与迭代也与服务器无关不会影响线上服务器
wait() #进程回收
wait()阻塞函数子进程存在但未产生僵尸wait阻塞等待产生僵尸后立即回收。
注意wait每调用一次只能回收一次
僵尸进程——如果进程结束没有被及时回收则产生僵尸进程僵尸进程是内存泄漏
waitpid()函数
支持非阻塞回收无需使父进程持续陷入阻塞可以让回收与任务之间切换交替执行
pid_t pidwaitpid(pid_t pid,int * status,int flag);
第一个参数pid: -1 回收任意子进程 0 传递一个指定进程的id回收指定进程 0 表示回收同组的任意子进程 -1 指定组id跨组回收回收父子关系的进程
5.僵尸进程 僵尸进程是指一个进程使用 fork 创建子进程如果子进程退出而父进程并没有调用 wait 或 waitpid 获取子进程的状态信息那么子进程的进程描述符仍然保存在系统中这种进程称之为僵尸进程。
僵尸进程产生原因 子进程先结束父进程需回收子进程PCB否则会造成内存泄漏当子进程结束时如exit系统会回收子进程用户区和部分内核区不回收PCB遗留下来的PCB若不回收会造成内存泄漏。
僵尸进程危害 内存泄漏持续占用系统资源无法重新分配PCB为较为庞大的结构体很多内部成员占据着较大的内存 影响子进程的创建系统创建子进程的数量为可以创建的PCB的数量僵尸进程大量占有PCB则可新创建的子进程会减少
如何回收僵尸进程
只能通过父进程回收 阻塞方式pid_t waitint* status函数调用完wait后等待子进程结束立即回收调用一次只回收一个僵尸进程PCB进程PCB会将退出状态通过参数返回便于父进程查看子进程结束的原因回收成功返回子进程的PID如果没有子进程或回收失败则返回-1。 非阻塞方式pid_t waitpid(pid_t pid, int *status, int option)函数采用轮询的方式回收子进程。 pid pid 0回收指定PID的子进程 pid 0回收和父进程同组的子进程 pid -1回收所有子进程 optionWNOHANG设置为非阻塞回收。 返回值 -1回收失败没有子进程 0非阻塞返回暂时不需要回收 大于0返回子进程的PID 3.设计父子进程通信机制子进程结束时向父进程发送信号父进程接收到信号后回收子进程资源。
6.孤儿进程 Orphan 孤儿进程属于后台程序使用终端快捷键对后台程序无效只能通过kill命令利用信号杀死进程 孤儿进程的危害是弹性的取决于孤儿进程执行的任务 如果孤儿进程持续占用系统资源会影响操作系统的稳定性 即使孤儿进程挂起或睡眠也要尽快杀死孤儿进程因为它会占用pcb也有系统开销影响进程创建
孤儿进程产生原因
父进程先于子进程结束子进程成为孤儿进程持续占用系统资源失控给系统造成危害。
Linux下所有孤儿进程都会由托管进程进行回收。父进程结束子进程变为孤儿进程父子关系变更子进程成为托管进程的子进程。
孤儿进程危害
1.内存泄露影响新进程创建孤儿进程还在执行可能持续占用系统资源
2.孤儿进程的危害是有弹性的如果孤儿进程阻塞或挂起为常态损失很小但如果孤儿进程是持续运行的并且大量申请系统资源那么危害较大
孤儿进程预防 在创建子进程后父进程在子进程执行完毕前不退出可以使用系统调用wait()或waitpid()等待子进程结束。 使用守护进程Daemon Process将子进程设置为守护进程当父进程退出后守护进程会被init进程接管并自动回收资源避免产生孤儿进程。
孤儿进程的检测与处理 创建一个进程和写端建立管道p向L发送自己的pid,如果P是正常退出即回收完全部子进程后退出就向L发送“quit”然后正常退出否则L如果发现P失效了就说明L是异常退出就按组杀死进程。 7.Deamon守护进程 守护进程又名精灵进程是操作系统经典的后台服务程序后台进程不干涉前台在后台持续工作守护进程在后台都是低消耗模式进程属于睡眠态S 守护进程工作模式间隔执行定时执行条件触发执行 守护进程生命周期更长开机启动关机才结束生命周期随系统持续 系统守护进程是操作系统组件每个组件都负责一类服务维护系统正常执行维护系统功能稳定 严格意义上来讲守护进程后台程序就是孤儿进程人为产生孤儿进程不仅无害还可以为系统服务为软件服务
9.进程间通信方法 进程用户空间是相互独立的一般而言是不能相互访问的。但很多情况下进程间需要互相通信来完成系统的某项功能。进程通过与内核及其它进程之间的互相通信来协调它们的行为。
进程间通信的应用场景
数据传输一个进程需要将它的数据发送给另一个进程发送的数据量在一个字节到几兆字节之间。
共享数据多个进程想要操作共享数据一个进程对共享数据的修改别的进程应该立刻看到。
通知事件一个进程需要向另一个或一组进程发送消息通知它它们发生了某种事件如进程终止时要通知父进程。
资源共享多个进程之间共享同样的资源。为了作到这一点需要内核提供锁和同步机制。
进程控制有些进程希望完全控制另一个进程的执行如Debug进程此时控制进程希望能够拦截另一个进程的所有陷入和异常并能够及时知道它的状态改变。
管道
管道是进程间通信的一种方式可以把管道看作是一个文件由操作系统内核进行管理。
管道分为命名管道和匿名管道命名管道是一个存在于文件系统中的一个文件通过mkfifo来创建可以在不同的进程间传递信息。
匿名管道相当于一个字节流用于有亲缘关系的进程间通信。数据传输是单向的一端写一端读。
管道的基本特性
1.方向性数据流通方向
2.存储性缓存少量数据暂时存储
3.确定通信方向单工
PIPE匿名管道
匿名管道通过打开的文件描述符来标识的。——用于具有亲缘关系间进程之间的通信。
进程的内核空间是共享内存绝大多数进程通信方式都是在内核层完成的。
用户层将数据传到内核层接收方就能从内核层找到要接收的数据再传给用户层就完成了进程间的通信。
pipe()创建管道函数会返回两个描述符用来读写管道接收方如果和发送方共用一个pipe就能共用一个管道了可以通过fork父子进程实现。
int pipeint fds[2] #可以创建一个管道创建成功后传出管道的使用描述符
fds[0] #管道读描述符用于读取管道缓冲区的数据队列出队操作
fds[1] #管道写描述符用于向管道写数据队列入队操作
#include stdlib.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include sys/fcntl.h
#include pthread.h
#include signal.h
#include sys/wait.h#define MSG Hi//父进程利用匿名管道通信
int main(void)
{pid_t pid;int fds[2];pipe(fds);//管道创建成功传出描述符pidfork();if(pid0){close(fds[0]);//向管道写入数据write(fds[1],MSG,strlen(MSG));printf(parent %d Send msg success..\n,getpid());close(fds[1]);wait(NULL);}else if(pid0){close(fds[1]);int len;char buffer[1500];bzero(buffer,sizeof(buffer));lenread(fds[0],buffer,sizeof(buffer));printf(child %d recv pipe ,msg%s\n,getpid(),buffer);close(fds[0]);exit(0);
}else{perror(fork call failed);exit(0);}return 0;
管道的销毁和释放
管道的销毁释放当管道的引用计数为0系统会自行释放管道空间 匿名管道的优缺点
优点 经典的进程间通信手段实现使用方便
缺点只能具有亲缘关系的进程完成数据交互独立的进程无法使用匿名管道通信
默认情况下匿名管道用于传输无格式字节流接收方不清楚数据的类型和大小 可以自定义数据格式
匿名管道为单工通信
单工任意时刻非读即写
半双工任意时刻非读即写不同时刻方向可切换单工的一种
全双工任意时刻可以同时读写
匿名管道使用时的几种特殊情况具有普遍意义
1.写阻塞读写两端有效读端未读数据写端写管道当写满管道后产生写阻塞
2.读阻塞读写两端有效写端未向管道写数据读端读取空管道时产生读阻塞
3.写端失效读端读取管道剩余内容后再次读返回0
4.读端失效写端尝试向管道写数据系统会向写端进程发送SIGPIPE信号杀死写端进程
FIFO命名管道
命名管道是一种有名的通信方式可以实现无关进程之间的通信。它可以在不具有亲缘关系的进程之间传递数据并且可以实现双向通信。
创建的两种方式
1.mkfifo names #命令方式
2.mkfifo(const char* names,int mod) #函数方式
#创建有名管道成功后都会生成一个同名的管道文件在内核里也会有一块缓冲区文件指针指向缓冲区文件类型p
#使用有名管道可以完成进程通信但是没有任何限制是否亲缘都可以
匿名管道的特殊情况适用于命名管道
命名管道文件的访问权限管道文件的使用规则必须同时满足读写两种权限才可以访问管道文件如果只有其中一种权限那么open 阻塞等待另外一种。命名管道访问与权限有关与进程数无关一个进程拥有足够的权限一样可以访问管道文件。一个进程中有多个读序列阻塞只对第一个读序列生效其他所有读序列被设置为非阻塞 原子访问与非原子访问
原子访问一次写入的数据小于等于管道的大小 非原子访问一次写入的数据大于管道的大小 写端代码
#include stdio.h
#include unistd.h
#include stdlib.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h#define MSG Hi Can u hear me?//写端
int main(void)
{int wfd;wfdopen(FIFO_FILE,O_WRONLY);write(wfd,MSG,strlen(MSG));printf(write pro %d send success..\n,getpid());close(wfd);return 0;}读端代码
#include stdio.h
#include unistd.h
#include stdlib.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h//读端
int main(void)
{int rfd;int len;char buf[1500];bzero(buf,sizeof(buf));rfdopen(FIFO_FILE,O_RDONLY);lenread(rfd,buf,strlen(buf));printf(read pro %d read msg %s\n,getpid(),buf);close(rfd);return 0;}匿名管道和命名管道的区别 对于匿名管道它的通信范围是存在父子关系的进程。因为管道没有实体也就是没有管道文件只能通过 fork 来复制父进程 fd 文件描述符来达到通信的目的。 对于命名管道它可以在不相关的进程间也能相互通信。因为命令管道提前创建了一个类型为管道的设备文件在进程里只要使用这个设备文件就可以相互通信。
匿名管道
a.读端关闭-如果写端继续写则会收到系统发送的信号写端被关闭。
b.读端存在-如果始终未读取数据写端可以继续写入数据写满后写阻塞
c.写端关闭-如果读端读取剩余数据可以。否则返回0。
d.写端存在-始终未写入数据读阻塞。
命名管道
a. 使用时如果多个读端则只有第一个读端阻塞读取其他非阻塞读取。
b. 阻塞读取时如果写入数据n 管道缓存区 - 写入的n字节是非原子的直到n字节全部写完write才返回。如果写入数据n 管道缓存区 - 写入的n字节是原子的如果没有n字节空间则写阻塞
c. 非阻塞读取时如果写入数据n 管道缓存区 - 如果管道满返回-1并设置EAGAIN否则会随机写入1-n字节数据具体写多少需要用户查看write返回值。
如果写入数据n 管道缓存区 - 如果空间足够则可以写入否则返回-1
MMAP 文件映射
mmap用途比较多样可以通过映射的方式处理文件也适合处理大文件可以进行进程间的通信进行零拷贝数据传输的一种方式。
共享内存让不同进程看到同一份资源的方式就是在物理内存当中申请一块内存空间然后将这块内存空间分别与各个进程各自的页表之间建立映射再在虚拟地址空间当中开辟空间并将虚拟地址填充到各自页表的对应位置使得虚拟地址和物理地址之间建立起对应关系至此这些进程便看到了同一份物理内存这块物理内存就叫做共享内存。
分类
1、文件映射 磁盘文件映射进程的虚拟地址空间使用文件内容初始化物理内存。
2、匿名映射 初始化全为0的内存空间。
而对于映射关系是否共享又分为
1、私有映射(MAP_PRIVATE) 多进程间数据共享修改不反应到磁盘实际文件是一个copy-on-write写时复制的映射方式。
2、共享映射(MAP_SHARED) 多进程间数据共享修改反应到磁盘实际文件中。 所以进程间的通信可以让两个进程对同一个映射文件进行共享映射。
映射文件大小不能为空要用\0填充拓展空文件。
映射文件大小要小于等于共享文件大小否则权限越界。
函数原型
1.void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
成功执行时如果mmap 调用成功返回映射内存的地址 void *p
如果映射失败返回map_failed,进行错误处理获取失败原因。
2.munmap(void* p,int size);映射内存使用完毕后通过该函数释放映射内存
写端代码
#include stdio.h
#include unistd.h
#include stdlib.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include sys/mman.h
typedef struct
{int price;char Gname[1024];
}msg_t;int main(void)
{//创建映射文件int fd;int No1;if((fdopen(MAP_IPC,O_RDWR|O_CREAT,0664))-1){perror(open call failed);exit(0);}
//拓展文件ftruncate(fd,sizeof(msg_t));msg_t* ptrNULL;if((ptrmmap(NULL,sizeof(msg_t),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0))MAP_FAILED){perror(mmap call failed);exit(0);}close(fd);ptr-price89;bzero(ptr-Gname,1024);while(1){//修改映射内存数据sprintf(ptr-Gname,Apex %d,No);sleep(1);}munmap(ptr,sizeof(msg_t));return 0;
}
读端代码
#include stdio.h
#include unistd.h
#include stdlib.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include sys/mman.h
typedef struct
{int price;char Gname[1024];
}msg_t;int main(void)
{//创建映射文件int fd;int No1;if((fdopen(MAP_IPC,O_RDWR|O_CREAT,0664))-1){perror(open call failed);exit(0);}//拓展文件msg_t* ptrNULL;if((ptrmmap(NULL,sizeof(msg_t),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0))MAP_FAILED){perror(mmap call failed);exit(0);}close(fd);while(1){//显示结果printf(Gname %s ,Price %d \n,ptr-Gname,ptr-price);sleep(1);}munmap(ptr,sizeof(msg_t));return 0;
}mmap的权限问题 如果用户通过共享映射来读写文件那么即使没有文件权限也可以访问文件了为了避免这种情况mmap会查看open中使用的权限如果是只读打开就不允许读写权限映射。
read与mmap的区别 从图中可以看出mmap要比普通的read系统调用少一次copy,因为read调用进程是无法直接访问kernel space 的所以在read系统调用返回前内核需要将数据从内核复制到进程指定的buffer但mmap之后进程可以访问mmap的数据。
优缺点
优点如下
1、对文件的读取操作跨过了页缓存减少了数据的拷贝次数用内存读写取代I/O读写提高了文件读取效率。
2、实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内从而被对方空间及时捕捉。
3、提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动达到进程间通信和进程间共享的目的。同时如果进程A和进程B都映射了区域C当A第一次读取C时通过缺页从磁盘复制文件页到内存中但当B再读C的相同页面时虽然也会产生缺页异常但是不再需要从磁盘中复制文件过来而可直接使用已经保存在内存中的文件数据。
4、可用于实现高效的大规模数据传输。内存空间不足是制约大数据操作的一个方面解决方案往往是借助硬盘空间协助操作补充内存的不足。但是进一步会造成大量的文件I/O操作极大影响效率。这个问题可以通过mmap映射很好的解决。换句话说但凡是需要用磁盘空间代替内存的时候mmap都可以发挥其功效。
缺点如下:
1.文件如果很小是小于4096字节的比如10字节由于内存的最小粒度是页而进程虚拟地址空间和内存的映射也是以页为单位。虽然被映射的文件只有10字节但是对应到进程虚拟地址区域的大小需要满足整页大小因此mmap函数执行后实际映射到虚拟内存区域的是4096个字节11~4096的字节部分用零填充。因此如果连续mmap小文件会浪费内存空间。
2.对变长文件不适合文件无法完成拓展因为mmap到内存的时候你所能够操作的范围就确定了。
3.如果更新文件的操作很多会触发大量的脏页回写及由此引发的随机IO上。所以在随机写很多的情况下mmap方式在效率上不一定会比带缓冲区的一般写快。
信号
信号是一种软件中断用于通知进程发生了某个事件。它可以用于进程间的通信和同步。
使用信号技术完成父子进程间通信实现父子进程交替报数数据要相互传递。
思路
1.父子进程均要完成捕捉设定让信号失效
2.使用开发者信号SIGUSR1(10),SIGUSR2(12)
3.sigqueue(pid_t pid,int signo,union sigval val);//发送信号的同时携带数据发送的数据可以让捕捉函数的参数捕捉捕捉函数中直接用就可以
4.通过继承的特性让父进程设置信号屏蔽子进程继承此屏蔽而后完成捕捉设定解除屏蔽避免在子进程刚刚被创建还没来得及捕捉就有信号递达而被杀死
信号量
消息队列
套接字
它是更为通用的进程间通信机制可用于不同机器之间的进程间通信。
总结
以下是这些进程间通信方式各自的一些优缺点 管道 优点简单易用。 缺点只能用于有亲缘关系的进程效率相对较低不适合大量数据传输。 命名管道 优点可用于无亲缘关系的进程间通信。 缺点依然存在一定局限性。 消息队列 优点可以实现异步通信能容纳多条消息。 缺点消息大小有限制消息队列满时可能会阻塞。 信号量 优点能很好地实现进程同步。 缺点主要用于同步控制数据通信能力相对较弱。 共享内存 优点效率非常高数据传输速度快。 缺点需要配合同步机制来避免数据竞争和一致性问题。 套接字 优点可跨网络适用范围广。 缺点实现相对复杂一些。