Linux进程间通信(IPC)
进程间通信: 不同进程之间传数据、
最近学习了Linux进程,也对exec族函数、system函数、popen函数等有了一定的了解,但是他们都不能更好的相互传数据。现在学习一下进程间的通信(IPC)。 进程间通信: 不同进程之间传数据、交换信息。 方式: 管道(无名管道和命名管道),消息队列,信号量,共享内存,socket,steams等。其中socket 和 steams 支持不同主机上的两个进程通信。 一.管道(无名管道): 通常指无名管道,是Unix 系统 IPC最古老的形式。 头文件:
原型:
特点: 1. 半双工,只能要么接收数据,要么传输数据。一个读,一个写。 2. 只能用于具有亲缘关系的进程通信,比如 父子进程。 3. 可以看成是特殊文件,它读写可以使用read write等函数,但它不是普通文件和系统文件,它是只存在内存中的。 我们代码实现父进程写入hello word 给 子进程。(无名管道) **1.**开管道 2. 关闭子进程的写管道 3. 开始读管道接收父进程传的信息。 4. 关闭父进程的读取通道 5. 写hello word 给子进程。 6. 子进程读取数据hello word。 7. 输出读取的内容 hello word 代码: 运行过程及结果: 二.FIFO(命名管道): **命名管道,是一种文件类型。 头文件:
原型:
**pathname:**文件名 **mode:**权限 比如 0600 就是可读可写。 特点: 可以在无关进程中交互数据,和无名管道不一样。 有路径名,是以特殊的文件形式存放在文件系统中。 创建命名管道: 结果: 注意:如果已经存在这个文件,创建会失败的。 当我们打开这个管道的时候,如果没有指定**O_NONBLOCK(**非阻塞标志), 如果你是只读的方式打开,它会一直阻塞到有另一个进程为写而打开此管道。如果你是只写的方式打开,它会一直阻塞到有另一个进程为读而打开此管道。 我们用代码实现一个写,写hello,word 给另一个进程。 让另一个读取并且输出。 代码: 做读取的代码 写进程代码: 当我们去运行读取的程序,会一直阻塞,等待写的程序运行。 读取方结果: 写方结果: 三.消息队列: 消息队列是消息的链接表,是存在内核中的,一个消息队列是由一个标识符(ID号)来标识。 头文件:
原型:
功能: 用于创建一个新的或打开一个已经存在的消息队列,此消息队列与key相对应。 创建成功返回队列的ID,失败返回-1 key: 函数ftok的返回值(ID号)或IPC_PRIVATE。 msgflag: IPC_CREAT:创建新的消息队列。 IPC_EXCL:与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。 IPC_NOWAIT:读写消息队列要求无法满足时,不阻塞。返回值: 调用成功返回队列标识符,否则返回-1. 添加消息:
成功返回0,失败返回-1。 读取消息:
成功返回数据的长度,失败返回-1. 参数: **msqid:**消息队列的识别码(ID)。 **msgp:**指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构,形态如下。
sgsz:消息的大小。 msgtyp: 消息类型 msgflg:这个参数依然是控制函数行为的标志,取值可以是:0,表示忽略。 控制消息队列:
成功返回0,失败返回-1。 参数: msqid:ID cmd: IPC_RMID:从系统内核中移走消息队列。 IPC_STAT:读取消息队列的数据结构msqid_ds,并将其存储在b u f指定的地址中。 IPC_SET: 设置消息队列的数据结构msqid_ds中的ipc_perm元素的值。这个值取自buf参数。 buf: 一般写NULL 特点: 1. 面向记录,其中消息具有特定的格式以及特定的优先级。 2. 是对立发送和接收进程,进程结束了,消息队列的内容不会删除,是由内核的机制决定的。 3. 可以实现消息的随机查询,不一定要先进先出的次序读取,常用的是按消息的类型来读取。 下面就用代码实现一下消息互传。 程序1: 程序2: 程序1结果: 程序2结果: 注意:msgget 第一个参数可以直接自定义0x… 也可通过ftok函数获取,要用ftok函数就把代码中的key替换代码中的0x123.
fname: 就是你指定的文件名(已经存在的文件名),一般使用当前目录,如:key = ftok(".", 1); 这样就是将fname设为当前目录。 id: 是子序号。虽然是int类型,但是只使用8bits(1-255)。这里的第二个参数也像代码那里用字母也行。 四、共享内存: 共享内存比消息队列更好一点,共享内存是有一个物理地址,进程都通过共享内存可以获取数据,写入数据。 思路: 1创建内存(shmget) 2.映射 (shmat) 3.数据 (strcpy拷贝) 4.释放内存(shmdt) 5.打掉共享内存(shmctl) 头文件:#include #include 1.创建获取获取共享内存
成功返回共享内存ID,失败返回-1。 参数: key: 共享内存名字(ID),确保不同进程看到同一份IPC资源;其中key值的又由ftok函数创建:key_t ftok(const char *pathname, int proj_id); 上面消息队列最后面有讲。 size: 共享内存的大小,共享内存的创建是以页为单位的,页的大小是4096k(4kb); shmflag: 有两个选项,分别是:IPC_CREAT和IPC_EXCL,这两个选项同时使用,会创建一个全新的共享内存,如果单独使用IPC_CREAT,创建成功,但是单独使用IPC_EXCL没有意义。 2.映射:连接共享内存到当前进程的地址空间
成功返回共享内存的指针,失败返回-1. 参数: shmid: 标识共性内存的ID sdmaddr: 指定链接的地址,一般默认为NULL或者0 shmflag: 它的两个可能取值是SHM_RND和SHM_RDONLY,但是这个参数,我们一般默认设置为0。 3.断开与共享内存的连接
成功返回0,成功返回-1. 参数: shmaddr: 由shmat返回的指针 注意:将共性内存与当前进程脱离不等于删除共享内存函数,它只是一个去链接的过程。 4.控制或者删除共享内存的相关信息
成功返回0,失败返回-1。 参数: shmid: 是shmget返回的共享内存标识码(ID) cmd: 将要采取的命令,有三个选项: IPC_STAT(把shnid_ds数据结构中的数据设置为共性内存的当前关联值); IPC_SET(在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值); IPC_PRID(删除共享内存) buf:指向一个保存着共享内存的模式和访问权限的数据结构,通常设置为NULL 或者 0。 代码实现内存的A程序写入和B程序读写 A程序: B程序: A程序运行结果: B程序运行结果: 查看共享内存 ipcs -m 删除共享内存 ipcrm -m ID号 ** 五、信号(signal):** 头文件:#include 对于Linux,实际信号是软中断,许多重要的程序也需要处理信号,信号能为Linux提供一种处理异常的方法,比如Ctrl + C 中断 程序,会通过信号机制停止程序。 信号都有名字和编号,信号名都定义为正整数,具体信号名称可以通过kill -l 查看信号名字以及编号。信号是从1开始编号的,不存在0, kill对信号0有特殊意义。 9号(SIGKILL)杀死、终止进程 01 SIGHUP 挂起(hangup) 02 SIGINT 中断,当用户从键盘按c键或break键时 03 SIGQUIT 退出,当用户从键盘按quit键时 04 SIGILL 非法指令 05 SIGTRAP 跟踪陷阱(trace trap)unix进程通信,启动进程,跟踪代码的执行 06 SIGIOT IOT 指令 07 SIGEMT EMT 指令 08 SIGFPE 浮点运算溢出 09 SIGKILL 杀死、终止进程 10 SIGBUS 总线错误 11 SIGSEGV 段违例(segmentation violation),进程试图去访问其虚地址空间以外的位置 12 SIGSYS 系统调用中参数错,如系统调用号非法 13 SIGPIPE 向某个非读管道中写入数据 14 SIGALRM 闹钟。当某进程希望在某时间后接收信号时发此信号 15 SIGTERM 软件终止(software termination) 16 SIGUSR1 用户自定义信号1 17 SIGUSR2 用户自定义信号2 18 SIGCLD 某个子进程死 19 SIGPWR 电源故障 信号的处理: 3种:忽略、捕捉、默认动作。 **忽略:**不能忽略的两个信号:SIGKILL、 SIGSTOP。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景 **捕捉:**由内核来调用用户自定义的函数,来实现某种信号的处理。 **默认动作:**对于每个信号,系统都对应由默认的处理动作。具体的可以 man 7 signal 查看。 入门版(signal) 忽略信号(SIG_IGN): 结果: 毫无反应 捕捉信号: 结果: 恢复默认动作(SIG_DFL): 结果: 上面的都是直接通过键盘发指令的,我们也可以写个程序来发指令。 A程序: B程序: 这里可以用system代替kill 我们通过运行A程序,然后 ps -aux|grep signal 获取A程序的进程号,然后用B程序对A程序进行操作 进程号为7809 运行B程序对A程序发信号 A程序运行结果: 高级版(sigaction) sigaction 是一个系统调用,为什么会有高级版,我们的入门版虽然可以发出和接收到了信号,但我们想发出信号的同时携带点数据,这时候需要用到高级版 sigaction。 头文件: #include 原型:
参数: signum: 注册信号的编号。 act: 是一个结构体,如果不为空说明需要对该信号有新的配置。 oldact: 如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复。
结构体中void (*sa_sigaction)(int, siginfo_t *, void *); siginfo_t有下列的内容
sigval_t si_value这个成员中有保存了发送过来的信息;在si_int或者si_ptr成员中也保存了对应的数据。si_int (整型)数据,sigval_t si_value 也是,value是一个结构体,int sival_int; void sival_ptr; 可以发一个整数* 也可以是字符串 信号发送函数: sigqueue 头文件:
原型:
参数: **pid:**进程号 **sig:**注册信号的编号 value就是下面的结构体
sigqueue 函数只能把信号发送给单个进程,可以使用 value 参数向信号处理程序传递整数值或者指针值 我们代码实现一下发送和接收,发送一个整型数1000和pid号给对方。 首先是接收端代码: 发送端: 发送端运行: 接收端运行: 六、信号量: 信号量与上面的IPC结构不同,上面的IPC是可以发数据的,而信号量是不能发数据,它是一个计数器,信号量用于实现进程间的互斥与同步,而不是用于储存进程间通信数据。 Linux下的信号量函数都是在通用的信号量数组上进行操作,而不是 一个单一的二值信号量上进程操作 二值信号量:信号量只能取0或者1的变量 特点: 用于进程同步,如果是要进程间传递数据,需要和共享内存结合。 信号量是基于操作系统的PV操作,P(拿锁)V (放回锁)。 每次PV操作不仅限于对信号量值加1或者减1,可以加减任意的正整数。 支持信号量组。 头文件:
1.创建或者获取一个信号量组,成功返回信号量集ID,失败返回-1
参数: key: 1.键值是IPC_PRIVATE,该值通常为0,意思就是创建一个仅能被进程进程给我的信号量。 2.键值不是IPC_PRIVATE,我们可以指定键值,例如1234;也可以一个**ftok()**函数来取得一个唯一的键值。 nsems: 表示初始化信号量的个数。比如我们要创建一个信号量,则该值为1.,创建2个就是2。 semflg: 信号量的创建方式或权限。 有IPC_CREAT(如果信号量不存在,则创建一个信号量,否则获取。)**,IPC_EXCL(只有信号量不存在的时候,新的信号量才建立,否则就产生错误。)。 2.对信号量组进行操作,改变信号量的值,成功返回0,失败返回-1.
参数: semid: 信号集的识别码,可通过semget获取。 sops: 指向存储信号操作结构的数组指针,信号操作结构的原型如下
nsops: 信号操作结构的数量,恒大于或等于1。 3.控制信号量的相关信息
参数: semnum: 操作第几个信号量 第一个是从0开始的。 cmd: IPC_STAT 读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。 IPC_SET 设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数 IPC_RMID 将信号量集从内存中删除。 GETALL 用于读取信号量集中的所有信号量的值。 GETNCNT 返回正在等待资源的进程数目。 GETPID 返回最后一个执行semop操作的进程的PID。 GETVAL 返回信号量集中的一个单个的信号量的值。 GETZCNT 返回正在等待完全空闲的资源的进程数目。 SETALL 设置信号量集中的所有的信号量的值。 SETVAL 设置信号量集中的一个单独的信号量的值。 第四个参数是一个联合体。
我们代码实现一下。让子进程先拿到钥匙 再放回,再让父进程拿钥匙,放回,销毁。 运行结果: (编辑:均轻资讯网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |