IPC进程线程

本文详细介绍了操作系统中的进程概念,包括进程的生命周期、状态转换、并发性和独立性等特性。重点阐述了进程的创建(fork()和exec())、执行和终止,以及wait()和waitpid()在父进程等待子进程结束时的角色。此外,还讨论了进程间的通信机制,如信号、管道(无名管道与有名管道)、消息队列、共享内存和信号量,以及它们在同步和互斥中的应用。最后,简要提及了线程和协程的概念,以及Linux守护进程的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

进程

  • 进程:进程是指一个具有独立功能的程序在某个数据集合上的一次动态执行过程,是操作系统及进行资源分配和调度的基本单位。
    注意:一次任务的运行可以激活多个进程。进程是一个程序的执行的过程,是动态的,包括程序的创建、调度、消亡

  • 进程具有并发性、动态性、交互性、独立性等主要特性
    1.并发性:指的是系统中多个进程可以同时并发执行,相互之间不受干扰。——马路上两个汽车各跑各的,互不影响
    2.动态性:指的是进程都有完整的生命周期,而且在进程的生命周期内,进程的状态时不断变化的,进程具有动态的地址空间
    3.交互性:指的是进程在执行过程中可能会与其他进程发生直接或间接地通信,如:进程同步、进程互斥
    4.独立性:指的是进程是一个相对万层的资源分配和调度的基本单位,各个进程的地址空间是相互独立的
    只有采用某些特定的通信机制才能实现进程间的通信

  • 从系统来看,进程是程序执行时相关资源的总称

  • 内核将所有的进程都放在双向循环链表中(进程链表),链表的每一项都是task_struct,称为进程控制块的机构,该结构包含一个进程相关的所有信息

    • task_struct结构图中最重要的两个域:

    state()——进程状态, pid()——进程标识符

    * state————进程状态:
      1. 运行状态:进程当前正在运行,或正在运行队列中等待调度
      2. 可中断的阻塞状态: 进程处于阻塞(睡眠)状态,正在等待被某些事件的发生或能够占用某些资源。(其实就是卡住了)
      在这种状态下,进程可以被信号中断,接收到信号或者被显示地唤醒呼叫之后,进程将变为运行状态。
      3. 不可中断的阻塞状态:	类似可中断的阻塞,只是不再处理信号,把信号传递到该状态下的进程并不能改变它的状态。
      只有在它所等待的事件发生,进程才能显示的被唤醒呼叫唤醒。
      4. 暂停状态: 进程的执行被暂停,当进程收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信号就会被暂停。
      5. 僵死状态: 子进程运行结束,父进程未退出,并且没有使用wait函数族等系统调用来回收子进程的退出状态。
      		 僵尸进程与孤儿进程:僵尸进程由父进程回收资源,孤儿进程由init进程回收资源。
      6. 消亡状态: 这是进程的最终状态,父进程调用wait函数族回收后,子进程彻底由系统删除,不可见。
    
    • pid()————进程标识符:

      • Linux内核通过唯一的进程标识符Pid来标识每个进程。系统中可以创建的进程数目有限。

      • 最大进程数: ulimit -u,查看最大软限制进程数 本机16007

      • cat /proc/sys/kernel/pid_max#查系统支持的最大进程数,一般会很大,相当于理论值,本机32768

      • 线程的stack所占用的内存,可用命令ulimits -s查看,一般是8192KB(8M))。

      • cat /proc/sys/kernel/threads-max #查系统支持的最大线程数,一般会很大,相当于理论值,本机32015

      • 因此理论上讲,用户空间大小/栈大小=最大线程数。3072M/8M=384

    • 进程的创建、执行和终止

      • 进程的创建和执行
        Linux中进程的创建分为两部分:fork() 和 exec函数族

        • fork()通过复制当前进程创建一个子进程,exec()函数族负责读取可执行文件并将其载入地址空间运行。

        • fork()创建的子进程与父进程的区别仅仅在不同的 PID、PPID(父进程号 )、以及某些资源和统计量。
          fork()函数返回两个值,父进程返回子进程的进程号,子进程返回0,因此可以利用返回值区别到底是父进程还是子进程。
          (原因:子进程是父进程的复制品,继承了父进程的绝大数信息,子进程独有的只有自己的进程号、资源使用和计时器)

        • Linux在fork()创建进程时,一开始并没有给子进程分配资源,而是需要在子进程写入数据时候才分配
          在此之前只是以只读的方式共享父进程的资源,这样可以使Linux拥有更快的执行力。

      • wait() 和 waitpid():(阻塞与非阻塞)

        • wait()函数用于使父进程阻塞(调用wait()的进程为父进程),直到一个子进程结束或该父进程接收特定指令为止。
          如果该进程没有子进程,或者他的子进程已经结束,wait()立刻返回 -1;————————阻塞
        • waitpid()函数作用于wait()一样,但并不一定等待第一个终止的子进程。——————————非阻塞
      • 进程的终止

        • 进程终止,系统必须保证进程所占用的资源回收,并通知父进程
          Linux首先把终止的进程设置为僵死状态(子进程结束、父进程未退出、且未回收子进程资源)
          此时子进程无法执行,只能为父进程提供信息,父进程调用wait函数族回收子进程的退出状态,子进程的资源被释放

        • exit()和_exit():

        • 这个两个函数都是用来终止进程的,当程序执行到 exit() 和 _exit()时,进程会无条件地停止剩下的所有操作,清除数据结构,终止运行

        • _exit()函数的作用:

          • 是直接使进程停止运行,清除器使用的内存空间,清除其在内核中各种数据结构;
        • exit()函数则是在在此基础上,执行退出前加了很多工序。
          exit()和_exit()最大的区别在于exit()函数在终止进程前要检查该进程打开了哪些文件,并将文件缓冲区的内容写会文件。
          对应每个打开的文件,在内存中都有一片缓冲区。
          若想保证数据的完整性,最好使用exit()函数

    • Linux守护进程

      • 守护进程也就是常说的Daemon进程,是Linux中的后台服务进程,常常在系统启动时开始执行,在系统关闭时终止。
        守护进程不收终端关闭的影响,不会因为用户、终端或者其他变化而受到影响。
    • 进程的内存结构

      • Linux系统采用虚拟内存管理技术,使得每个进程都有独立的地址空间。该地址空间大小为 4G,线性,利用这种虚拟地址不但更安全(用户无法直接访问物理内存),而且比实际物理内存更大

      • 进程空间分配:

        • 4G的进程地址空间被分成 2 部分:
          • 1.用户空间(0-3GB)
          • 2.内核空间(3-4GB)
        • 通常情况下用户进程只能访问用户空间的虚拟地址,只有用户进程使用系统调用时才可以访问内核空间
          用户空间随着进程的变化而变化,内核空间是固定的。每个进程的用户空间是完全独立、互不干扰的。
      • 用户空间包括以下几个功能区域:

      1. 只读段————————包含程序代码和只读数据
      2. 数据段————————存放全局变量和静态变量
      3. 栈————————————存放函数的参数值、局部变量的值、返回地址等
      4. 堆————————————存放动态分配的数据,一般由程序员动态分配和释放。malloc();
      5. 共享库的内存映射区域,这是Linux动态连接器和其他共享代码的映射区域。

