Linux必知必会,答应我拿下这些Linux必备技能(2)

简介: Linux必知必会,答应我拿下这些Linux必备技能(2)

进程间通信 (共享信息)

  • 进程间通信的理解

通信是为了什么?             我知道, 是为了传递信息, 共享消息, 好日常的解释哈, perfect.


日常生活中的通信是为了在两个人之间共享消息.  进程间通信其实本质就是 两个进程之间共享一些资源,数据...


在前文我们介绍了一个进程的重点性质:独立性, 的确,在用户空间来看, 每一个进程之间都是完完全全的独立运行, 独立存在,互不干扰的.               ---  为啥, 不让你乱来呀, 保持进程间的隔离独立


但是, 两个进程之间总会存在需要交流的情况, 如下


数据传输   eg: A进程需要传输 一串数据 到B进程    


共享资源:eg : A B 进程之间需要共享一块内存资源memory, 使得A  跟 B 对于 这块memory都可见.


信号发送(通知):  好常见的好吧, 其实你已经用过很多了.  子进程挂了之后,父进程咋知道子进程躺尸了, 我要去为它接收退出码, 回收系统资源.           ---  其实就是子进程终止会给父进程发送信号,通知


如何实现通信,  通信的场所在哪里?         内核


有必要搞得很清楚哈, 来张图:     啥意思?   各个进程之间在用户空间中确实是完全隔离的, 但是内核大家是共享的, 是操作系统来维护操作的.   所以内核好比一个交易场所, 进程间的通信都是在内核态中完成的

  • 管道通信
  • 图解管道

何为管道, 管道总是用来传输的,   下水管道是用来传输水的.  同样 进程间在内核态插入的一根管道是为了传输数据的.

管道存在空间, 是内核态的一个缓冲区, 缓存,  管道的实现还是基于Linux下一切皆为文件的思想来完成的, 所以在一定意义上面,     我们可以将pipe管道理解为一个内核文件的感觉  (仅作为个人理解, 可能存在错误, 但是将pipe理解为内核缓存是绝对OK的)


我们从用户进程A中将数据读取到内核pipe文件中, 再将数据从pipe文件写进进程B,也就实现了数据从A流向B, 传输到B的目的了


匿名管道

对于匿名管道, 第一印象应该是什么?     是  |  ,你可能会说这不是竖划线嘛, 的确它是一根竖划线


但是,这是在你没有学习管道之前你可以这样说, 今天,请你记住它的另外一个称呼, 匿名管道.  其实很形象呀, 管道竖起来不正是很像一根竖划线吗?   对对对, 就是这样滴.    


A | B  含义是什么?      将A的输出通过管道传入B 当作B的输入.


eg: 场景带如:   fork出来一个子进程, 父进程关闭管道写端, 从管道中读取数据, 子进程关闭管道读端, 向管道中写入数据, 父读子写.                       --- 父子进程之间进行通信

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#define EXIT_ERR(m)\
  do { perror(m); exit(EXIT_FAILURE);} while(0)
int main() {
  int fds[2];
  if (pipe(fds) < 0) {    //获取管道
    EXIT_ERR("pipe");
  }     
  pid_t pid = fork();     //fork操作一定要在pipe的后面才OK      
  if (pid < 0) {
    EXIT_ERR("fork");
  }
  if (pid) {              //fa
    close(fds[1]);        //关闭写端
    char buff[1024] = {0};
    int n = read(fds[0], buff, 1024);
    close(fds[0]);
    if (n <= 0) {
      EXIT_ERR("read");
    } 
    printf("read %d bytes\n", n);
    write(1, buff, n);
    putchar(10);
  } else {                //son
    close(fds[0]);        //关闭读取端口
    char buff[1024];
    printf("请说:");
    scanf("%s", buff);             //sizeof(buff) 是容器大小
    write(fds[1], buff, strlen(buff) + 1); //此处绝对不能使用sizeof()
    close(fds[1]);
  }
  return 0;
}

匿名管道的弊端, 以及命名管道的引出, 匿名管道只能用于向父子进程这种存在血缘关系之间的进程间通信, 没有关系的进程间无法实现通信

  • 命名管道

解决了匿名管道无法实现非血缘关系的进程之间的通信, 对于命名管道, 可以实现任意两个进程之间的通信.  

mkfifo 命令可以创建一根命名管道, 或者使用系统调用也可以创建命名管道

 

管道模型的优缺点以及应用场景总结:


