加入收藏 | 设为首页 | 会员中心 | 我要投稿 南京站长网 (https://www.025zz.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Unix > 正文

Linux进程间通信(IPC)

发布时间:2023-01-29 14:01:13 所属栏目:Unix 来源:未知
导读: 最近学习了Linux进程,也对exec族函数、system函数、popen函数等有了一定的了解,但是他们都不能更好的相互传数据。现在学习一下进程间的通信(IPC)。
进程间通信: 不同进程之间传数据、

最近学习了Linux进程,也对exec族函数、system函数、popen函数等有了一定的了解,但是他们都不能更好的相互传数据。现在学习一下进程间的通信(IPC)。

进程间通信: 不同进程之间传数据、交换信息。

方式: 管道(无名管道和命名管道),消息队列,信号量,共享内存,socket,steams等。其中socket 和 steams 支持不同主机上的两个进程通信。

一.管道(无名管道):

通常指无名管道,是Unix 系统 IPC最古老的形式。

头文件:

#include 

原型:

int pipe(int pipefd[2]);

特点:

1. 半双工,只能要么接收数据,要么传输数据。一个读,一个写。

2. 只能用于具有亲缘关系的进程通信,比如 父子进程。

3. 可以看成是特殊文件,它读写可以使用read write等函数,但它不是普通文件和系统文件,它是只存在内存中的。

我们代码实现父进程写入hello word 给 子进程。(无名管道)

**1.**开管道

2. 关闭子进程的写管道

3. 开始读管道接收父进程传的信息。

4. 关闭父进程的读取通道

5. 写hello word 给子进程。

6. 子进程读取数据hello word。

7. 输出读取的内容 hello word

代码:

在这里插入图片描述

运行过程及结果:

在这里插入图片描述

二.FIFO(命名管道):

**命名管道,是一种文件类型。

头文件:

**  #include <sys/types.h> 
    #include 

原型:

**`** int mkfifo(const char * pathname,mode_t mode);`

**pathname:**文件名

**mode:**权限 比如 0600 就是可读可写。

特点: 可以在无关进程中交互数据,和无名管道不一样。

有路径名,是以特殊的文件形式存放在文件系统中。

创建命名管道:

在这里插入图片描述

结果:

注意:如果已经存在这个文件,创建会失败的。

当我们打开这个管道的时候,如果没有指定**O_NONBLOCK(**非阻塞标志),

如果你是只读的方式打开,它会一直阻塞到有另一个进程为写而打开此管道。如果你是只写的方式打开,它会一直阻塞到有另一个进程为读而打开此管道。

我们用代码实现一个写,写hello,word 给另一个进程。 让另一个读取并且输出。

代码:

做读取的代码

在这里插入图片描述

写进程代码:

在这里插入图片描述

当我们去运行读取的程序,会一直阻塞,等待写的程序运行。

读取方结果:

在这里插入图片描述

写方结果:

三.消息队列:

消息队列是消息的链接表,是存在内核中的,一个消息队列是由一个标识符(ID号)来标识。

头文件:

 #include 
 #include 
 #include 
 

原型:

  **1. int msgget(key_t key, int msgflg);**

功能: 用于创建一个新的或打开一个已经存在的消息队列,此消息队列与key相对应。

创建成功返回队列的ID,失败返回-1

key: 函数ftok的返回值(ID号)或IPC_PRIVATE。

msgflag: IPC_CREAT:创建新的消息队列。

IPC_EXCL:与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。 IPC_NOWAIT:读写消息队列要求无法满足时,不阻塞。返回值: 调用成功返回队列标识符,否则返回-1.

添加消息:

 **2.int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);**

成功返回0,失败返回-1。

读取消息:

 **3. ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);**

成功返回数据的长度,失败返回-1.

参数:

**msqid:**消息队列的识别码(ID)。

**msgp:**指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构,形态如下。