线程

  • 进程是系统中程序执行和资源分配的基本单位。线程是轻量级的进程。
    一个进程可以拥有多个线程,其中每个线程共享该进程所有的资源
    线程指的是共享相同地址空间的多个任务,线程共享同一片通信空间。
    线程间的通信用全局变量,主线程死了,子线程也会死,空间将被释放,与进程不一样。

协程

  • 协程的特点在于是一个线程中异步执行不同处理

    • 第一大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
    • 第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
  • 因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

进程、线程、协程总结

  1. 进程有自己独立的堆和栈,都不共享,是操作系统调度的基本单位
  2. 线程有自己独立的栈和共享的堆,是cpu调度的基本单位
  3. 协程有自己独立的栈和共享的堆,由程序员在代码里显示调度。

进程间通信

  • Linux中使用较多的进程间通信方式:

    1. 无名管道(pipe)与有名管道(fifo):

      • 无名管道可用于具有亲缘关系的进程间通信(fork()建立的父子间进程通信)
        有名管道除了具有和无名管道相似的功能外,它还允许 无亲缘关系的进程进行通信
        管道类似水管,存在两端,且,管道只存在于内存中,因此管道也是内存。
      • 无名管道(pipe):
        1. 只能用于具有亲缘关系的进程间通信(父子进程、兄弟进程)
        2. 是单工模式(左——>右,右——>左),具有固定的读端和写端,要么进程只能读管道,要么进程智能写管道。
        3. 管道是特殊的文件,具有文件描述符,对于它的读写可以使用read()、write()等函数,但它不属于任何文件系统。
        4. 只存在于内存中,所以必须通过继承的方式制定另一个管道。
      • 无名管道的创建、关闭
        1. 无名管道是基于文件描述符的通信方式,当创建管道时,会创建两个文件描述符,fd[0]固定读,fd[1]固定于写管道
        2. 管道的关闭只需要用close()关闭两个文件描述符就可。
        3. 用pipe()创建的管道处于同一进程,但管道属于进程间通信,因此还需要fork()一个进程,该进程会继承父进程的管道当父进程和子进程都有管道后,无名管道是单工模式,因此需要关闭各自一个不用的读/写端描述符。当关闭后就自动连接了
      • 有名管道(fifo):
        1. 可以使互不相关的两个进程间实现彼此通信
        2. 拥有实体,在文件系统中可见。建立了管道后,两个进程就可以把它当做普通文件进行读写操作。
        3. 有名管道遵从先进先出的原则,有名管道的读从开始出返回数据,写是将数据追加到末尾,且不支持lseek()定位
        4. 有名管道实际上只起标记作用,无名管道是介质。
      • 有名管道的创建、关闭
        1. 有名管道的创建可用mkfifo()函数,也可以在命令行使用“mknod <有名管道名> ”来创建
        2. 创建成功后便可与普通文件一样使用open()、write()、read()函数,需要注意读管道用O_RDONLY,写用O_WRONLY
        3. 对于读进程:当FIFO中没有数据,读端进程一直阻塞有数据,或者FIFO写端被关闭
        4. 对于写进程:只要FIFO中有空间,数据就可以被写入,若空间不足,写进程阻塞,直到写入。
      • 注意:
      1. 只有读端存在,向管道写入数据在有意义。
      2. 向管道写入数据,如果管道缓冲区已满,写操作将阻塞,只要缓冲区有空间,进程将试图向管道写数据
      3. 父子进程在运行时,并不保证先后次序
    2. 信号(signal):

      • 信号是在软件层次上对中断机制的一种模拟,用于通知进程某时间的发生。信号是在软件层上对中断机制的一种模拟;是进程间通信机制中唯一的异步通信机制;
      1. 信号是异步的,一个进程不必通过任何操作来等待信号的到达。没有严格的先后顺序,上一次的结果可以等下次来看

      2. 信号事件的产生由硬件来源(如按下键盘)和软件来源,常用的信号相关参数:kill()、raise()、alarm()、setitimer()、sigqueue()

      3. 进程可以以三种方式来响应一个信号:

        • 忽略信号
          不对信号做任何处理:SIGKILL 和 SIGSTOP 这两个信号不能被忽略,也不能自定义处理

        • 捕捉信号:
          定义信号捕捉函数,当信号发生时,执行相应的处理函数(想一想Qt中的信号和槽)

        • 执行默认操作:
          linux对每种信号都规定了默认操作

      4. 信号相关的函数,包括信号的发送与设置:

        • 信号的发送:
          1. kill():可以发送信号给进程或进程组,不止可以终止进程,也可以发送其他信号(向指定的进程发送信号)
          2. raise():只允许进程向自己本身发送信号。
            区别: kill(pid_t pid, int sig) raise(int sig) sig为信号类型,比如:SIGKILL,SIGSTOP
        • 信号的设置:
          1. signal(): 捕捉信号类型和进行指定信号的处理函数。不支持信号传递、主要用于前32种非实时信号的处理
            void (* signal( int signum, void( * handler)(int)))(int): sugnum为要关联的某个信号, 内void()关联函数
            signal 只是关联函数,并不等待。
          2. sigaction(): 检查或修改与指定信号相关联的处理动作(可同时操作)
            int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
          • signum:指出要捕捉的信号类型,
          • act: 新的信号处理方式, oldact: 输出先前信号的处理方式
    3. 消息队列(message queue):

    • 消息队列是消息的链接表,克服了信号和管道中信息量有限的缺点消息队列就是一些消息的列表,用户可以在消息队列中添加消息和读取消息。这些消息存在于内核,由队列ID标识。
      消息队列的实现:
      1. 创建和打开消息队列——————msgget();消息队列数量会受到系统限制
        msgget( key_t key, int msgflg)
        key:键值,多个进程通过key访问同一个个消息队列, msgflg: 权限标志位: IPC_CREAT | 0666

      2. 添加消息————————————————msgsnd();将消息添加到已打开的的消息队列末位
        msgsnd( int msqid, const void * msgp, size_t msgsz, int msgflg)

         msqid:消息队列的ID; 
         msgp: 指向消息队列的指针,此指针含有消息类型long mtype 和消息正文 char mtext[1];
         msgsz: 消息正文的字节数; 
         msgflg: IPC_NOWAIT 若消息无法发出则函数立即返回0:
         msgsnd调用被阻塞至发送成功为止
        
      3. 读取消息————————————————msgrcv();把消息从消息队列中取走,可以取走指定的某一消息

      4. 控制消息队列————————————msgctl();可以完成多项功能。

    1. 共享内存(shared memory):
    • 最有效的进程间通信方式。使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程对共享内存中数据的更新,在Linux中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,页表负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。

    • 共享内存不提供任何的同步与互斥关系,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取,所以我们通常需要用其他的机制来同步对共享内存的访问,例如信号量。

    • 共享内存的具体位置:
      *进程间需要共享的数据被放在一个叫做 IPC 共享内存区域的地方,所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。

    • 共享内存需要依靠某种同步机制:互斥锁、信号量等

    • 共享内存实现:

      1. 创建:int shmget( key_t key, int size, int shmflg);

        • key:键值,多个进程通过key访问同一个共享内存
        • size:共享内存的大小,单位:字节
        • shmflg:权限,可用同open()的权限位,也可以是八进制表示法,常用IPC_CREAT|0666表示
      2. 映射共享内存:将这段创建的贡献内存映射到具体的进程空间中。 void shmat( int shmid, const void * shmaddr, int shmflg)

        • shmid: 要映射的共享内存区域标识符
        • shmaddr: 将共享内存映射到指定地址(为0则表示系统自动分配地址并将该段共享内存映射到调用进程的地址空间)
        • shmaddr: 要指定的映射地址, 默认为NULL
        • shmflg: 权限, SHM_RDONLY,共享内存只读; 默认0,共享内存可读写
        • 返回值: 1.成功: 被映射的段地址 2.出错:-1
      3. 撤销映射

      4. 删除共享内存:int shmdt( const void * shmaddr)

        • shmaddr: 被映射的共享内存段地址
        • 返回值: 1.成功:0 2.出错:-1
      • 总结:
        1. 用shmget()申请一共享内存区间
        2. 将创建好的共享内存映射到用户空间(系统调用)
        3. 撤销映射
        4. 删除共享内存
    1. 信号量(semaphore):

      • 主要作为进程间以及统一进程的不同线程之间的同步和互斥手段
        信号量是用来解决进程\线程之间同步与互斥问题的一种机制,包括一个称为信号量的变量和
        在该信号量下等待资源的进程等待队列,以及对信号量进行的两个原子操作( P V 操作)

      • 信号量(Semaphore)由一个值和一个指针组成,指针指向等待该信号量的进程。
        信号量的值表示相应资源的使用情况。信号量S≥0时,S表示可用资源的数量

      • 任务间的同步、互斥关系存在的根源在于临界资源。

      • 临界资源: 在同一时刻只允许有限个(常为 1 )任务可以访问(读)或修改(写)的资源,包括硬件资源和软件资源
        访问临界资源的代码被称为临界区。

      • 信号量值指的是当前可用的资源的数量,若 = 0,则意味着当前没有可用的资源

      • P V 操作:( P 分配资源,V释放资源)

        • P: 如果有可用资源,信号量值 > 0,则占用一个资源(信号量 - 1),如果没有,则阻塞
        • V: 如果在该信号量的等待队列中有任务在等待资源,则唤醒一个阻塞任务,若没有任务等待它,则释放一个资源。
        • INIT_VAL(S)————对信号量S进行初始化, P(S) 进行P操作,V(S)进行V操作
      • 最简单的信号量只有 0 1两种值, 这种信号量被称为二值信号量。

      • 信号量编程:

        1. 创建信号量或获得系统已存在的信号量
          调用semget()函数。不同进程通过使用同一个信号量键值来获得同一个信号量
        2. 初始化信号量
          使用semctl()函数的SETVAL操作,常将二维信号量初始化为1
        3. 信号量的 P V操作
          调用semop()函数,进程间同步与互斥的核心操作
        4. 如果不需要信号量,则从系统删除
          调用semclt()函数的IPC_RMID操作。已经被删除的不能再次删除。
    2. 套接字(socket):
      它可用于网络中不同主机间的进程间通信。

      TCP

      Server(绑定自己IP地址): socket,bind,listen,accept(read write)

      Client(connectServerIP):socket,connect,(read write)

      UDP

      通常,UDP Client程序不和Server程序建立连接,而是直接使用sendto()来发送数据。同样,UDP Server程序不需要允许Client程序的连接,而是直接使用recvfrom()来等待直到接收到Client程序发送来的数据。

      Server:socket,bind(recvfrom)

      Client:socket,sendto
      注意:网络就是一种特殊的管道,读和写都可判断网络中对方走没走,读走了管道会破裂,写走了管道会阻塞或读出0;