场景:  数据从一个进程传输到另外一个进程


优点:  数据传输比较及时


缺点1:不适合频繁的交换数据, 只要交换数据就必须要打通内核态中的管道通道, 但是我们都知道信息的传输, 数据的传输还存在一种场景就是,消息太多了, 我们可以不用立刻处理, 可以将消息, 信息放在哪里, 后面慢慢看, 就像发邮件一样.                   --- 提出问题, 如果需要频繁沟通, 我们不一定可以实时处理, 可以借助邮件思维, 先通过一个容器存储着, 慢慢看, 慢慢交流, 慢慢的完成频繁的数据交互。


缺点2:管道有同步,阻塞的问题限制.    eg :  阻塞读, 当 读取的字节数 > 管道缓冲区中的已有字节数, 读的进程就会阻塞等待, 写进程写入数据     阻塞造成了低效

  • ftok

ftok 仅仅只是一个简简单单的系统调用, 我却将其完全独立出来说明,是否小题大作了呀?  NONONO,   因为后序的所有IPC  通信全部需要获取一个唯一key值, 获取这个key 值得最优方式就是通过一个file 的 inode 来获取这个全局唯一 key 值, 可以避免重复.

System V 消息队列

  • 理解消息队列

消息队列:   生活化, 发邮件, 很日常呀, 别人发来的邮件都放在邮箱里面, 得空就可以对于邮件进行阅读和回寄邮件.                        消息队列其实就蛮像一个邮箱的.


我们创建一个内核态的全局的邮箱    --- 消息队列.


通过系统调用,各个进程可以进入内核态, 像消息队列,邮箱中 send 邮件,和 recv 邮件.  这种工作模式就可以达到   频繁的数据交换


进程A : 我 send 一封邮件 (struct数据单元) 到  Message Queue 里面去


进程B : 我从  Message Queue 读取到一封邮件 (struct 数据单元) 然后按照约定回 进程A一封


注意:我特意的将每一封邮件说成是一个数据单元.   既然是一个单元, 就是存在大小和数据格式上面的限制的.                              --- 大小限制留疑, 其实答案在后文介绍的系统调用参数中

  • msgget

Return Value : 规则同上    failure  return -1                sucess return msgid;

eg :

key : 至此开始, 几乎在每一个 IPC 通信, 下面都可以看见, 他就是一个共享容器的一个代言, 标识, 名称.            


msgflag : 9位权限标志位:    我们通常使用  0x666  |  IPC_CREAT | IPC_EXCL


插一个权限介绍:     r : 读权限    w : 可写权限   X : 可执行权限.   所谓的9位权限标识位, 就是针对: 文件拥有者, 文件拥有组人员, 其他人员的访问权限的限制

来吧: 虽然其他后面的还没有学习, 咱可以先创建一个msgqueue出来玩一玩呀:  

code :

#include <unistd.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <stdio.h>
int main(int argc, char* argv[]) {
  if (argc != 2) {
    fprintf(stderr, "usage: %s <filepath>\n", argv[0]);
    return -1;
  }
  key_t key = ftok("./a.txt", 'k');
  if (-1 == key) {
    perror("ftok");
    return -2;
  }
  printf("key: %d\n", key);
  //此刻已经获取到唯一key值了
  //创建一个msg queue
  int msgid = msgget(key, 0666 | IPC_CREAT | IPC_EXCL);
  if (-1 == msgid) {
    perror("msgget");
    return -3;
  }
  printf("msgid: %d\n", msgid);
  return 0;
}

ans:   看吧  ipcs -q :  查看创建的 IPC 全局的  msg queue.     ipcs -m 查看  ipc share memory

ystem V IPC 体系有一个统一的命令行工具:ipcmk,ipcs 和 ipcrm 用于创建、查看和删除 IPC 对象。        


记忆大师 : ipcs : 就是简单的查看  ipcmk : ipc make 创建   ipcrm : ipc remove移除


msgsnd

功能:    像IPC queue消息队列发送一条消息


msgflg  设为 IPC_NOWAIT , 设置非阻塞 不论是 send 的时候msg queue满了, 还是 recv 的时候msg queue 是空的都不会阻塞住. 无阻塞调用


Return Val :


sucess : return 0  


failure: return -1  && set errn

  • msgrcv

msgtyp :   指定接收的消息的优先级, 优先接收消息队列哪里的消息


