文件系统与异步操作异步IO那些破事

为什么想起写这篇文章前面这篇文章提到,旧的 Linux AIO 只支持直接(Direct)IO,还对读写区域大小有限制,但是 Windows 上的 IOCP 就有完整的 AIO 支持。之前真的觉得 Windows 真的很牛B,但是对为什么这样一直懵懵懂懂。 直到昨天我看到了这篇讨论帖:https://news.ycombinator.com/...,他说微软的异步IO是用线程模拟的。 WTF?这个内核原生支持这么高大上的东西居然是模拟的?但是人家还拿了微软官方的文章佐证 ... so if you issue an asynchronous cached read, and the pages are not in memory, the file system driver assumes that you do not want your thread blocked and the request will be handled by a limited pool of worker threads.微软官方的说明总不会错。但为什么会这样?缓冲(Buffered)IO实现异步为什么就这么难? 还得从硬件说起回顾一下大学学的计算机硬件知识:https://www.cnblogs.com/jswan... 每个硬盘有多个盘面,每个盘面都是由一圈圈的同心圆组成的,叫做磁道(track)。每个磁道又被等比划分为若干个弧段,叫做扇区(sector)。扇区有固定的大小和个数,而且从硬盘诞生就被分配在固定位置。一般一个扇区具体的大小视总磁盘大小而定,传统上512B为一个扇区(但是也有不同)。 扇区是实际上的磁盘最小读写单位,操作系统与磁盘文件系统通信必须以扇区的整数个进行。这里的整数个不仅代表大小,而且指个体,例如你不能只读第一个扇区的后半个+第二个扇区的前半个,虽然加起来大小也是一个扇区。 直接(Direct)IO,最原始的文件IO这种扇区操作磁盘的方式就直接派生了一种文件IO方式——直接(Direct)IO,也叫裸(Raw)IO,也叫非缓存(Unbuffered)IO。在 *nix 系中对应 O_DIRECT,在 Windows 中对应 FILE_FLAG_NO_BUFFERING。 ...

June 16, 2019 · 1 min · jiezi

Ubuntu安装Samba文件共享服务器(NAS)