示例代码

管道 (半双工 单向, 传输数据快,)

建立两个管道,分别一个读 一个写

  • Pipe 匿名管道 父子进程间通信

  • Mkfifo 管道文件 任意进程间通信

Pipe


#include<stdio.h>

#include<unistd.h>

voidfun(char *buf){
      for(;*buf;buf++){
           if(*buf <= 'z' && *buf>= 'a')

                 *buf -= 32;

      }

}

int main(intargc,char **argv)

{
      pid_t pid;

      int fds[4];

      int ret;

      ret = pipe(fds);

      if(ret)

           goto err_pipe;

      ret = pipe(fds+2);

      if(ret)

           goto err_pipe;

      //fds[0]:读 fds[1]:写

      //fds[2]:读 fds[3]:写

      pid = fork();

      if(pid > 0){//父进程

           close(fds[0]);

           close(fds[3]);

           char buf[100];

           while(1){
                 gets(buf);

                 write(fds[1],buf,sizeof(buf));

                 read(fds[2],buf,sizeof(buf));

                 printf("pid[%d]:%s\n",getpid(),buf);

           }

           wait(NULL);

      }

      if(pid == 0){//子进程

           close(fds[1]);

           close(fds[2]);

           char buf[100];

           while(1){
                 read(fds[0],buf,sizeof(buf));

                 fun(buf);

                 write(fds[3],buf,sizeof(buf));

           }

      }

      return 0;

err_pipe:

      perror("pipe:");

      return 1;

}