If msgtyp is 0, then the first message in the queue is read.   读取消息队列第一条消息, 不论message 的 type, 只是接收第一条消息


如果msgtyp > 0 则会接收具有相同消息类型的第一条消息


如果msgtyp < 0 则会接收类型 <= msgtyp绝对值的第一条消息


Return Val:


success: return the  number of bytes actually copied into the mtext array   成功从mtext正文中读取的字节数


failure : return -1 && set errno

msgctl

功能 : 功能取决于 cmd, 其实就是对于IPC 执行 cmd 操作


cmd :   IPC_RMID : 删除消息队列.  


消息队列模型的优缺点以及应用场景总结:


场景: 两个进程频繁的进行数据交换.


优点: 避免了管道的同步阻塞问题, 提高了效率. 相比匿名管道只能是血缘进程间通信, 消息队列支持毫不相干的两个进程间的通信


缺点1:数据块存在长度限制, 不适合大数据的传输


缺点2:通信不及时


缺点3:  存在用户空间和内核空间数据的拷贝.     --- 留个思考, 共享内存是最快的通信机制, 向较于msg queue, 它连内核空间跟用户空间的数据拷贝代价都省去了

案例实操:    现在存在 A B  两个进程,  通过消息队列, 实现 A 向 B 发送消息, B 接收并且打印消息.

code msgA.c

#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TEXT_SIZE 1024
typedef struct msgbuff {
  long int type;     //消息数据类型
  char text[TEXT_SIZE];//正文大小
} msgbuff;
int main(int argc, char* argv[]) {
  if (argc != 2) {
    fprintf(stderr, "usage: %s <filepath>\n", argv[0]);
    return -1;
  }
  msgbuff buff;
  //1获取key 
  key_t key = ftok(argv[1], 'c');//当前目录
  if (key == -1) {
    perror("ftok");
    return -2;
  }
  //2.创建msg queue
  int msgid = msgget(key, 0666 | IPC_CREAT | IPC_EXCL);
  if (-1 == msgid) {
    perror("msgget");
    return -3;
  }
  //3.循环写入数据
  while (1) {
    printf("请输入消息类型type:\n");
    scanf("%d", &buff.type);
    printf("请输入消息正文text:\n");
    scanf("%s", buff.text);
    if (-1 == msgsnd(msgid, &buff, TEXT_SIZE, IPC_NOWAIT)) {
      perror("msgsnd");
      return -4;
    }
  }
  return 0;
}

code msgB.c

#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define TEXT_SIZE 1024
#define TYPE 2
typedef struct msgbuff {
  long int type;     //消息数据类型
  char text[TEXT_SIZE];//正文大小
} msgbuff;
int main(int argc, char* argv[]) {
  if (argc != 2) {
    fprintf(stderr, "usage: %s <filepath>\n", argv[0]);
    return -1;
  }
  msgbuff buff;
  buff.type = TYPE;
  //1获取key 
  key_t key = ftok(argv[1], 'c');//当前目录
  if (-1 == key) {
    perror("ftok");
    return -2;
  }
  //2.获取消息队列msgid
  int msgid = msgget(key, 0666 | IPC_CREAT);
  if (-1 == msgid) {
    perror("msgget");
    return -3;
  }
  //3.循环读取数据
  while (1) {
    memset(&buff.text, 0, TEXT_SIZE);
    int read_size = msgrcv(msgid, &buff, TEXT_SIZE, TYPE, IPC_NOWAIT);
    if (-1 == read_size && errno == ENOMSG) {
      continue;//还没有消息到来
    }
    if (-1 == read_size) {
      perror("msgrcv");
      return -4;
    }
    buff.text[read_size] = 0;//设置结束字符,习惯
    if (strcmp("quit", buff.text) == 0) {
      break;//通信结束
    }
    printf("recv: %s\n", buff.text);
  } 
  //4.结束通讯删除msg queue
  if (-1 == msgctl(msgid, IPC_RMID, NULL)) {
    perror("msgctl");
    return -5;
  }
  return 0;
}

ans:

编程模型: 模子.

  1. 创建key = ftok()    唯一标识
  2. 创建消息队列, msgid = msgget()
  3. 收发数据   msgrcv() + msgsnd()
  4. 结束通信, 删除消息队列 msgctl(IPC_RMID)  or  ipcrm -q

System V 共享内存