终于有点时间来解决下家中NAS需求了。一般自制NAS,只有选Samba。速度比FTP快,便利性比Windows文件夹共享好,设置多等等。▶参考:samba简介安装Samba$ sudo apt-get update$ sudo apt-get install samba samba-common-bin核心步骤:配置SambaSamba唯一设置的入口就算一个smb.conf文件,所有变化都依次而来,出了问题也只需要在这里找原因。配置之前先说明,这里我不打算只共享一个文件夹,而是共享树莓派连接上的所有外置硬盘。树莓派的外置硬盘默认挂载在了/media/pi目录下,每个硬盘挂载为/media/pi/drive1,/media/pi/drive2等。所以不用一个一个共享,直接把/media/pi共享就OK了。下面配置还会限制:只有pi这个用户可以访问。常用且肯定没问题的最简单配置如下:# 编辑Samba的配置文件sudo vim /etc/samba/smb.conf# 文件末尾添加这个共享文件夹的定义:[NAS]comment = NAS External drivepath = /media/pipublic = Yesbrowseable = Yeswriteable = Yesvalid users=pi其中:valid users:只允许指定的用户和用户组访问设置Samba用户名和密码这一步也至关重要,直接影响各设备的访问。注意,这个用户必须是本机已经在group和user里面都存在的用户,且必须权限设置什么的符合samba要求才行。否则会导致有些设备完全无法访问这个文件夹。之前试了自己groupadd和useradd本地用户后,又在samba里smbpasswd -a添加用户名密码,结果Mac完全访问不了,Windows也是根据系统的不同有的能访问有的不能访问。所以这里推荐用树莓派的默认用户名pi:# 输入Samba用户的访问密码sudo smbpasswd -a pi重启Samba# 推荐重启方法(可以看到自检过程)$ sudo /etc/init.d/samba restart到这一步,如果没出问题的话,就会显示成功:按照之前的配置,现在你就可以访问Samba共享文件夹了。访问方法一般访问方法如下:Windows:直接打开桌面的网络(网上邻居)-> RaspberryPi(树莓派的网络名),然后就可以看到树莓派上所有共享的文件夹和设备了。Mac: 稍微麻烦一点,在Finder中点击菜单 -> Go -> Connect to server -> 输入smb://IP地址,按照要求输入本机或树莓派的Samba用户名密码:然后可以看到,目录中和本地目录几乎没什么区别:能看预览,支持所有文件夹正常的快捷键,随意拷贝粘贴,这是FTP远不能比的。将Samba的共享目录映射到本地Windows上,直接在文件夹里点击菜单->工具->映射网络驱动器。然后选择映射出来的驱动盘字母,点击浏览,选择网络邻居里的树莓派,确定完成。就会在本地的计算机里显示出映射磁盘了。Mac上,一般在文件夹里面通过Cmd+K连接服务器后打开共享文件夹后,系统就会自动把它挂载到/Volumes/你的共享文件夹名这里。可以直接通过命令行随意访问。然后即使桌面上的文件夹关闭后,也还是可以在命令行里正常访问。多用户访问Samba我们用Samba,就肯定有多用户需求。但是多用户问题恰是Samba最麻烦的地方,如果是像我这样对Linux用户权限不熟悉的话。首先需要明了:Samba的里面添加的用户,必须是Linux已经存在的用户!而且这个用户必须有相应的权限,才行。所以多用户策略大概如下:创建Linux本机用户组,并赋予相应权限创建Linux本机的用户,并赋予相应权限创建共享文件夹,修改文件夹权限,修改文件夹所有者,改为对应的Samba用户或用户组创建与Linux用户对应的Samba用户,并创建密码在Samba配置文件里面,声明有权访问共享文件夹的用户或用户组注意:挂载的NTFS磁盘,是不支持unix体系的group和user的,所以里面的文件默认所有者和所属组都是root。要解决这个,需要在mount挂载时就指定所有者,但是也不能分别指定里面某个文件夹或目录的所有者。Samba调试Samba的自检程序testparm自动测试,并显示Samba所有的共享和定义:$ testparm列出当前所有已注册的Samba用户$ sudo pdbedit -L使用smbclient测试smbclinet是命令行客户端,需要下载安装使用:# 安装$ sudo apt-get install smbclient# 连接Samba服务器$ smbclient //192.168.1.111/share -U sambaUser01$ smb: ls如果连接成功,就会进入smb的交互shell,然后输入ls,成功列出目录,则连接完全成功。这是常用的最方便的测试方法,如果有任何一点不成功,这个连接命令都无法执行。只要这里能够正常访问,那么其它地方都没有问题。常见问题Mac上能用guest访问却不能用设置了的用户访问这个是你的Samba用户设置出了问题。有可能是Samba中定义的用户,在本机中权限不够。解决方法就是:直接用树莓派的原生用户pi,或仔细研究新创建的用户权限,添加好了再到Samba配置中设置原生用户pi以外的用户都不能访问外置磁盘尝试过多用户方案,只要不是外置磁盘,都能正常访问、读写。但是插的U盘,外置移动硬盘,除了pi用户以外全都只能进入,不能写入。就算把新建用户升级到超级用户,就算把文件目录的所有者改为新建的用户,也还是一样的。消除来自Mac的.DS_Store文件安全隐患Mac上访问远程文件夹会留下.DS_Store文件,其中包含太多信息这样很不安全。所以我们要在Mac上设置,在访问远程文件夹时不留下这个文件:$ defaults write com.apple.desktopservices DSDontWriteNetworkStores true但是以上方法不是完全生效,目前MacOS 10.12以上都不一定能生效。访问外置硬盘Permission Denied这个也是用户权限问题,配置原生pi用户就没问题了。

January 24, 2019 · 1 min · jiezi

存储引擎及如何选择合适的存储 API