Mkfifo


#include<stdio.h>

#include<fcntl.h>

void fun(char*buf){
      for(;*buf;buf++){
           if(*buf <= 'z' && *buf>= 'a')

                 *buf -= 32;

      }

}

int main(intargc,char **argv)

{
      int fdr,fdw,ret;

      char buf[12];

      mkfifo("fifo1_2",0666);

      mkfifo("fifo2_1",0666);

      fdr = open("fifo1_2",O_RDONLY);

      fdw = open("fifo2_1",O_WRONLY);

      while(1){
      ret = read(fdr,buf,sizeof(buf));

      if(ret <= 0)break;

      printf("P2:%s\n",buf);

      fun(buf);

      write(fdw,buf,sizeof(buf));

      }

      close(fdr);

      close(fdw);

      return 0;

}

#include<stdio.h>

#include<fcntl.h>

int main(intargc,char **argv)

{
      int fdr,fdw,ret;

      char buf[12];

      mkfifo("fifo1_2",0666);

      mkfifo("fifo2_1",0666);

      fdw = open("fifo1_2",O_WRONLY);

      fdr = open("fifo2_1",O_RDONLY);

      while(1){
           gets(buf);

      write(fdw,buf,sizeof(buf));

      read(fdr,buf,sizeof(buf));

      printf("p1:%s\n",buf);

      }

      close(fdr);

      close(fdw);

      return 0;

}

