最近扒垃圾桶有点上瘾了…
哦,不,谁说这些是垃圾了,这些只是历史久远了而已。互联网时代的从业人员总觉得什么都是新的,这并不正确,这也很悲哀!
虽然没有UNIX/Linux fork那般久远,本文的主角select也是一个直到今天我们还在使用的系统调用。
由于周末白天的时间并不完全属于自己,只能长话短说。
1969年,UNIX诞生。
1970年代,UNIX获得了蓬勃的发展,到了1980年代,不过在此很早之前,UNIX就抽象出了 “一切皆文件” 的思想。
一切皆文件是一个很典型的适配器设计模式的实例,不管底层是什么,是磁盘文件,是管道,还是键盘鼠标,或者显示器,在UNIX系统中都表示成文件,区分它们的方式是定义不同的 读和写 规则。
后来,Sun公司把上面这段话做成了一个叫做VFS(Virtual file system)的抽象层并实现了一个实例,这是1985年的事了,我们关于select的故事在那之前,大约1980年吧,那时的UNIX远没有这么规整,虽然接口很perfect,思想也还不错,但是实现还远谈不上优美。
select来源于基于文件描述符的全双工通信的需求。 这个需求在socket出现之后尤甚。
先从最初的情况说起,大概就是1969年,1970年前后吧。UNIX系统shell就是一个普通的半双工本地IO。操作员写终端,操作员读终端两个方向的操作是不能同时进行的。
这没什么不合理的,毕竟对端是个机器,它没有智能,它只能根据操作员的指令而行动:
- 操作员发出指令:写文件描述符。
- 机器返回结果:读文件描述符。
机器不能在操作员发出指令前给出结果。所以,半双工就够了,UNIX的shell一直也都是这样做的:
void loop()
{
while(1) {
cmd = read_tty(); // read
if (fork() == 0) {
exec(cmd); //write
}
wait();
}
}
然而,文件描述符的对端要是个人怎么办?
cu命令的出现呈现了这样一种不同的诉求。cu命令本身不重要,简单点说,cu就是打电话。打电话是个全双工操作:
- 你在说话的时候,对方可能也在说话,比如吵架的场景。
UNIX遇到这种场景,还是可以应对的,这个全双工通信需求第一次挑战了UNIX哲学,解决方案的事实证明UNIX足够强大:
- 一切皆文件配合fork。
以下是一个典型的全双工通信场景,打电话的伪代码:
void phone()
{
int fd;
fd = dial(...);
if (fork() == 0) {
while (1) {
word = read(fd, $我收听的话);
... // 理解这句话
}
} else {
while (1) {
... // 准备回复的言语
talk = write(fd, $我要说的话);
}
}
}
貌似还不错。
现在考虑一种混合的场景,考虑传话中继的工作:
- 接线员监听N个终端:监听终端。
- 接线员听某个终端:读终端。
- 接线员把听到的话传给另一个终端:写终端。
根据UNIX文件的传统特性,如果一个文件描述符代表的文件没有数据可读,那么读文件操作将会阻塞,传话中继接线员并不知道N个终端到底哪个终端可读,随便读一个终端都会造成进程阻塞。
假设终端1的数据2分钟后才会到来,中继接线员不巧让进程去读终端1,这会造成终端1阻塞2分钟…在10秒后,终端2来了数据,然而此时进程已经阻塞在了终端1,只能兴叹!
中继接线员迫切需要一个指示:
- 告诉我哪个终端有数据可读!
这个事情可以通过IPC来解决,但是这都是后话,那个时候还没有IPC…换句话说,如果说要实现这个需求,IPC是一个有力的竞争者,毕竟那个时候System V已经有了几个可用的IPC了,但是对于产生socket的BSD,却没有好用的IPC。
select来也!select是典型的 “需要做什么,我就做什么” 的代表,它所做的不多也不少。select就是来救场的,还不错,这个救场动作足够帅!
select会一并检查所有N个终端,然后返回有数据可读的终端的列表。
如果没有任何终端有数据可读,select将会阻塞,只要有任何终端到来数据,select就会返回。
select系统调用非常有用,它解决了全双工通信场景下的几乎所有 “由于不知情而盲目阻塞” 的问题。
1983年,TCP/IP登场,随后BSD socket打败System V Streams成为了标准,说白了socket就是一个扁平化的全双工文件描述符,没有Streams那样重度依赖ioctl,这是socket的优势,同时促进了select成为标准。
至于说后面的poll,epoll,那不过是针对select的优化而已。
但是这是故事的全部吗?貌似远远没有fork的故事精彩。确实,我也觉得,无论从UNIX进程还是从TCP/IP的角度,select都是配角,但正是这个select融合了二者。
回到fork方案,一个文件描述符分别由两个进程来读写,然而,在UNIX的进程抽象里,两个进程是隔离的,在前IPC年代,它们显然不好过于阴阳两隔。
既然不能让多个进程协同,那就在一个进程里完成所有。 这就是select!
历史总是让人觉得惊奇,不知是必然还是有人故意剔除了不精彩的部分。
BSD和System之间当时1V1。
BSD产生了socket,而System V则有IPC,二者都是为了同一个目标,无奈的是,socket认同了 “一切皆文件” 但System V却另辟蹊径,IPC!
于是对于BSD而言,socket也是文件,对于System V,显然IPC方案就被select给降维打击了,因为select的前提就是“一切皆文件”。
很多人会觉得这没有意义,讲select的历史没有意义,但是持这种观点的人真懂select的实质吗?无非也就是精通库函数用法吧。
有人问为什么UNIX发信号统称为kill这个不太吉利的词,事实上最开始的UNIX系统发送信号就是为了很不吉利的目的。几乎所有的信号默认动作都是结束进程(又要怼了是吧,因为总是能找个例子,信号的默认操作不是结束进程)。看点历史就明白了。
看看我们现在的说法,IPC都有哪些?…,最后别忘了,socket也是IPC的一种。
浙江温州皮鞋湿,下雨进水不会胖!
原文链接: https://blog.csdn.net/dog250/article/details/100591091
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;
也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/406477
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!