进程间通信
操作系统底层工作的整体认识
-输入和输出设备:就是我们的磁盘的IO设备(如:键盘,U盘,显示器)
使用这些设备最终反馈的都是电信号,电信号被反馈到内部处理组件里面转成数字信号,所谓的数字信号就是我们的芯片,芯片集成了大量电路,电路通过高低压(高低电阻)判断电信号的开关(1和0),所以这就是我们的CPU为什么只认0101这种二进制的数据。
CPU
- 控制单元 :指令计数器、指令寄存器、地址寄存器
- 运算单元:ALU
- 数据单元:存储单元(寄存器、CPU缓存):主要存指令、存数据…(要算的时候从内存中捞过来)
CPU结构
- L3 cache是一个CPU上的多个内核(Core0和Core1)共享.
- L1和L2是内核独享。
- 还有一点值得注意:缓存是由最小的存储区块-缓存行(cacheline),缓存行大小通常为64byte
long(8字节)那么一个缓存行只能装64/8=8个long
先看一个空间局部性原则的例子:
- 运行结果是第一种比第二种快
- 第一种是按行加,
- 把二维数组的一维(第一行)读取到cache缓存,只需要跟内存交互一次。
CPU运行安全等级
- CPU在为java程序创建一个线程时(JAT),会从ring3切换到ring0,这里是状态切换
线程模型
- 内核线程模型(KLT):线程的创建调度和销毁由OS完成
- 用户线程模型(ULT):线程的创建调度和销毁由用户程序自己完成
- 堆栈一般有两个:一个在用户空间,一个在内核空间
- JVM运用的是KLT线程模型:java启动200个线程时,cpu的线程会立马+200
线程上下文切换:切换时会把执行结果保存到内存TSS(任务状态段)Task State Segment
虚拟机指令集架构
进程
每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享,所以进程之间要通信必须通过内核。
Linux内核提供了不少进程间通信的机制:
- 管道
- 消息队列
- 共享内存
- 信号量
- 信号
- Socket
管道
linux中的“|”竖线
$ ps auxf| grep mysql
上面命令行里的|竖线就是一个管道,它的功能是将一个命令(ps auxf)的输出,作为后一个命令(grep mysql)的输入,从这功能描述,可以看出管道传输数据是单向的。
如果想互相通信,我们需要创建两个管道才行。
同时,|这种管道是没有名字,表示匿名管道,用完了就销毁。
所以相应地,管道还有另外一个类型是命名管道,也叫做FIFO,因为数据是先进先出的传输方式。
在使用命名管道前,先需要通过mkfifo命令来创建,并且指定管道名字:
$ mkfifo myPipe
//myPipe就是管道的名字
基于Linux一切皆文件的理念,所以管道也是文件的方式存在,我们可以用ls查看一下,这个文件的类型是p,也就是pipe(管道)的意思:
$ ls -l
pew-r--r--.
1 root root 0 Aug 11 02:45 myPipe
接下来,我们往myPipe这个管道写入数据:
$ echo "hello" > myPipe //将数据写进管道
//停住了...
你会发现,操作后,就停在这了,这是因为管道里内容没有被读取,只有当管道里的数据被读取后,命令才可以正常退出???
于是我们在另一个终端执行另外一个命令来读取这个管道里的数据:
$ cat < myPipe //读取管道里数据
hello
可以看到,管道里的内容被读取出来了,并打印在了终端上,另外一个方面,echo那个命令也正常退出了。
我们可以看出,管道这种通信方式效率低,不适合进程间频繁地交换数据。当然它的好处自然就是简单哈哈哈。同时我们也很容易得出管道里的数据已经被另外一个进程读取了!!!。完成了进程间的通信!!!
那么,上面讲了命名管道,下面讲讲匿名管道的创建,需要通过下面这个命令调用:
int pipe(int fd[2])
这里表示创建一个匿名管道,并返回了两个描述符,一个是管道的读取端描述符fd[0],另一个是管道的写入端描述符fd[1]。注意,这个匿名管道是特殊文件,只存在内存,不存在文件系统中。
其实,所谓的管道,就是内核里面的一串缓存。
从管道的一端写入数据,实际上是缓存在内核中的,另一端的读取,也就是从内核中读取这段数据。
另外,管道传输的数据是无格式的流且大小受限。
但是了解到这,我们不能忘了主题!!!进程间的通信!!!
所以我们这时要会质问:这两个描述符都是在一个进程里面,并没有起到进程间通信的作用,怎么样才能使得管道是跨过两个进程的呢???
我们可以使用fork创建子进程,woc创建的子进程会复制父进程的文件描述符,这样就做到了两个进程各有两个(fd[0]与fd[1]),两个进程就可以通过各自的fd写入和读取同一个管道文件!!!实现跨进程通信了。
但是,管道只能一端读取另一端写入,所以上面这种模式容易造成混乱,因为父进程和子进程都可以同时写入,也可以同时读取。那么为了避免这种情况,通常的做法是:
- 父进程关闭读取的fd[0],只保留写入的fd[1];
- 子进程关闭写入的fd[1],只保留读取的fd[0].
所以说如果要双向通信,则应该创建两个管道。