共享内存

共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。

mmap系统调用是的是的进程间通过映射同一个普通文件实现共享内存.普通文件被映射到进程地址空间后,进程可以向像访问普通内存一样对文件进行访问,不必再调用read,write等操作.与mmap系统调用配合使用的系统调用还有munmap,msync等.

注:实际上,mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存方式读写文件。

mmap/munmap函数声明如下:


#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags,

                  int fd, off_t offset);

int munmap(void *addr, size_t length);

  • addr:如果不为NULL,内核会在此地址创建映射;否则,内核会选择一个合适的虚拟地址。

  • length:表示映射到进程地址空间的大小。

  • prot:内存区域的读/写/执行属性。

  • flags:内存映射的属性,共享、私有、匿名、文件等。

  • fd:表示这是一个文件映射,fd是打开文件的句柄。

  • zoffset:在文件映射时,表示相对文件头的偏移量;返回的地址是偏移量对应的虚拟地址。

Mmap.c


#include <stdio.h>

#include <sys/mman.h>

#include <fcntl.h>

//mytt str1 str2 filename

int main(int argc,char **argv)

{
    int fd;

    int size;

    char *ptr;

    fd =open("a",O_RDWR);

    /*

    fstat(fd,&info);

    size = info.st_size;

    */

    size = lseek(fd,0,SEEK_END);

    ptr =mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

    if(ptr == MAP_FAILED){
       perror("mmap:");

       return 1;

    }

    ptr[0] = 'z';

    munmap(ptr,size);

    close(fd);

    return 0;

}

