Linux进程通信

本文发布时间: 2019-Mar-22
Linux的进程操作方式主要有产生进程、终止进程,并且进程之间存在数据和控制的交互,即进程间通信和同步。进程的产生过程进程的产生有多种方式,其基本过程是一致的。(1)首先复制其父进程的环境配置。(2)在内核中建立进程结构。(3)将结构插入到进程列表,便于维护。(4)分配资源给此进程。(5)复制父进程的内存映射信息。(6)管理文件描述符和链接点。(7)通知父进程。• 进程的终止方式有5种方式使进程终止:从main返回。调用exit。调用_exit。调用abort。由一个信号终止。进程在终止的时候,系统会释放进程所拥有的资源,例如内存、文件符、内核结构等。• 进程之间的通信进程之间的通信有多种方式,其中管道、共享内存和消息队列是最常用的方式。管道:利用Linux内核在两个进程之间建立管道,在管道的一端只写,另一端只读。利用读写的方式在进程间传递数据。共享内存:将内存中的一段内存地址,在多个进程之间共享,多个进程利用获得的共享内存的地址来直接对地址进行操作。消息队列:在内核中建立一个链表,发送方按照一定的标识将数据发送到内核中,内核将其放入量表中,等待对方接收方请求,接收方发送请求,内核按照请求的标识,从内核中的链表中将其取下,传递给接收方。这个是一种异步操作,等到请求的时候,才发送数据。• 进程之间的同步多个进程之间需要协作完成任务时,经常发生任务之间的依赖现象,从而出现了进程的同步问题。Linux下进程的同步方式主要有消息队列、信号量等。信号量是一个共享的表示数量的值。用于多个进程之间操作或者共享资源的保护,它是进程之间同步的最主要方式。进程和线程的区别线程和进程是另一对有意义的概念,主要区别和联系如下:进程是操作系统进行资源分配的基本单位,进程拥有完整的虚拟空间。进行系统资源分配的时候,除了CPU资源之外,不会给线程分配独立的资源,线程所需要的资源需要共享。线程是进程的一部分多个线程之间像内存、变量等资源可以通过简单的办法共享,多进程则不同,进程间的共享方式有限。进程有进程控制表PCB,系统通过PCB对进程进行调度;线程有线程控制表TCB。进程号:每个进程初始化的时候,都会分配一个ID号,用次来标识进程,进程号是唯一的。linux可以通过getpid()和getppid()来获得当前进程和其父进程。#include <sys/types.h>#include <unistd.h>#include<stdio.h>int main(){ pid_t pid,ppid; /* 获得当前进程和其父进程的ID号 */ pid = getpid(); ppid = getppid(); printf('当前进程的ID号为:%d',pid); printf('当前进程的的父进程号ID号为:%d',ppid); return 0;}进程复制:fork()函数以父进程为蓝本复制一个进程,其ID号和父进程ID号不同。#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>int main(void){ pid_t pid; /* 分叉进程 */ pid = fork(); /* 判断是否执行成功 */ if(-1 == pid){ printf('进程创建失败!'); return -1; } else if(pid == 0){ /* 子进程中执行此段代码 */ printf('子进程,fork返回值:%d, ID:%d, 父进程ID:%d',pid, getpid(), getppid()); } else{ /* 父进程中执行此段代码 */ printf('父进程,fork返回值:%d, ID:%d, 父进程ID:%d',pid, getpid(), getppid()); } return 0;}system()函数调用shell的外部命令在当前进程中开始另一个进程。在使用fork()函数和system()函数的时候,系统中都会建立一个新的进程,执行调用者的操作,而原来的进程还会存在,直到用户显式地退出;#include<stdio.h>#include<unistd.h>#include<stdlib.h>int main(){ int ret; printf('系统分配的进程号是:%d',getpid()); ret = system('ping www.baidu.com -c 2'); printf('返回值为:%d',ret); return 0;}而exec()族的函数与之前的fork()和system()函数不同,exec()族函数会用新进程代替原有的进程,系统会从新的进程运行,新进程的PID值会与原来进程的PID值相同。#include<stdio.h>#include<unistd.h>int main(void){ char *args[]={'/bin/ls',NULL}; printf('系统分配的进程号是:%d',getpid()); if(execve('/bin/ls',args,NULL)<0) printf('创建进程出错!'); return 0;}注意:在Linux系统中,所有的进程都是有父子或者堂兄关系的,除了初始进程init,没有哪个进程与其他进程完全独立。进程间通信和同步半双工管道管道是一种把两个进程之间的标准输入和标准输出连接起来的机制。管道是一种历史悠久的进程间通信的办法,自UNIX操作系统诞生,管道就存在了。由于管道仅仅是将某个进程的输出和另一个进程的输入相连接的单向通信的办法,因此称其为“半双工”。在shell中管道用“|”表示。#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>int main(void){ int result = -1; /*创建管道结果*/ int fd[2]; /*文件描述符,字符个数*/ pid_t pid; /*PID值*/ /* 文件描述符1用于写,文件描述符0用于读 */ int *write_fd = &fd[1]; /*写文件描述符*/ int *read_fd = &fd[0]; /*读文件描述符*/ result = pipe(fd); /*建立管道*/ if( -1 == result) /*建立管道失败*/ { printf('建立管道失败'); /*打印信息*/ return -1; /*返回错误结果*/ } pid = fork(); /*分叉程序*/ if( -1 == pid) /*fork失败*/ { printf('fork 进程失败'); /*打印信息*/ return -1; /*返回错误结果*/ } if( 0 == pid) /*子进程*/ { close(*read_fd); /*关闭读端*/ } else /*父进程*/ { close(*write_fd); /*关闭写端*/ } return 0;}管道读写操作:#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>int main(void){ int result = -1; /*创建管道结果*/ int fd[2],nbytes; /*文件描述符,字符个数*/ pid_t pid; /*PID值*/ char string[] = '你好,管道'; char readbuffer[80]; /* 文件描述符1用于写,文件描述符0用于读 */ int *write_fd = &fd[1]; /*写文件描述符*/ int *read_fd = &fd[0]; /*读文件描述符*/ result = pipe(fd); /*建立管道*/ if( -1 == result) /*建立管道失败*/ { printf('建立管道失败'); /*打印信息*/ return -1; /*返回错误结果*/ } pid = fork(); /*分叉程序*/ if( -1 == pid) /*fork失败*/ { printf('fork 进程失败'); /*打印信息*/ return -1; /*返回错误结果*/ } if( 0 == pid) /*子进程*/ { close(*read_fd); /*关闭读端*/ result = write(*write_fd,string,strlen(string)); /*向管道端写入字符*/ return 0; } else /*父进程*/ { close(*write_fd); /*关闭写端*/ nbytes = read(*read_fd, readbuffer,sizeof(readbuffer)); /*从管道读取数值*/ printf('接收到%d个数据,内容为:”%s“',nbytes,readbuffer); /*打印结果*/ } return 0;}管道阻塞和管道操作的原子性当管道的写端没有关闭时,如果写请求的字节数目大于阈值PIPE_BUF,写操作的返回值是管道中目前的数据字节数,如果请求的字节数目不大于PIPE_BUF,则返回管道中现有数据字节数(此时,管道中数据量小于请求的数据量);或者返回请求的字节数(此时,管道中数据量不小于请求的数据量)。#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#define K 1024#define WRITELEN (128*K)int main(void){ int result = -1; /*创建管道结果*/ int fd[2],nbytes; /*文件描述符,字符个数*/ pid_t pid; /*PID值*/ char string[WRITELEN] = '你好,管道'; char readbuffer[10*K]; /*读缓冲区*/ /* 文件描述符1用于写,文件描述符0用于读 */ int *write_fd = &fd[1]; int *read_fd = &fd[0]; result = pipe(fd); /*建立管道*/ if( -1 == result) /*建立管道失败*/ { printf('建立管道失败'); /*打印信息*/ return -1; /*返回错误结果*/ } pid = fork(); /*分叉程序*/ if( -1 == pid) /*fork失败*/ { printf('fork 进程失败'); /*打印信息*/ return -1; /*返回错误结果*/ } if( 0 == pid) /*子进程*/ { int write_size = WRITELEN; /*写入的长度*/ result = 0; /*结果*/ close(*read_fd); /*关闭读端*/ while( write_size >= 0) /*如果没有将数据写入继续操作*/ { result = write(*write_fd,string,write_size); /*写入管道数据*/ if(result >0) /*写入成功*/ { write_size -= result; /*写入的长度*/ printf('写入%d个数据,剩余%d个数据',result,write_size); } else /*写入失败*/ { sleep(10); /*等待10s,读端将数据读出*/ } } return 0; } else /*父进程*/ { close(*write_fd); /*关闭写端*/ while(1) /*一直读取数据*/ { nbytes = read(*read_fd, readbuffer,sizeof(readbuffer)); /*读取数据*/ if(nbytes <= 0) /*读取失败*/ { printf('没有数据写入了');/*打印信息*/ break; /*退出循环*/ } printf('接收到%d个数据,内容为:”%s“',nbytes,readbuffer); } } return 0;}命名管道命名管道的工作方式与普通的管道非常相似,但也有一些明显的区别。在文件系统中命名管道是以设备特殊文件的形式存在的。不同的进程可以通过命名管道共享数据。在FIFO中,必须使用一个open()函数来显式地建立连接到管道的通道。一般来说FIFO总是处于阻塞状态。如果不希望在进行命名管道操作的时候发生阻塞,可以在open()调用中使用O_NONBLOCK标志,以关闭默认的阻塞动作。消息队列消息队列是内核地址空间中的内部链表,通过Linux内核在各个进程之间传递内容。消息顺序地发送到消息队列中,每个消息队列可以用IPC标识符唯一地进行标识。内核中的消息队列是通过IPC的标识符来区别的,不同的消息队列之间是相对独立的。每个消息队列中的消息,又构成一个独立的链表。1.消息缓冲区结构常用的结构是msgbuf结构。程序员可以以这个结构为模板定义自己的消息结构。struct msgbuf {long mtype;char mtext[1];};2.结构msgid_dsstruct msqid_ds {struct ipc_perm msg_perm;time_t msg_stime; /发送到队列的最后一个消息的时间戳/time_t msg_rtime; /从队列中获取的最后一个消息的时间戳/time_t msg_ctime; /对队列进行最后一次变动的时间戳/unsigned long __msg_cbytes; /在队列上所驻留的字节总数/msgqnum_t msg_qnum; /当前处于队列中的消息数目/msglen_t msg_qbytes; /队列中能容纳的字节的最大数目/pid_t msg_lspid; /发送最后一个消息进程的PID /pid_t msg_lrpid; /接收最后一个消息进程的PID /};3.结构ipc_perm内核把IPC对象的许可权限信息存放在ipc_perm类型的结构中。struct ipc_perm {key_t key; /函数msgget()使用的键值/uid_t uid; /用户的UID/gid_t gid; /用户的GID/uid_t cuid; /建立者的UID/gid_t cgid; /建立者的GID/unsigned short mode; /权限 /unsigned short seq; /序列号/};4.内核中的消息队列关系作为IPC的消息队列,其消息的传递是通过Linux内核来进行的。5.键值构建ftok()函数ftok()函数将路径名和项目的表示符转变为一个系统V的IPC键值。6.获得消息msgget()函数创建一个新的消息队列,或者访问一个现有的队列,可以使用函数msgget()7.发送消息msgsnd()函数一旦获得了队列标识符,用户就可以开始在该消息队列上执行相关操作了。为了向队列传递消息,用户可以使用msgsnd()函数8.接收消息msgrcv()函数当获得队列标识符后,用户就可以开始在该消息队列上执行消息队列的接收操作。msgrcv()函数用于接收队列标识符中的消息9.消息控制msgctl()函数为了在一个消息队列上执行控制操作,用户可以使用msgctl()函数。看下面一个例子:在建立消息队列后,打印其属性,并在每次发送和接收后均查看其属性,最后对消息队列进行修改。#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/msg.h>#include <unistd.h>#include <time.h>#include <sys/ipc.h>void msg_show_attr(int msg_id, struct msqid_ds msg_info) /*打印消息属性的函数*/{ int ret = -1; sleep(1); ret = msgctl(msg_id, IPC_STAT, &msg_info); /*获取消息*/ if( -1 == ret) { printf('获得消息信息失败'); /*获取消息失败,返回*/ return ; } printf(''); /*以下打印消息的信息*/ printf('现在队列中的字节数:%ld',msg_info.msg_cbytes); /*消息队列中的字节数*/ printf('队列中消息数:%d',(int)msg_info.msg_qnum); /*消息队列中的消息数*/ printf('队列中最大字节数:%d',(int)msg_info.msg_qbytes); /*消息队列中的最大字节数*/ printf('最后发送消息的进程pid:%d',msg_info.msg_lspid); /*最后发送消息的进程*/ printf('最后接收消息的进程pid:%d',msg_info.msg_lrpid); /*最后接收消息的进程*/ printf('最后发送消息的时间:%s',ctime(&(msg_info.msg_stime))); /*最后发送消息的时间*/ printf('最后接收消息的时间:%s',ctime(&(msg_info.msg_rtime))); /*最后接收消息的时间*/ printf('最后变化时间:%s',ctime(&(msg_info.msg_ctime))); /*消息的最后变化时间*/ printf('消息UID是:%d',msg_info.msg_perm.uid); /*消息的UID*/ printf('消息GID是:%d',msg_info.msg_perm.gid); /*消息的GID*/}int main(void){ int ret = -1; int msg_flags, msg_id; key_t key; struct msgmbuf{ /*消息的缓冲区结构*/ int mtype; char mtext[10]; }; struct msqid_ds msg_info; struct msgmbuf msg_mbuf; int msg_sflags,msg_rflags; char *msgpath = '/ipc/msg/'; /*消息key产生所用的路径*/ key = ftok(msgpath,'b'); /*产生key*/ if(key != -1) /*产生key成功*/ { printf('成功建立KEY'); } else /*产生key失败*/ { printf('建立KEY失败'); } msg_flags = IPC_CREAT|IPC_EXCL; /*消息的类型*/ msg_id = msgget(key, msg_flags|0x0666); /*建立消息*/ if( -1 == msg_id) { printf('消息建立失败'); return 0; } msg_show_attr(msg_id, msg_info); /*显示消息的属性*/ msg_sflags = IPC_NOWAIT; msg_mbuf.mtype = 10; memcpy(msg_mbuf.mtext,'测试消息',sizeof('测试消息')); /*复制字符串*/ ret = msgsnd(msg_id, &msg_mbuf, sizeof('测试消息'), msg_sflags); /*发送消息*/ if( -1 == ret) { printf('发送消息失败'); } msg_show_attr(msg_id, msg_info); /*显示消息属性*/ msg_rflags = IPC_NOWAIT|MSG_NOERROR; ret = msgrcv(msg_id, &msg_mbuf, 10,10,msg_rflags); /*接收消息*/ if( -1 == ret) { printf('接收消息失败'); } else { printf('接收消息成功,长度:%d',ret); } msg_show_attr(msg_id, msg_info); /*显示消息属性*/ msg_info.msg_perm.uid = 8; msg_info.msg_perm.gid = 8; msg_info.msg_qbytes = 12345; ret = msgctl(msg_id, IPC_SET, &msg_info); /*设置消息属性*/ if( -1 == ret) { printf('设置消息属性失败'); return 0; } msg_show_attr(msg_id, msg_info); /*显示消息属性*/ ret = msgctl(msg_id, IPC_RMID,NULL); /*删除消息队列*/ if(-1 == ret) { printf('删除消息失败'); return 0; } return 0;}信号量信号量是一种计数器,用来控制对多个进程共享的资源所进行的访问。它们常常被用做一个锁机制,在某个进程正在对特定资源进行操作时,信号量可以防止另一个进程去访问它。信号量数据结构是信号量程序设计中经常使用的数据结构union semun { /信号量操作的联合结构/int val; /整型变量/struct semid_ds buf; /*semid_ds结构指针/unsigned short array; /数组类型*/struct seminfo __buf; /信号量内部结构*/};新建信号量函数semget();信号量操作函数semop();控制信号量参数semctl();一个信号量的例子,代码先建立一个信号量,然后再对这个信号量进行P、V操作,并将信号量的值打印出来,最后销毁信号量。:#include <stdio.h>#include <sys/sem.h>#include <sys/ipc.h>typedef int sem_t;union semun { /*信号量操作的联合结构*/ int val; /*整型变量*/ struct semid_ds *buf; /*semid_ds结构指针*/ unsigned short *array; /*数组类型*/} arg; /*定义一个全局变量*/sem_t CreateSem(key_t key, int value) /*建立信号量,魔数key和信号量的初始值 value*/{ union semun sem; /*信号量结构变量*/ sem_t semid; /*信号量ID*/ sem.val = value; /*设置初始值*/ semid = semget(key,0,IPC_CREAT|0666); /*获得信号量的ID*/ if (-1 == semid) /*获得信号量ID失败*/ { printf('create semaphore error');/*打印信息*/ return -1; /*返回错误*/ } semctl(semid,0,SETVAL,sem); /*发送命令,建立value个初始值的信号量*/ return semid; /*返回建立的信号量*/}int Sem_P(sem_t semid) /*增加信号量*/{ struct sembuf sops={0,+1,IPC_NOWAIT}; /*建立信号量结构值*/ return (semop(semid,&sops,1)); /*发送命令*/}int Sem_V(sem_t semid) /*减小信号量值*/{ struct sembuf sops={0,-1,IPC_NOWAIT}; /*建立信号量结构值*/ return (semop(semid,&sops,1)); /*发送信号量操作方法*/}void SetvalueSem(sem_t semid, int value) /*设置信号量的值*/{ union semun sem; /*信号量操作的结构*/ sem.val = value; /*值初始化*/ semctl(semid,0,SETVAL,sem); /*设置信号量的值*/}int GetvalueSem(sem_t semid) /*获得信号量的值*/{ union semun sem; /*信号量操作的结构*/ return semctl(semid,0,GETVAL,sem); /*获得信号量的值*/}void DestroySem(sem_t semid) /*销毁信号量*/{ union semun sem; /*信号量操作的结构*/ sem.val = 0; /*信号量值的初始化*/ semctl(semid,0,IPC_RMID,sem); /*设置信号量*/}int main(void){ key_t key; /*信号量的键值*/ int semid; /*信号量的ID*/ char i; int value = 0; key = ftok('/ipc/sem','a'); /*建立信号量的键值*/ semid = CreateSem(key,100); /*建立信号量*/ for (i = 0;i <= 3;i++){ /*对信号量进行3次增减操作*/ Sem_P(semid); /*增加信号量*/ Sem_V(semid); /*减小信号量*/ } value = GetvalueSem(semid); /*获得信号量的值*/ printf('信号量值为:%d',value); /*打印结果*/ DestroySem(semid); /*销毁信号量*/ return 0;}共享内存共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,它是在多个进程之间对内存段进行映射的方式实现内存共享的。1.创建共享内存函数shmget()函数shmget()用于创建一个新的共享内存段,或者访问一个现有的共享内存段,它与消息队列以及信号量集合对应的函数十分相似2.获得共享内存地址函数shmat()函数shmat()用来获取共享内存的地址,获取共享内存成功后,可以像使用通用内存一样对其进行读写操作。3.删除共享内存函数shmdt()函数shmdt()用于删除一段共享内存。4.共享内存控制函数shmctl()共享内存的控制函数shmctl()的使用类似ioctl()的方式对共享内存进行操作:向共享内存的句柄发送命令来完成某种功能。5.一个共享内存的例子在父进程和子进程之间利用共享内存进行通信,父进程向共享内存中写入数据,子进程读出数据。两个进程之间的控制采用了信号量的方法,父进程写入数据成功后,信号量加1,子进程在访问信号量之前先等待信号。#include <stdio.h>#include <sys/shm.h>#include <sys/ipc.h>#include <string.h>static char msg[]='你好,共享内存';int main(void){ key_t key; int semid,shmid; char i,*shms,*shmc; struct semid_ds buf; int value = 0; char buffer[80]; pid_t p; key = ftok('/ipc/sem','a'); /*生成键值*/ shmid = shmget(key,1024,IPC_CREAT|0604); /*获得共享内存,大小为1024个 字节*/ semid = CreateSem(key,0); /*建立信号量*/ p = fork(); /*分叉程序*/ if(p > 0) /*父进程*/ { shms = (char *)shmat(shmid,0,0); /*挂接共享内存*/ memcpy(shms, msg, strlen(msg)+1); /*复制内容*/ sleep(10); /*等待10s,另一个进程将数据读 出*/ Sem_P(semid); /*获得共享内存的信号量*/ shmdt(shms); /*摘除共享内存*/ DestroySem(semid); /*销毁信号量*/ } else if(p == 0) /*子进程*/ { shmc = (char *)shmat(shmid,0,0); /*挂接共享内存*/ Sem_V(semid); /*减小信号量*/ printf('共享内存的值为:%s',shmc); /*打印信息*/ shmdt(shmc); /*摘除共享内存*/ } return 0;}信号信号用于在一个或多个进程之间传递异步信号。信号可以由各种异步事件产生,例如键盘中断等。Shell也可以使用信号将作业控制命令传递给它的子进程。1.信号截取函数signal()signal()函数用于截取系统的信号,对此信号挂接用户自己的处理函数。2.向进程发送信号函数kill()和raise()在挂接信号处理函数后,可以等待系统信号的到来。同时,用户可以自己构建信号,发送到目标进程中。此类函数有kill()和raise()函数#include <signal.h>#include <stdio.h>typedef void (*sighandler_t)(int);static void sig_handle(int signo) /*信号处理函数*/{ if( SIGSTOP== signo) /*为SIGSTOP信号*/ { printf('接收到信号SIGSTOP'); /*打印信息*/ } else if(SIGKILL==signo) /*为SIGKILL信号*/ { printf('接收到信号SIGKILL'); /*打印信息*/ } else /*其他信号*/ { printf('接收到信号:%d',signo); /*打印信息*/ } return;}int main(void){ sighandler_t ret; ret = signal(SIGSTOP, sig_handle); /*挂接SIGSTOP信号处理函数*/ if(SIG_ERR == ret) /*挂接失败*/ { printf('为SIGSTOP挂接信号处理函数失败'); return -1; /*返回*/ } ret = signal(SIGKILL, sig_handle); /*挂接SIGKILL处理函数*/ if(SIG_ERR == ret) /*挂接失败*/ { printf('为SIGKILL挂接信号处理函数失败'); return -1; /*返回*/ } for(;;); /*等待程序退出*/}linux下的进程之间的通信主要方式就这么多,这里从书上看来的,认为很好的学习材料,并自己总结下,方便以后自己遗忘后,再学习。


(以上内容不代表本站观点。)
---------------------------------
本网站以及域名有仲裁协议。
本網站以及域名有仲裁協議。

2024-Mar-04 02:08pm
栏目列表