理解共享内存

  • 正常情况下, 进程地址空间中的虚拟内存,映射到的物理内存都一定是互不相同的, 是不会存在重合的, 因为进程的独立性。                                        --- 但是为了通信, 可以固定一块物理内存, 将其挂载到进程A, 进程B上去, 这样, A 和 B 对于这块物理内存都是可见的, 于是在这块物理内存为介质, 多进程可以实现通信


最快速,最高效的IPC 通信.

  • shmget

功能:申请共享内存


shmflag : 9位权限标志位:    我们通常使用  0x666  |  IPC_CREAT | IPC_EXCL


插一个权限介绍:     r : 读权限    w : 可写权限   X : 可执行权限.   所谓的9位权限标识位, 就是针对: 文件拥有者, 文件拥有组人员, 其他人员的访问权限的限制

  • shmat

功能:将物理内存连接,挂载,关联到进程地址空间中去


shmaddr :NULL 自动选取连接地址,         不为NULL, 人为指定, 希望挂载到的地址


我们还是填写 NULL


Return Val : 返回一个ptr, 指向共享物理内存的首地址. 虚拟空间中的连接首地址

  • shmdt

功能:解除挂载, 将共享内存和当前进程进行脱离.


shmaddr :  之前 shmat 的 return val, 共享内存映射在虚拟地址空间上的首地址.


Return Val:          success return 0     failure return -1

  • shmctl

cmd:  IPC_RMID  删除共享内存

共享内存模型的优缺点以及应用场景总结:


场景:  多个进程都需要加载一份配置文件的场景下, 由一个进程负责将配置文件加载到共享内存中, 需要这份配置文件的进程直接挂载这个共享内存使用即可


优点:  高效, 快速, 是最快的IPC通信机制了.


缺点:  需要借助信号量来进行进程间的同步工作, 自身不支持进程间同步

案例实操:    现在存在 A B  两个进程, 共享一块内存 m, A  往里面写入数据, B 从中读取 A 写入的数据

code A:

#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <errno.h>
#define SHM_SIZE 4096
int main(int argc, char* argv[]) {
  if (argc != 2) {
    fprintf(stderr, "usage: %s <filename>", argv[0]);
    return -1;
  }
  //1创建key
  key_t key = ftok(argv[1], 'a');
  if (-1 == key) {
    perror("ftok");
    return -2;
  }
  //2.创建共享内存
  int shmid = shmget(key, SHM_SIZE, 0666 | IPC_CREAT | IPC_EXCL);
  if (-1 == shmid) {
    perror("shmget");
    return -3;
  }
  //3. 挂载共享内存
  char* p = (char*)shmat(shmid, NULL, 0);
  if ((char*)-1 == p) {
    perror("shmat");
    return -4;
  }
  //4.通信阶段, 往共享内存中写入数据
    *p = 0;//先设置为NULL
  printf("请输入共享数据:\n");
  scanf("%s", p);
  return 0;
}


code B

#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define SHM_SIZE 4096
int main(int argc, char* argv[]) {
  if (argc != 2) {
    fprintf(stderr, "usage: %s <filename>", argv[0]);
    return -1;
  }
  //1创建key
  key_t key = ftok(argv[1], 'a');
  if (-1 == key) {
    perror("ftok");
    return -2;
  }
  //2.获取共享内存
  int shmid = shmget(key, SHM_SIZE, 0666 | IPC_CREAT);
  if (-1 == shmid) {
    perror("shmget");
    return -3;
  }
  //3. 挂载共享内存
  char* p = (char*)shmat(shmid, NULL, 0);
  if ((char*)-1 == p) {
    perror("shmat");
    return -4;
  }
  //4.通信阶段,从共享内存中读取数据
    while (1) {
        if (*p) break; //等A传输数据  
    }
  printf("%s\n", p);
  //5.卸载共享内存
  if (-1 == shmdt(p)) {
    perror("shmdt");
    return -5;
  }
  return 0;
}

编程模型: 模子.

  1. 创建key = ftok() 唯一标识
  2. 创建共享内存 int shmid = shmget()
  3. 挂载共享内存 p = shmat()
  4. 利用共享内存完成通信
  5. 卸载共享内存 shmdt()
  6. 删除共享内存 shmctl()
  • POSIX信号量
  • 理解信号量

信号量是一种资源, 是临界资源, 是操作系统封装好的, 具有保护作用的临界资源, 不需要我们手动上锁, 因为对于信号量资源的请求和归还, 操作系统自身就已经帮我们保护好了的.