父子进程通过匿名映射实现共享内存

#include <sys/mman.h>

#include <sys/types.h>

#include <fcntl.h>

#include <unistd.h>

typedef struct{
 char name[4];

 int  age;

}people;

main(int argc, char** argv)

{
 int i;

 people *p_map;

 char temp;

 p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,

      MAP_SHARED|MAP_ANONYMOUS,-1,0);

 if(fork() == 0)

  {
   sleep(2);

   for(i = 0;i<5;i++)

     printf("child read: the %d people's age is%d\n",i+1,(*(p_map+i)).age);

     (*p_map).age = 100;

     munmap(p_map,sizeof(people)*10); //实际上,进程终止时,会自动解除映射。

     exit();

  }

 temp = 'a';

 for(i = 0;i<5;i++)

  {
   temp += 1;

   memcpy((*(p_map+i)).name, &temp,2);

   (*(p_map+i)).age=20+i;

  }

 sleep(5);

 printf( "parent read: the first people,s age is%d\n",(*p_map).age );

 printf("umap\n");

 munmap( p_map,sizeof(people)*10 );

 printf( "umap ok\n" );

}

共享内存 shm


/***** testwrite.c *******/

#include <sys/ipc.h>

#include <sys/shm.h>

#include <sys/types.h>

#include <unistd.h>

typedef struct{
         char name[4];

         int age;

} people;

main(int argc, char** argv)

{
         int shm_id,i;

         key_t key;

         char temp;

         people *p_map;

         char* name = "/dev/shm/myshm2";

         key = ftok(name,0);

         if(key==-1)

                 perror("ftok error");

         shm_id=shmget(key,4096,IPC_CREAT); 

         if(shm_id==-1)

         {
                 perror("shmget error");

                 return;

         }

         p_map=(people*)shmat(shm_id,NULL,0);

         temp='a';

         for(i = 0;i<10;i++)

         {
                 temp+=1;

                 memcpy((*(p_map+i)).name,&temp,1);

                 (*(p_map+i)).age=20+i;

         }

         if(shmdt(p_map)==-1)

                 perror(" detach error ");

}


 

/********** testread.c ************/

#include <sys/ipc.h>

#include <sys/shm.h>

#include <sys/types.h>

#include <unistd.h>

typedef struct{
         char name[4];

         int age;

} people;

main(int argc, char** argv)

{
         int shm_id,i;

         key_t key;

         people *p_map;

         char* name = "/dev/shm/myshm2";

         key = ftok(name,0);

         if(key == -1)

                 perror("ftok error");

         shm_id = shmget(key,4096,IPC_CREAT);        

         if(shm_id == -1)

         {
                 perror("shmget error");

                 return;

         }

         p_map = (people*)shmat(shm_id,NULL,0);

         for(i = 0;i<10;i++)

         {
         printf( "name:%s\n",(*(p_map+i)).name );

         printf( "age %d\n",(*(p_map+i)).age );

         }

         if(shmdt(p_map) == -1)

                 perror(" detach error ");

}

  • 1、mmap保存到实际硬盘,实际存储并没有反映到主存上。优点:储存量可以很大(多于主存)
  • 2、shm保存到物理存储器(主存),实际的储存量直接反映到主存上。优点,进程间访问速度(读写)比磁盘要快;缺点,储存量不能非常大(多于主存)