存储引擎及如何选择合适的存储 API原文请查阅这里,略有删减,本文采用知识共享署名 4.0 国际许可协议共享,BY Troland。本系列持续更新中,Github 地址请查阅这里。这是 JavaScript 工作原理的第十六章。概述每当设计网页程序的时候,为本地设备选择合适的存储机制尤为重要。一个好的存储引擎可以帮助开发人员有效地存储数据,减少传输带宽及提高程序的响应速度。正确的存储缓存策略是构建移动端离线网页体验的核心组成部分,越来越多的用户想当然以为可以离线使用移动端网页程序。本章,我们将讨论各种可用的存储 API 和服务,并提供一些在构建网页程序时如何正确地选择存储引擎的建议。数据模型数据存储模型决定了其内部是如何组织存储数据的。这会影响到整个网页程序的设计,并计算出为获取高性能网页程序及解决其所遇到的问题所需要的代价。没有所谓更好的技术和一刀切的解决方案,因为所有的问题都是工程学相关的问题。那么,让我们来瞧瞧可供选择的数据模型吧:结构型:以预定义字段将数据存储于表中,因其为典型的基于 SQL 的数据库管理系统,所以可以很好地适应灵活和动态的数据查询。IndexedDB 即浏览器端结构型数据库的一个典型例子。键/值型:键/值数据存储及关系型 NoSQL 数据库,允许开发者通过唯一键索引来存储和获取非结构型数据(即非预定数据类型的字段的数据)。键/值数据存储就像哈希表存储,意及其允许在一定时间内访问索引的不定数据类型的数据。键/值数据型存储的很好的例子有浏览器端的 Cache API 和 服务器端 Apache Cassandra。字节流型:这一简单的模型把数据存储为定长,混淆字符串的字节变量,让应用层来控制其内部数据组织。该模型尤其适合于文件存储和其它层次型组织的 blob 数据。字节流存储的典型例子包括文件系统和云存储设备。持久性网页程序的数据存储方法可以以数据的存储时长来进行分析:会话持久性:仅当活动的单个网页会话或者浏览器选项卡时数据有效,关闭即失效。会话持久性数据存储一个例子即 Session Storage API。设备持久性:该类数据存储于指定设备的跨会话和浏览器选项卡/窗口有效。设备持久性存储机制的一个例子即 Cache API。全局持久性:该类数据跨会话和设备存储。因此,它是兼容性最好的数据持久性方案。它不会存储于设备上,这意味着需要从服务端存储中获得数据。因为这里只讨论针对设备的数据存储,所以这里只是稍微提下服务端数据存储。客户端数据持久性现如今,有相当多的浏览器 API 可供选择用于存储数据。这里将详细讨论这些方法,然后对其进行比较以便让开发者轻松地选择正确的数据存储方案。然而,首先在选择如何存储数据之前,开发者需要考虑几件事情 。当然了,第一件事即必须想清楚打算如何使用网页程序及之后的维护和性能优化。即使胸有成竹,可代选择的方案可能只有几个。以下为开发者需要考虑的问题:浏览器支持-优先考虑标准化和组织良好的 API,因为这些 API 不会轻易变动且兼容性好。这些 API 同样有非常丰富的文档和活跃的开发者社区。事务-有时候,事务对于相关的数据存储操作集原子化成功或失败至关重要。传统数据库使用事务模型来实现该功能,在事务模型中以把相关数据更新划分为任意的单元。同步/异步-少数存储 API 是同步的意即存储或者检索数据请求会阻塞当前活跃线程直到数据请求结束。使用同步数据存储 API 会阻塞主线程且会让程序界面假死。尽量使用异步存储 API。对比这里,让我们浏览一下网页开发者当前可用的 API 并使用上述的几个维度来进行比较。<iframe src=“https://airtable.com/embed/sh...;></iframe>文件系统 API有了 FileSystem API,网页程序就可以在用户本地文件系统的沙箱中进行新建,读取,操作和写文件。该接口包含如下几个部分:读取和操作文件:File/Blob,FileList,FileReader新建和写文件:Blob(),FileWriter访问目录和文件系统:DirectoryReader,FileEntry/DirectoryEntry,LocalFileSystem文件系统 API 并不是标准的 API.因其兼容性不太好,所以切记不要在生产环境中使用。各种浏览器厂商的实现会有很大的不同且该 API 以后可能会变更。文件和目录条目 API 接口文件系统用来表示一个文件系统。可从任意文件系统条目的 filesystem 属性中获取这些 对象。少数浏览器提供了额外的 API 来创建和操作文件系统。该接口不会允许开发者访问用户的文件系统。相反,开发者会在浏览器沙箱内获得一个虚拟磁盘。若想要访问用户的文件系统,可以采取安装 Chrome 插件的方法。获得文件系统网页程序可调用 window.requestFileSystem() 来访问沙箱文件系统。:// 注意: 该文件系统以 Google Chrome 12 为前缀The file system has been prefixed as of Google Chrome 12:window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;window.requestFileSystem(type, size, successCallback, opt_errorCallback)第一次调用 requestFileSystem() 方法的时候会新建一个本地存储。需要注意的是该文件系统是沙箱型的,意即网页程序不可以访问其它程序的文件。在获得访问文件系统的权限后,开发者可以对文件和目录进行大部分常规文件系统操作。和其它存储策略相比,FileSystem 大有不同,它旨在满足数据库不能很好地解决客户端存储的情况。一般来说,程序用来处理大型二进制 blobs 文件或者和浏览器上下文之外的程序分享数据。以下为使用 FileSystem 的好范例:断点续传工具-当选择文件或者文件目录来上传的时候,会把文件复制进沙箱后一次上传一个分片。视频游戏,音乐或者其它含有大量媒体文件的程序为了更好的性能提供离线访问或者本地缓存功能的音频/图片编辑器-数据 blobs 一般会非常庞大且需要可读写离线视频播放器-其需要下载大型文件以备之后观看或者快速的寻轨-缓冲离线网页邮件客户端-下载附件且进行本地存储以下为该 API 的当前浏览器支持情况:Local storagelocalStorage API 允许开发者访问 文档 源的 Storage 对象。存储的数据在多个浏览器会话之间仍然有效。localStorage 和 sessionStorage 类似,只不过存储在 localStorage 中的数据没有过期时间,而存储在 sessionStorage 中的数据会在页面会话结束时丢失-意即当关闭页面时即丢失。无论是 localStorage 还是 sessionStorage 其数据只存储在特定的页面源中,所谓页面源包含协议,主机名和端口。以下为该 API 的当前浏览器支持情况:Session storagesessionStorage 属性允许开发者访问当前源的会话 Storage 对象。前面简述过,sessionStorage 和 localStorage 类似。唯一的区别即,存储在 localStorage 中的数据没有过期时间而 sessionStorage 中的数据会在页面会话结束时丢失。页面会话的时效为浏览器打开时且在页面重载和恢复时。在新的选项卡中打开新页面或者窗口会导致重新初始化一个新的会话,这与会话 cookies 的工作机制是不一样的。请注意无论数据存储于 sessionStorage 还是 localStorage 都仅只在指定的页面源中有效。以下为该 API 的当前浏览器支持情况:Cookies所谓 cookie(网页 cookie,浏览器 cookie) 指的是由用户的服务器发送到客户端的一小段数据。浏览器将其存储下来然后在下一次请求的时候捎带上它发往服务器。典型地,它被用来告知两个请求是否来自于同一个客户端-比如保持用户登录状态。它为 无状态 HTTP 协议记录有状态信息。Cookies 有以下三个主要用途:会话管理-登录,购物车,游戏积分或者其它需要在服务端存储的数据。个性化-用户参数,皮肤和其它设置监控-记录和分析用户行为Cookies 曾经一统客户端存储方案。当它是客户端存储的唯一方案的时候,这是不二选择,现如今推荐选择使用现代存储 API 来存储客户端数据。每次发送请求都会捎带上 Cookies,所以会影响性能(特别是当在一个移动端请求数据的时候)。有两种类型的 cookies:会话 cookie-当用户关闭浏览器时失效。网页浏览器可以使用恢复会话技术来固化大多数会话 cookie,就好像未曾关闭浏览器一样。永久性 cookie-和客户端关闭即过期相反,永久性 cookie 会在指定的过期时间过期或者在一个指定的时间(Max-age)后过期。请注意不要在 cookie 中存储凭据或者敏感信息,因其固有的不安全缺陷机制。然而,不肖说,cookie 是兼容性最好的方案。CacheCache 接口是缓存请求/响应对象的存储机制。请注意和 workers 一样可在窗口作用域内使用 Cache 接口。虽然 Cache 是在服务工作线程规范中定义的,但这并不表示一定要和服务工作线程一起使用。一个源可以拥有多个命名的缓存对象。开发者只需要在脚本(比如在服务工作线程中)中实现如何处理更新缓存即可。除非显示请求否则不会更新缓存中的对象,只能通过删除缓存对象,否则不会过期。使用 CacheStorage.open() 来打开指定命名的缓存对象,然后调用任意的缓存方法来维护缓存。开发者需要定时清除缓存条目。每个源在浏览器端都有限额的缓存数据。使用 StorageEstimate 来估算使缓存配额使用率。浏览器尽力管理硬盘空间,但它有可能会删除指定源的缓存数据。浏览器可能会删除指定源的所有数据抑或不会。切记使用名称来对脚本进行版本控制且只操作可以安全操作的脚本版本。查看 Deleting old caches 以获取更多信息。CacheStorage 接口表示 Cache 对象存储。接口:提供一个可以为 ServiceWorker,其它类型工作线程或者 window 作用域可访问的所有命名的缓存的主目录(虽然是在 服务工作线程 中定义的缓存,但是并不意味着只能将其和工作线程配合使用)维护一份字符名称和 Cache 对象的映射使用 CacheStorage.open() 来创建 Cache 实例。使用 CacheStorage.match() 来检查指定的 Request 是否是 CacheStorage 对象中的 Cache 对象的键。使用通过全局 caches 属性来访问 CacheStorage。IndexedDBIndexedDB 是一种客户端持久性数据存储方案。因其允许开发者创建拥有富查询能力的网页程序而不用关心网络情况,这些网页程序可以线上或者离线运行。IndexedDB 适用于大量的数据存储(比如,商业图书馆DVD 目录)和不需要保持网络连通的网页程序(比如,邮件客户端,待办事项及便笺)。因大家都比较熟悉其它存储 API ,本文将对 IndexedDB 多唠会嗑。另外,如今随着网页程序越来越复杂 IndexedDB 也正变得越来越流行。IndexedDB 原理IndexedDB 允许开发者使用键来存储和获取一个对象。所有对数据库的操作均发生于事务之中。和其它网页存储方案一样,IndexedDB 遵循同源策略。因此,不能够跨域访问数据,只能访问同一个域名下的存储数据。可以在包括 网页服务线程 的大多数上下文上使用该异步 API。IndexedDB 曾经也有 synchronous 版本,应用于网页线程中,但是由于社区对此并不感冒所以被从规范中删除了。IndexedDB 曾经也有一个被称为 WebSQL 数据库的竞品规范,但是已经被 W3C 所弃用。虽然 IndexedDB 和 WebSQL 均是存储方案,但是它们功能并不一样。WebSQL 数据库是一个关系型数据库访问系统,而 IndexedDB 只是一个索引表系统。不要以其它类型数据库为蓝本,想当然地使用 IndexedDB。相反,需要仔细阅读文档。以下为开发者所需要了解的核心概念:IndexedDB 数据库存储键-值对-值可以是复杂的结构型对象而键可以是这些对象的属性。开发者可以使用对象的任意属性来创建索引进行快速搜索,比如枚举排序。键也可以是二进制型对象。IndexedDB 是建立在事务型数据模型之上的-IndexedDB 中的所有操作都发生于事务上下文之中。因此,开发者不可以在事务之外执行命令或者打开游标。同样地,事务只能自动而不可以手动提交。大多数 IndexedDB 都是异步的 -API 不会通过返回值地形式来返回数据。相反,需要传入回调函数来处理返回值。意即,开发者不是同步把值存储进数据库或者直接从数据库中取回值。相反,发起 request 请求即表示一次数据库操作。当数据处理结束会通知开发者,开发者所监听的事件类型会通知数据操作是否成功。这和 XMLHttpRequest (或者其它这么多 JavaScript 相关的东西) 的工作原理大同小异。IndexedDB 使用大量的请求-请求是对象用来接收之前提到的成功或者失败事件。它们包含 onsuccess 和 onerror 属性,和 readyState,result,errorCode 等用来告知请求状态的属性一样。IndexedDB 是面向对象的-IndexedDB 并不是一个含有表示行列集合的表关系型数据库。这一巨大的差异影响开发者设计和构建网页程序。IndexedDB 不使用结构型查询语言(SQL)-它在索引上使用查询后会创建一个游标,可以使用该游标来遍历结果集。若不熟悉 NoSQL 系统,可以阅读 维基百科关于 NoSQL 的文章。IndexedDB 也应用了同源策略-一个源即包含域名,应用程序层协议及 URL 端口地址的文档,脚本即在源中执行。每个源都拥有其关联的数据库集。每个数据库在源中都有唯一的标识。IndexedDB 局限性IndexedDB 被设计用来满足大多数的客户端存储情况的。然而,它并没有被设计用来处理如下情况:国际化排序-并不是所有的语言以同样的方式排列字符串,因此国际化排序是不支持的。虽然数据库并不能够以指定的国际化顺序来存储数据,开发者可以读取数据库中数据然后自行排列数据。同步- API 并不是用来和服务端数据进行同步的。开发者必须自己写代码来把客户端 indexedDB 数据库和服务端数据库进行同步。全文检索-该 API 中没有和 SQL 中的 LIKE 类似的操作符。另外,需要注意的是浏览器会在以下情况清除数据库:用户发起清除操作的请求-许多浏览器都允许用户清除指定网站的数据,包括 cookie, 书签,存储的密码以及 IndexedDB 数据。浏览器在隐私模式下-一些浏览器含有『隐私浏览』(Firefox) 和 『无痕浏览』(Chrome)模式。会话结束会清除数据库。超出了磁盘容量或者磁盘限额。数据损坏。。。虽然现实情况和浏览器能力日新月异,但是浏览器产商都朝着尽一切可能保存数据的方向努力。选择合适的存储 API正如之前所说的那样,最好尽可能采用兼容性好的 API 且提供异步调用模型来最大限度地提升 UI 响应速度。这些标准自然而然会产生如下技术选择:使用 Cache API 来操作离线存储。该 API 在创建离线应用所必须的支持 服务工作线程 功能的浏览器中可用。Cache API 非常适用于排列已知 URL 的关联资源。使用 IndexedDB 来存储程序状态和用户生成的内容。和只支持 Cache API 的浏览器相比,这使得用户可以在更多的浏览器中离线使用程序。参考https://developers.google.com…https://developer.mozilla.org...https://developer.mozilla.org…本系列持续更新中,Github 地址请查阅这里。 ...