目的: 是为了在多线程同步访问临界资源的时候不会产生资源竞争造成的冲突问题.  用于线程间同步访问临界资源. 这个临界资源就是sem 信号量

  • sem_init      

功能:初始化信号量

Return Val : 还是老规矩, success return 0     failure return -1

  • sem_destroy

功能: 销毁信号量

  • sem_wait

功能: 申请, 消费一个sem资源.

Return Val : success return 0 and failure return -1

  • sem_post

功能: 归还, 增加一个sem 资源

Return Val : success return 0 and failure return -1

信号量的优缺点以及应用场景总结:


场景:  多线程同步访问资源, 或者是多进程同步访问资源


优点:  比文件锁有优势,效率高一些,起码不用打开文件关闭文件这些耗时间的工作, 相较加锁操作更简单方便


缺点: 不够安全,PV操作使用不当容易产生死锁,遇到复杂同步互斥问题实现复杂, 针对代码块需要加锁保护的问题无能为力, 因为代码块毕竟不是能用数字衡量的呀.

信号

  • 何为信号?
  • 常见的信号学习
  • 如何注册自己的信号处理函数

全文终,综述留续

送君入门,  各位大哥们, 可以看到此处,实属不易呀, 或许是被小杰的文章引的入迷了, 哈哈哈,开了玩笑.


说正事,   摊在寝室床头与小学生抢人头, 说实话咱真不如去看看博文, 出去走走,锻炼锻炼, 学习学习, 计算机的学习之途本就是一头扎进汪洋大海.


没有绝对的强者, 没有绝对的天赋, 只有针对一个方向的强者, 所谓天赋偷偷告诉你,是高手们的凡尔赛, 他们肯定也是经过了无数日夜的煎熬, 刷题, 从初学, 到读书, 到总结出属于自己的知识点. 不容易呀.


看到此处,小杰真心佩服,说明您也有炙热的心, 无限的渴望提升的信念. 如果觉得小杰写的还蛮不错的, 可以伸手给小杰一个小心心, 小杰万分感谢, 对于哪个信号章节, 请原谅小杰, 创作不易, 如果硬要写完, 发文就得再拖一周, 小杰希望自己可以写出有质量, 对自己有提升的总结                                                    --- 祝学习的学业有成, 工作的前辈们升



相关文章
|
存储 算法 Linux
Linux必知必会,答应我拿下这些Linux必备技能(1)
Linux必知必会,答应我拿下这些Linux必备技能(1)
Linux必知必会,答应我拿下这些Linux必备技能(1)
|
网络协议 Linux 数据安全/隐私保护
LINUX命令必备技能
LINUX命令必备技能
89 0
|
运维 供应链 Cloud Native
报名中!阿里云、统信软件、西安邮电等多位专家教授畅谈eBPF和Linux的硬核技能|2022云栖大会
12场技术分享+3大产品动手实践,本专场邀请了创业公司、高校师生老师和学生等多视角 KOL一起分享。
报名中!阿里云、统信软件、西安邮电等多位专家教授畅谈eBPF和Linux的硬核技能|2022云栖大会
|
Linux
2000字教你如何玩转Linux man命令,隐藏技能非常nice!
man命令对于日常使用linux的朋友非常友好,提供了不少好用的参数,配上参数简直就是一把利器,除了我举了例子的参数,还有好多好用的参数等着大家去开发,大家可以在使用的时候直接man man查看有哪些自己能够利用上的参数哦!
188 0
2000字教你如何玩转Linux man命令,隐藏技能非常nice!
|
Linux 应用服务中间件 开发工具
Linux必备技能:如何在Vim中跳到文件的开头或者结尾?
今天给大家带来的是Linux方面的小实战:如何在Vim中跳到文件的开头或者结尾?
954 1
Linux必备技能:如何在Vim中跳到文件的开头或者结尾?
|
安全 网络协议 Ubuntu
[CentOS,LISTEN,运行级别,NFS,Debian]强化Linux安全的10个技能
  1.找出不必要的服务   很明显,服务器上跑的服务,并不是每个都有用的。强烈建议检查并关掉不需要的服务,从而减少风险(多跑一个服务,就可能多几个漏洞)。   查询运行在runlevel 3的服务列表:   [afei@afei ~]# /sbin/chkconfig --list |grep '3:on'
125 0
|
网络协议 Linux 数据安全/隐私保护
LINUX命令必备技能
LINUX命令必备技能
106 0
|
关系型数据库 Shell 应用服务中间件