使用上看:如果分配的存储量不大,那么使用shm;如果存储量大,那么使用mmap。

消息队列:常用兼容性好


#ifndefPROTO_H

#definePROTO_H

enum {P1 =1, P2};

structmsgbuf{
      long type;

      char buf[100];

};

#endif

#include<stdio.h>

#include<string.h>

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/msg.h>

#include"proto.h"

int main(intargc,char **argv)

{
    key_t key;

    int msgqid;

    key = 0x12345678;

    struct msgbuf msg;

    //key = ftok("/etc/passwd",10);

    msgqid = msgget(key,IPC_CREAT|0666);

    while(1){
    msg.type = P1;   // > 0

    gets(msg.buf);

    msgsnd(msgqid,&msg,sizeof(msg)-sizeof(long),0);

    msgrcv(msgqid,&msg,sizeof(msg)-sizeof(long),P2,0);

    printf("snd:buf=%s\n",msg.buf);

    }

    //msgctl(msgqid,IPC_RMID,NULL);

}

#include<stdio.h>

#include<string.h>

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/msg.h>

#include"proto.h"

void fun(char*buf){
    for(;*buf;buf++){
       if(*buf <= 'z' && *buf >='a')

           *buf -= 32;

    }

}

int main(intargc,char **argv)

{
    key_t key;

    int msgqid;

    key = 0x12345678;

    struct msgbuf msg;

    msgqid = msgget(key,IPC_CREAT|0666);

    printf("msgqid = %d\n",msgqid);

    //msgrcv(msgqid,&msg,sizeof(msg)-sizeof(long),0,0);

    while(1){
    msgrcv(msgqid,&msg,sizeof(msg)-sizeof(long),P1,0);

    printf("msgrcv:%s\n",msg.buf);

    fun(msg.buf);

    msg.type = P2;

    msgsnd(msgqid,&msg,sizeof(msg)-sizeof(long),0);

    }

    //msgctl(msgqid,IPC_RMID,NULL);

}

信号量 :协调进程,线程同步


#include <stdio.h>

#include <string.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <sys/sem.h>

int main(int argc,char **argv)

{
       key_tkey;

       charbuf[100];

       intshmid;

       char*p;

       key= 0x1234;

       shmid= shmget(key,1024,IPC_CREAT|0666);

       p= shmat(shmid,NULL,0);

       if((void*)p < NULL){
              perror("shmat:");

              return1;

       }

       key= 0x2234;

       intsemid;

       structsembuf sem;

       semid= semget(key,2,IPC_CREAT|0666);

       semctl(semid,0,SETVAL,0);

       semctl(semid,1,SETVAL,0);

       while(1){
              printf("enter:");

              gets(buf);

              strcpy(p,buf);

              sem.sem_num= 1;

              sem.sem_op= +1;

              sem.sem_flg= 0;

              semop(semid,&sem,1);

              sem.sem_num= 0;

              sem.sem_op= -1;

              sem.sem_flg= 0;

              semop(semid,&sem,1);

              strcpy(buf,p);

              printf("p1buf:%s\n",buf);

       }

}

#include <stdio.h>

#include <string.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <sys/sem.h>

void fun(char *buf){
       for(;*buf;buf++){
              if(*buf<= 'z' && *buf >= 'a')

                     *buf-= 32;

       }

}

int main(int argc,char **argv)

{
       charbuf[100] = "";

       key_tkey;

       intshmid;

       key= 0x1234;

       shmid= shmget(key,1024,IPC_CREAT|0666);

       char*p;

       p= shmat(shmid,NULL,0);

       if((void*)p < NULL){
              perror("shmat:");

              return1;

       }

       key= 0x2234;

       intsemid;

       structsembuf sem;

       semid= semget(key,2,IPC_CREAT|0666);

       semctl(semid,0,SETVAL,0);

       semctl(semid,1,SETVAL,0);

       while(1){
              sem.sem_num= 1;

              sem.sem_op= -1;

              sem.sem_flg= 0;

              semop(semid,&sem,1);

              strcpy(buf,p);

              printf("buf:%s\n",buf);

              fun(buf);

              strcpy(p,buf);

              sem.sem_num= 0;

              sem.sem_op= +1;

              sem.sem_flg= 0;

              semop(semid,&sem,1);

       }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值