January 1, 2019 · 2 min · jiezi

COW奶牛!Copy On Write机制了解一下

前言只有光头才能变强在读《Redis设计与实现》关于哈希表扩容的时候,发现这么一段话:执行BGSAVE命令或者BGREWRITEAOF命令的过程中,Redis需要创建当前服务器进程的子进程,而大多数操作系统都采用写时复制(copy-on-write)来优化子进程的使用效率,所以在子进程存在期间,服务器会提高负载因子的阈值,从而避免在子进程存在期间进行哈希表扩展操作,避免不必要的内存写入操作,最大限度地节约内存。触及到知识的盲区了,于是就去搜了一下copy-on-write写时复制这个技术究竟是怎么样的。发现涉及的东西蛮多的,也挺难读懂的。于是就写下这篇笔记来记录一下我学习copy-on-write的过程。本文力求简单讲清copy-on-write这个知识点,希望大家看完能有所收获。一、Linux下的copy-on-write在说明Linux下的copy-on-write机制前,我们首先要知道两个函数:fork()和exec()。需要注意的是exec()并不是一个特定的函数, 它是一组函数的统称, 它包括了execl()、execlp()、execv()、execle()、execve()、execvp()。1.1简单来用用fork首先我们来看一下fork()函数是什么鬼:fork is an operation whereby a process creates a copy of itself.fork是类Unix操作系统上创建进程的主要方法。fork用于创建子进程(等同于当前进程的副本)。新的进程要通过老的进程复制自身得到,这就是fork!如果接触过Linux,我们会知道Linux下init进程是所有进程的爹(相当于Java中的Object对象)Linux的进程都通过init进程或init的子进程fork(vfork)出来的。下面以例子说明一下fork吧:#include <unistd.h> #include <stdio.h> int main () { pid_t fpid; //fpid表示fork函数返回的值 int count=0; // 调用fork,创建出子进程 fpid=fork(); // 所以下面的代码有两个进程执行! if (fpid < 0) printf(“创建进程失败!/n”); else if (fpid == 0) { printf(“我是子进程,由父进程fork出来/n”); count++; } else { printf(“我是父进程/n”); count++; } printf(“统计结果是: %d/n”,count); return 0; } 得到的结果输出为:我是子进程,由父进程fork出来统计结果是: 1我是父进程统计结果是: 1解释一下:fork作为一个函数被调用。这个函数会有两次返回,将子进程的PID返回给父进程,0返回给子进程。(如果小于0,则说明创建子进程失败)。再次说明:当前进程调用fork(),会创建一个跟当前进程完全相同的子进程(除了pid),所以子进程同样是会执行fork()之后的代码。所以说:父进程在执行if代码块的时候,fpid变量的值是子进程的pid子进程在执行if代码块的时候,fpid变量的值是01.2再来看看exec()函数从上面我们已经知道了fork会创建一个子进程。子进程的是父进程的副本。exec函数的作用就是:装载一个新的程序(可执行映像)覆盖当前进程内存空间中的映像,从而执行不同的任务。exec系列函数在执行时会直接替换掉当前进程的地址空间。我去画张图来理解一下:参考资料:程序员必备知识——fork和exec函数详解https://blog.csdn.net/bad_good_man/article/details/49364947linux中fork()函数详解(原创!!实例讲解):https://blog.csdn.net/jason314/article/details/5640969linux c语言 fork() 和 exec 函数的简介和用法:https://blog.csdn.net/nvd11/article/details/8856278Linux下Fork与Exec使用:https://www.cnblogs.com/hicjiajia/archive/2011/01/20/1940154.htmlLinux 系统调用 —— fork()内核源码剖析:https://blog.csdn.net/chen892704067/article/details/765962251.3回头来看Linux下的COW是怎么一回事fork()会产生一个和父进程完全相同的子进程(除了pid)如果按传统的做法,会直接将父进程的数据拷贝到子进程中,拷贝完之后,父进程和子进程之间的数据段和堆栈是相互独立的。但是,以我们的使用经验来说:往往子进程都会执行exec()来做自己想要实现的功能。所以,如果按照上面的做法的话,创建子进程时复制过去的数据是没用的(因为子进程执行exec(),原有的数据会被清空)既然很多时候复制给子进程的数据是无效的,于是就有了Copy On Write这项技术了,原理也很简单:fork创建出的子进程,与父进程共享内存空间。也就是说,如果子进程不对内存空间进行写入操作的话,内存空间中的数据并不会复制给子进程,这样创建子进程的速度就很快了!(不用复制,直接引用父进程的物理空间)。并且如果在fork函数返回之后,子进程第一时间exec一个新的可执行映像,那么也不会浪费时间和内存空间了。另外的表达方式:在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)。而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。Copy On Write技术实现原理:fork()之后,kernel把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。Copy On Write技术好处是什么?COW技术可减少分配和复制大量资源时带来的瞬间延时。COW技术可减少不必要的资源分配。比如fork进程时,并不是所有的页面都需要复制,父进程的代码段和只读数据段都不被允许修改,所以无需复制。Copy On Write技术缺点是什么?如果在fork()之后,父子进程都还需要继续进行写操作,那么会产生大量的分页错误(页异常中断page-fault),这样就得不偿失。几句话总结Linux的Copy On Write技术:fork出的子进程共享父进程的物理空间,当父子进程有内存写入操作时,read-only内存页发生中断,将触发的异常的内存页复制一份(其余的页还是共享父进程的)。fork出的子进程功能实现和父进程是一样的。如果有需要,我们会用exec()把当前进程映像替换成新的进程文件,完成自己想要实现的功能。参考资料:Linux进程基础:http://www.cnblogs.com/vamei/archive/2012/09/20/2694466.htmlLinux写时拷贝技术(copy-on-write)http://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601655.html当你在 Linux 上启动一个进程时会发生什么?https://zhuanlan.zhihu.com/p/33159508Linux fork()所谓的写时复制(COW)到最后还是要先复制再写吗?https://www.zhihu.com/question/265400460写时拷贝(copy-on-write) COW技术https://blog.csdn.net/u012333003/article/details/25117457Copy-On-Write 写时复制原理https://blog.csdn.net/ppppppppp2009/article/details/22750939二、解释一下Redis的COW基于上面的基础,我们应该已经了解COW这么一项技术了。下面我来说一下我对《Redis设计与实现》那段话的理解:Redis在持久化时,如果是采用BGSAVE命令或者BGREWRITEAOF的方式,那Redis会fork出一个子进程来读取数据,从而写到磁盘中。总体来看,Redis还是读操作比较多。如果子进程存在期间,发生了大量的写操作,那可能就会出现很多的分页错误(页异常中断page-fault),这样就得耗费不少性能在复制上。而在rehash阶段上,写操作是无法避免的。所以Redis在fork出子进程之后,将负载因子阈值提高,尽量减少写操作,避免不必要的内存写入操作,最大限度地节约内存。参考资料:fork()后copy on write的一些特性:https://zhoujianshi.github.io/articles/2017/fork()%E5%90%8Ecopy%20on%20write%E7%9A%84%E4%B8%80%E4%BA%9B%E7%89%B9%E6%80%A7/index.html%E5%90%8Ecopy%20on%20write%E7%9A%84%E4%B8%80%E4%BA%9B%E7%89%B9%E6%80%A7/index.html)写时复制:https://miao1007.github.io/gitbook/java/juc/cow/三、文件系统的COW下面来看看文件系统中的COW是啥意思:Copy-on-write在对数据进行修改的时候,不会直接在原来的数据位置上进行操作,而是重新找个位置修改,这样的好处是一旦系统突然断电,重启之后不需要做Fsck。好处就是能保证数据的完整性,掉电的话容易恢复。比如说:要修改数据块A的内容,先把A读出来,写到B块里面去。如果这时候断电了,原来A的内容还在!参考资料:文件系统中的 copy-on-write 模式有什么具体的好处?https://www.zhihu.com/question/19782224/answers/created新一代 Linux 文件系统 btrfs 简介:https://www.ibm.com/developerworks/cn/linux/l-cn-btrfs/最后最后我们再来看一下写时复制的思想(摘录自维基百科):写入时复制(英语:Copy-on-write,简称COW)是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此作法主要的优点是如果调用者没有修改该资源,就不会有副本(private copy)被建立,因此多个调用者只是读取操作时可以共享同一份资源。至少从本文我们可以总结出:Linux通过Copy On Write技术极大地减少了Fork的开销。文件系统通过Copy On Write技术一定程度上保证数据的完整性。其实在Java里边,也有Copy On Write技术。这部分留到下一篇来说,敬请期待~如果大家有更好的理解方式或者文章有错误的地方还请大家不吝在评论区留言,大家互相学习交流~~~参考资料:写时复制,写时拷贝,写时分裂,Copy on write:https://my.oschina.net/dubenju/blog/815836不会产奶的COW(Copy-On-Write)https://www.jianshu.com/p/b2fb2ee5e3a0一个坚持原创的Java技术公众号:Java3y,欢迎大家关注3y所有的原创文章:文章的目录导航(脑图+海量视频资源):https://github.com/ZhongFuCheng3y/3y ...

October 31, 2018 · 1 min · jiezi