struct msgbuf 
{
	long mtype; /* 消息类型,必须 > 0 */
	char mtext[1]; /* 消息文本 *
};
**m

sgsz:消息的大小。

msgtyp: 消息类型

msgflg:这个参数依然是控制函数行为的标志,取值可以是:0,表示忽略。

控制消息队列:

**4.int msgctl(int msqid,int cmd,struct msqid_ds *buf);

成功返回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.

key_t ftok( const char * fname, int id )

fname:

就是你指定的文件名(已经存在的文件名),一般使用当前目录,如:key = ftok(".", 1); 这样就是将fname设为当前目录。

id:

是子序号。虽然是int类型,但是只使用8bits(1-255)。这里的第二个参数也像代码那里用字母也行。

四、共享内存:

共享内存比消息队列更好一点,共享内存是有一个物理地址,进程都通过共享内存可以获取数据,写入数据。

思路:

1创建内存(shmget)

2.映射 (shmat)

3.数据 (strcpy拷贝)

4.释放内存(shmdt)

5.打掉共享内存(shmctl)

头文件:#include

#include

1.创建获取获取共享内存

int shmget(key_t key, size_t size, int shmflg);

成功返回共享内存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.映射:连接共享内存到当前进程的地址空间

void *shmat(int shmid, const void *shmaddr, int shmflg);

成功返回共享内存的指针,失败返回-1.

参数:

shmid: 标识共性内存的ID

sdmaddr: 指定链接的地址,一般默认为NULL或者0

shmflag: 它的两个可能取值是SHM_RND和SHM_RDONLY,但是这个参数,我们一般默认设置为0。

在这里插入图片描述

3.断开与共享内存的连接

int shmdt(const void *shmaddr);

成功返回0,成功返回-1.

参数:

shmaddr: 由shmat返回的指针

注意:将共性内存与当前进程脱离不等于删除共享内存函数,它只是一个去链接的过程。

4.控制或者删除共享内存的相关信息

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

成功返回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

原型:

 int sigaction(int signum, const struct sigaction act,
                     struct sigaction oldact);

参数:

signum: 注册信号的编号。

act: 是一个结构体,如果不为空说明需要对该信号有新的配置。

oldact: 如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复。

   struct sigaction {
               void     (*sa_handler)(int);//信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
               void     (*sa_sigaction)(int, siginfo_t *, void *);//信号处理程序,能够接受额外数据和sigqueue配合使用
               sigset_t   sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
               int        sa_flags;//影响信号的行为SA_SIGINFO表示能够接受,信号处理程序带有附加信息数据。
               void     (*sa_restorer)(void);
           };
           //回调函数句柄sa_handler、sa_sigaction只能任选其一

结构体中void (*sa_sigaction)(int, siginfo_t *, void *); siginfo_t有下列的内容

 siginfo_t {
  int      si_signo;    /* Signal number */
  int      si_errno;    /* An errno value */
  int      si_code;     /* Signal code */
  int      si_trapno;   /* Trap number that caused hardware-generated signal
 (unused on most architectures) */
  pid_t    si_pid;      /* Sending process ID */
  uid_t    si_uid;      /* Real user ID of sending process */
  int      si_status;   /* Exit value or signal */
  clock_t  si_utime;    /* User time consumed */
  clock_t  si_stime;    /* System time consumed */
  sigval_t si_value;    /* Signal value */
  int      si_int;      /* POSIX.1b signal */
  void    *si_ptr;      /* POSIX.1b signal */
  int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
  int      si_timerid;  /* Timer ID; POSIX.1b timers */
  void    *si_addr;     /* Memory location which caused fault */
  long     si_band;     /* Band event (was int in    glibc 2.3.2 and earlier) */
  int      si_fd;       /* File descriptor */
  short    si_addr_lsb; /* Least significant bit of address (since kernel 2.6.32) */
  }

sigval_t si_value这个成员中有保存了发送过来的信息;在si_int或者si_ptr成员中也保存了对应的数据。si_int (整型)数据,sigval_t si_value 也是,value是一个结构体,int sival_int; void sival_ptr;

可以发一个整数* 也可以是字符串

信号发送函数: sigqueue

头文件:

 #include 

原型:

 int sigqueue(pid_t pid, int sig, const union sigval value);

参数:

**pid:**进程号

**sig:**注册信号的编号

value就是下面的结构体

union sigval {
               int   sival_int;
               void *sival_ptr;
           };

sigqueue 函数只能把信号发送给单个进程,可以使用 value 参数向信号处理程序传递整数值或者指针值

我们代码实现一下发送和接收,发送一个整型数1000和pid号给对方。

首先是接收端代码:

在这里插入图片描述

在这里插入图片描述

发送端:

在这里插入图片描述

发送端运行:

在这里插入图片描述

接收端运行:

在这里插入图片描述

六、信号量:

信号量与上面的IPC结构不同,上面的IPC是可以发数据的,而信号量是不能发数据,它是一个计数器,信号量用于实现进程间的互斥与同步,而不是用于储存进程间通信数据。

Linux下的信号量函数都是在通用的信号量数组上进行操作,而不是 一个单一的二值信号量上进程操作

二值信号量:信号量只能取0或者1的变量

特点:

用于进程同步,如果是要进程间传递数据,需要和共享内存结合。

信号量是基于操作系统的PV操作,P(拿锁)V (放回锁)。

每次PV操作不仅限于对信号量值加1或者减1,可以加减任意的正整数。

支持信号量组。

头文件:

 #include 
 #include 
 #include 

1.创建或者获取一个信号量组,成功返回信号量集ID,失败返回-1

 int semget(key_t key, int nsems, int semflg)

参数:

key:

1.键值是IPC_PRIVATE,该值通常为0,意思就是创建一个仅能被进程进程给我的信号量。

2.键值不是IPC_PRIVATE,我们可以指定键值,例如1234;也可以一个**ftok()**函数来取得一个唯一的键值。

nsems: 表示初始化信号量的个数。比如我们要创建一个信号量,则该值为1.,创建2个就是2。

semflg: 信号量的创建方式或权限。

有IPC_CREAT(如果信号量不存在,则创建一个信号量,否则获取。)**,IPC_EXCL(只有信号量不存在的时候,新的信号量才建立,否则就产生错误。)。

2.对信号量组进行操作,改变信号量的值,成功返回0,失败返回-1.

 
  int semop(int semid, struct sembuf *sops, unsigned nsops);

参数:

semid: 信号集的识别码,可通过semget获取。

sops: 指向存储信号操作结构的数组指针,信号操作结构的原型如下

struct sembuf
{
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};
sem_num:操作信号在信号集中的编号,第一个信号的编号是0
sem_op:如果值是正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权;
如果sem_op的值是负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权;
如果sem_op的值为0,如果没有设置IPC_NOWAIT,则调用该操作的进程或者线程将暂时睡眠,直到信号量的值为0;否则,进程或者线程不会睡眠,函数返回错误EAGAIN。
sem_flg:信号操作标志,可能的选择有两种
IPC_NOWAIT //对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息
SEM_UNDO //程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。

nsops: 信号操作结构的数量,恒大于或等于1。

3.控制信号量的相关信息

 
  int semctl(int semid, int semnum, int cmd, ...);

参数:

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 设置信号量集中的一个单独的信号量的值。

第四个参数是一个联合体。

union semun {
  int              val;    /* Value for SETVAL */
  struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
  unsigned short  *array;  /* Array for GETALL, SETALL */
  struct seminfo  *__buf;  /* Buffer for IPC_INFO
 (Linux-specific) */
}

我们代码实现一下。让子进程先拿到钥匙 再放回,再让父进程拿钥匙,放回,销毁。

在这里插入图片描述

在这里插入图片描述

运行结果:

在这里插入图片描述

(编辑:南京站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章