├── .gitignore ├── .nojekyll ├── 404.html ├── CNAME ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── NAV.md ├── README.md ├── SUMMARY.md ├── asset ├── docsify-apachecn-footer.js ├── docsify-baidu-push.js ├── docsify-baidu-stat.js ├── docsify-clicker.js ├── docsify-cnzz.js ├── docsify-copy-code.min.js ├── docsify-sidebar-collapse.min.js ├── docsify.min.js ├── flygon_qr_alipay.png ├── prism-darcula.css ├── prism-python.min.js ├── search.min.js ├── style.css └── vue.css ├── docs ├── 1.md ├── 10.md ├── 11.md ├── 12.md ├── 13.md ├── 14.md ├── 15.md ├── 16.md ├── 17.md ├── 18.md ├── 19.md ├── 20.md ├── 21.md ├── 22.md ├── 23.md ├── 24.md ├── 25.md ├── 26.md ├── 27.md ├── 28.md ├── 29.md ├── 3.md ├── 30.md ├── 31.md ├── 32.md ├── 33.md ├── 34.md ├── 35.md ├── 36.md ├── 37.md ├── 38.md ├── 39.md ├── 4.md ├── 40.md ├── 41.md ├── 42.md ├── 43.md ├── 44.md ├── 45.md ├── 46.md ├── 47.md ├── 48.md ├── 49.md ├── 5.md ├── 50.md ├── 51.md ├── 52.md ├── 53.md ├── 54.md ├── 55.md ├── 56.md ├── 57.md ├── 58.md ├── 59.md ├── 6.md ├── 60.md ├── 61.md ├── 62.md ├── 63.md ├── 64.md ├── 65.md ├── 66.md ├── 67.md ├── 68.md ├── 69.md ├── 7.md ├── 70.md ├── 71.md ├── 72.md ├── 73.md ├── 74.md ├── 75.md ├── 76.md ├── 77.md ├── 78.md ├── 79.md ├── 8.md ├── 80.md ├── 81.md ├── 82.md ├── 83.md ├── 84.md ├── 85.md ├── 86.md ├── 87.md ├── 88.md ├── 89.md ├── 9.md ├── 90.md ├── 91.md └── img │ ├── 1d72e84109674f2e5db6da917167668b.jpg │ ├── 1e5467473b254a4f5bb8d50f0f58ed17.jpg │ ├── 1e88c8266f91417008f275b2eec36df7.jpg │ ├── 261964eb034ed12b257f30989e6eb740.jpg │ ├── 27f5f2c018c60372297e6cf5790934bc.jpg │ ├── 2d19ee5fcdb522a5a43850cd1746a70a.jpg │ ├── 332fa7df9feb67953e1554176b74fd84.jpg │ ├── 3332236e74b2fb5bdc6e33bec4ce909e.jpg │ ├── 4013002f4b22a09d0fc6a117c0a29816.jpg │ ├── 70f6ba3a3d379fcd8d3214846a16c410.jpg │ ├── 921a3e0534d1220fea61c43535dadd2c.jpg │ ├── a939273ec986aaa3938cdbe2867a08e2.jpg │ ├── aacd324af23dbabdf0c8511652cfadb2.jpg │ ├── b6620ef95e96fee4fcdff893eabdf95c.jpg │ ├── bddaf711271daa8286dff3dbac48867e.jpg │ ├── bfa1f1caf816f2b844ae5b14da43f11b.svg │ ├── c012049198839822b4b9b3716bf1ddff.jpg │ ├── c17a56237998885b1dd4cd23e86f4c90.jpg │ ├── d478b259062697ec8a19b3fa93d75962.svg │ ├── d5c232c0012ccdb9ff8c9e79b2429cb9.jpg │ ├── d69ff523ab899f8909888c58907d4ca8.jpg │ ├── e209a7428a9ce45094abf36a151c7d63.jpg │ ├── ef4830cf8767dd32936281aca42c8eac.jpg │ └── faa7ac1f5f07a2ceee3dcc5057f329c6.jpg ├── donate.md ├── index.html └── update.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | .DS_Store 103 | 104 | # gitbook 105 | _book 106 | 107 | # node.js 108 | node_modules 109 | 110 | # windows 111 | Thumbs.db 112 | 113 | # word 114 | ~$*.docx 115 | ~$*.doc 116 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/.nojekyll -------------------------------------------------------------------------------- /404.html: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /404.html 3 | --- 4 | 5 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | cs241.apachecn.org -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 贡献指南 2 | 3 | > 请您勇敢地去翻译和改进翻译。虽然我们追求卓越,但我们并不要求您做到十全十美,因此请不要担心因为翻译上犯错——在大部分情况下,我们的服务器已经记录所有的翻译,因此您不必担心会因为您的失误遭到无法挽回的破坏。(改编自维基百科) 4 | 5 | 负责人: 6 | 7 | + [飞龙](https://github.com/wizardforcel):562826179 8 | 9 | ## 有用的链接 10 | 11 | + [ApacheCN 文档导航](https://docs.apachecn.org/) 12 | + [谷歌翻译](https://translate.google.cn/) 13 | + [ApacheCN 校对活动参与手册](https://github.com/apachecn/home/blob/master/docs/translate/joining-guide.md) 14 | + [译后编辑](https://www.bing.com/search?q=%E8%AF%91%E5%90%8E%E7%BC%96%E8%BE%91&mkt=zh-CN) 15 | + [当翻译竟然变成了文本编辑——李笑来](https://zhuanlan.zhihu.com/p/465979584) 16 | + [翻译引擎易错术语列表(欢迎补充)](https://github.com/apachecn/home/blob/master/docs/translate/trans-table.md) 17 | + [廖雪峰 Git 教程](https://www.liaoxuefeng.com/wiki/896043488029600) 18 | 19 | ## 流程 20 | 21 | ### 一、认领 22 | 23 | 校对者需要熟练掌握 Markdown 和 Git,以及文档的主题(编程,Web开发,大数据,AI,安全之一)。 24 | 25 | 首先查看[整体进度](https://github.com/apachecn/ds-cmd-line-2e-zh/issues/1),确认没有人认领了你想认领的章节。 26 | 27 | 然后回复 ISSUE,注明“章节 + QQ 号”,便于联系和跟踪进度。 28 | 29 | ### 二、校对 30 | 31 | 需要校对【专业术语】和【格式】。 32 | 33 | 【语法】无需校对因为已经校对完了,并且请最大程度保留原文的语言风格。 34 | 35 | 译文在`docs`目录下,原文请见每个文章开头处的链接。 36 | 37 | **注意**:不要修改译文的文件名,因为它们和章节对应! 38 | 39 | 确保译文符合下方的【Markdown 排版要求】一节。 40 | 41 | 请参考下方的【有用的正则表达式】一节,以及[【翻译引擎易错术语列表】](https://github.com/apachecn/home/blob/master/docs/translate/trans-table.md)来提高效率。 42 | 43 | ### 三、提交 44 | 45 | + `fork` Github 项目 46 | + 在`docs`文件夹下编辑译文 47 | + `add`、`commit`和`push` 48 | + `pull request` 49 | 50 | 请见[廖雪峰 Git 教程](https://www.liaoxuefeng.com/wiki/896043488029600)。 51 | 52 | ## Markdown 排版要求 53 | 54 | 1. 代码块和图片无需校对,并且不计入字数。 55 | 3. 汉字和英文字母,汉字和数字之间空一格。但是中文标点和任何字符之间都不用空格。 56 | 4. 粗体斜体和链接要求同上,中文和英文粗体,英文和中文粗体之间也需要空格。 57 | 5. 任何编程语言中出现的东西,比如变量名,类名,函数名,包名,以及命令行中出现的东西,比如命令,文件名,路径,扩展名,都需要包在内联代码中。内联代码与汉字/标点之间无需空格,但和英文字母或数字之间空一格。 58 | 6. 表格的格式容易乱,保证它们显示正常。 59 | 7. 标题和较短的列表需要特别校对。 60 | 8. 有少量未翻译的段落,使用[谷歌翻译](https://translate.google.cn/)之后再校对。 61 | 62 | ## 有用的正则表达式 63 | 64 | 链接: 65 | 66 | ``` 67 | (? 原文:[angrave/SystemProgramming/wiki](https://github.com/angrave/SystemProgramming/wiki) 4 | > 5 | > 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) 6 | > 7 | > 阶段:机翻(1) 8 | > 9 | > 真正的程序员在冒险和搞事时脑子最聪明。 10 | 11 | * [在线阅读](https://cs241.apachecn.org) 12 | * [在线阅读(Gitee)](https://apachecn.gitee.io/uiuc-cs241-notes-zh/) 13 | * [ApacheCN 面试求职交流群 724187166](https://jq.qq.com/?_wv=1027&k=54ujcL3) 14 | * [ApacheCN 学习资源](http://www.apachecn.org/) 15 | 16 | ## 贡献指南 17 | 18 | 项目当前处于校对阶段,请查看[贡献指南](CONTRIBUTING.md),并在[整体进度](https://github.com/apachecn/uiuc-cs241-notes-zh/issues/1)中领取任务。 19 | 20 | > 请您勇敢地去翻译和改进翻译。虽然我们追求卓越,但我们并不要求您做到十全十美,因此请不要担心因为翻译上犯错——在大部分情况下,我们的服务器已经记录所有的翻译,因此您不必担心会因为您的失误遭到无法挽回的破坏。(改编自维基百科) 21 | 22 | ## 联系方式 23 | 24 | ### 负责人 25 | 26 | * [飞龙](https://github.com/wizardforcel): 562826179 27 | 28 | ### 其他 29 | 30 | * 认领翻译和项目进度-地址: 31 | * 在我们的 [apachecn/uiuc-cs241-notes-zh](https://github.com/apachecn/uiuc-cs241-notes-zh) github 上提 issue. 32 | * 发邮件到 Email: `apachecn@163.com`. 33 | * 在我们的 [组织学习交流群](http://www.apachecn.org/organization/348.html) 中联系群主/管理员即可. 34 | 35 | ## 下载 36 | 37 | ### Docker 38 | 39 | ``` 40 | docker pull apachecn0/uiuc-cs241-notes-zh 41 | docker run -tid -p :80 apachecn0/uiuc-cs241-notes-zh 42 | # 访问 http://localhost:{port} 查看文档 43 | ``` 44 | 45 | ### PYPI 46 | 47 | ``` 48 | pip install uiuc-cs241-notes-zh 49 | uiuc-cs241-notes-zh 50 | # 访问 http://localhost:{port} 查看文档 51 | ``` 52 | 53 | ### NPM 54 | 55 | ``` 56 | npm install -g uiuc-csersiyi-notes-zh 57 | uiuc-csersiyi-notes-zh 58 | # 访问 http://localhost:{port} 查看文档 59 | ``` 60 | 61 | ## 赞助我们 62 | 63 | 本项目由[飞龙](https://github.com/wizardforcel)赞助,扫下面的二维码打赏他来表示感谢: 64 | 65 | ![](asset/flygon_qr_alipay.png) 66 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | + [UIUC CS241 系统编程中文讲义](README.md) 2 | + [0\. 简介](docs/1.md) 3 | + [#Informal 词汇表](docs/3.md) 4 | + [#Piazza:何时以及如何寻求帮助](docs/4.md) 5 | + [编程技巧,第 1 部分](docs/5.md) 6 | + [系统编程短篇小说和歌曲](docs/6.md) 7 | + [1.学习 C](docs/7.md) 8 | + [C 编程,第 1 部分:简介](docs/8.md) 9 | + [C 编程,第 2 部分:文本输入和输出](docs/9.md) 10 | + [C 编程,第 3 部分:常见问题](docs/10.md) 11 | + [C 编程,第 4 部分:字符串和结构](docs/11.md) 12 | + [C 编程,第 5 部分:调试](docs/12.md) 13 | + [C 编程,复习题](docs/13.md) 14 | + [2.进程](docs/14.md) 15 | + [进程,第 1 部分:简介](docs/15.md) 16 | + [分叉,第 1 部分:简介](docs/16.md) 17 | + [分叉,第 2 部分:Fork,Exec,等等](docs/17.md) 18 | + [进程控制,第 1 部分:使用信号等待宏](docs/18.md) 19 | + [进程复习题](docs/19.md) 20 | + [3.内存和分配器](docs/20.md) 21 | + [内存,第 1 部分:堆内存简介](docs/21.md) 22 | + [内存,第 2 部分:实现内存分配器](docs/22.md) 23 | + [内存,第 3 部分:粉碎堆栈示例](docs/23.md) 24 | + [内存复习题](docs/24.md) 25 | + [4.介绍 Pthreads](docs/25.md) 26 | + [Pthreads,第 1 部分:简介](docs/26.md) 27 | + [Pthreads,第 2 部分:实践中的用法](docs/27.md) 28 | + [Pthreads,第 3 部分:并行问题(奖金)](docs/28.md) 29 | + [Pthread 复习题](docs/29.md) 30 | + [5.同步](docs/30.md) 31 | + [同步,第 1 部分:互斥锁](docs/31.md) 32 | + [同步,第 2 部分:计算信号量](docs/32.md) 33 | + [同步,第 3 部分:使用互斥锁和信号量](docs/33.md) 34 | + [同步,第 4 部分:临界区问题](docs/34.md) 35 | + [同步,第 5 部分:条件变量](docs/35.md) 36 | + [同步,第 6 部分:实现障碍](docs/36.md) 37 | + [同步,第 7 部分:读者编写器问题](docs/37.md) 38 | + [同步,第 8 部分:环形缓冲区示例](docs/38.md) 39 | + [同步复习题](docs/39.md) 40 | + [6.死锁](docs/40.md) 41 | + [死锁,第 1 部分:资源分配图](docs/41.md) 42 | + [死锁,第 2 部分:死锁条件](docs/42.md) 43 | + [死锁,第 3 部分:餐饮哲学家](docs/43.md) 44 | + [死锁复习题](docs/44.md) 45 | + [7.进程间通信&调度](docs/45.md) 46 | + [虚拟内存,第 1 部分:虚拟内存简介](docs/46.md) 47 | + [管道,第 1 部分:管道介绍](docs/47.md) 48 | + [管道,第 2 部分:管道编程秘密](docs/48.md) 49 | + [文件,第 1 部分:使用文件](docs/49.md) 50 | + [调度,第 1 部分:调度过程](docs/50.md) 51 | + [调度,第 2 部分:调度过程:算法](docs/51.md) 52 | + [IPC 复习题](docs/52.md) 53 | + [8.网络](docs/53.md) 54 | + [POSIX,第 1 部分:错误处理](docs/54.md) 55 | + [网络,第 1 部分:简介](docs/55.md) 56 | + [网络,第 2 部分:使用 getaddrinfo](docs/56.md) 57 | + [网络,第 3 部分:构建一个简单的 TCP 客户端](docs/57.md) 58 | + [网络,第 4 部分:构建一个简单的 TCP 服务器](docs/58.md) 59 | + [网络,第 5 部分:关闭端口,重用端口和其他技巧](docs/59.md) 60 | + [网络,第 6 部分:创建 UDP 服务器](docs/60.md) 61 | + [网络,第 7 部分:非阻塞 I O,select()和 epoll](docs/61.md) 62 | + [RPC,第 1 部分:远程过程调用简介](docs/62.md) 63 | + [网络复习题](docs/63.md) 64 | + [9.文件系统](docs/64.md) 65 | + [文件系统,第 1 部分:简介](docs/65.md) 66 | + [文件系统,第 2 部分:文件是 inode(其他一切只是数据...)](docs/66.md) 67 | + [文件系统,第 3 部分:权限](docs/67.md) 68 | + [文件系统,第 4 部分:使用目录](docs/68.md) 69 | + [文件系统,第 5 部分:虚拟文件系统](docs/69.md) 70 | + [文件系统,第 6 部分:内存映射文件和共享内存](docs/70.md) 71 | + [文件系统,第 7 部分:可扩展且可靠的文件系统](docs/71.md) 72 | + [文件系统,第 8 部分:从 Android 设备中删除预装的恶意软件](docs/72.md) 73 | + [文件系统,第 9 部分:磁盘块示例](docs/73.md) 74 | + [文件系统复习题](docs/74.md) 75 | + [10.信号](docs/75.md) 76 | + [过程控制,第 1 部分:使用信号等待宏](docs/76.md) 77 | + [信号,第 2 部分:待处理的信号和信号掩码](docs/77.md) 78 | + [信号,第 3 部分:提高信号](docs/78.md) 79 | + [信号,第 4 部分:信号](docs/79.md) 80 | + [信号复习题](docs/80.md) 81 | + [考试练习题](docs/81.md) 82 | + [考试主题](docs/82.md) 83 | + [C 编程:复习题](docs/83.md) 84 | + [多线程编程:复习题](docs/84.md) 85 | + [同步概念:复习题](docs/85.md) 86 | + [记忆:复习题](docs/86.md) 87 | + [管道:复习题](docs/87.md) 88 | + [文件系统:复习题](docs/88.md) 89 | + [网络:复习题](docs/89.md) 90 | + [信号:复习题](docs/90.md) 91 | + [系统编程笑话](docs/91.md) -------------------------------------------------------------------------------- /asset/docsify-apachecn-footer.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var footer = [ 3 | '
', 4 | '
', 5 | '

我们一直在努力

', 6 | '

apachecn/uiuc-cs241-notes-zh

', 7 | '

', 8 | ' ', 9 | ' ', 10 | ' ML | ApacheCN

', 11 | '

', 12 | '
', 13 | ' ', 17 | '
', 18 | '
' 19 | ].join('\n') 20 | var plugin = function(hook) { 21 | hook.afterEach(function(html) { 22 | return html + footer 23 | }) 24 | hook.doneEach(function() { 25 | (adsbygoogle = window.adsbygoogle || []).push({}) 26 | }) 27 | } 28 | var plugins = window.$docsify.plugins || [] 29 | plugins.push(plugin) 30 | window.$docsify.plugins = plugins 31 | })() -------------------------------------------------------------------------------- /asset/docsify-baidu-push.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var plugin = function(hook) { 3 | hook.doneEach(function() { 4 | new Image().src = 5 | '//api.share.baidu.com/s.gif?r=' + 6 | encodeURIComponent(document.referrer) + 7 | "&l=" + encodeURIComponent(location.href) 8 | }) 9 | } 10 | var plugins = window.$docsify.plugins || [] 11 | plugins.push(plugin) 12 | window.$docsify.plugins = plugins 13 | })() -------------------------------------------------------------------------------- /asset/docsify-baidu-stat.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var plugin = function(hook) { 3 | hook.doneEach(function() { 4 | window._hmt = window._hmt || [] 5 | var hm = document.createElement("script") 6 | hm.src = "https://hm.baidu.com/hm.js?" + window.$docsify.bdStatId 7 | document.querySelector("article").appendChild(hm) 8 | }) 9 | } 10 | var plugins = window.$docsify.plugins || [] 11 | plugins.push(plugin) 12 | window.$docsify.plugins = plugins 13 | })() -------------------------------------------------------------------------------- /asset/docsify-cnzz.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var plugin = function(hook) { 3 | hook.doneEach(function() { 4 | var sc = document.createElement('script') 5 | sc.src = 'https://s5.cnzz.com/z_stat.php?id=' + 6 | window.$docsify.cnzzId + '&online=1&show=line' 7 | document.querySelector('article').appendChild(sc) 8 | }) 9 | } 10 | var plugins = window.$docsify.plugins || [] 11 | plugins.push(plugin) 12 | window.$docsify.plugins = plugins 13 | })() -------------------------------------------------------------------------------- /asset/docsify-copy-code.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * docsify-copy-code 3 | * v2.1.0 4 | * https://github.com/jperasmus/docsify-copy-code 5 | * (c) 2017-2019 JP Erasmus 6 | * MIT license 7 | */ 8 | !function(){"use strict";function r(o){return(r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(o){return typeof o}:function(o){return o&&"function"==typeof Symbol&&o.constructor===Symbol&&o!==Symbol.prototype?"symbol":typeof o})(o)}!function(o,e){void 0===e&&(e={});var t=e.insertAt;if(o&&"undefined"!=typeof document){var n=document.head||document.getElementsByTagName("head")[0],c=document.createElement("style");c.type="text/css","top"===t&&n.firstChild?n.insertBefore(c,n.firstChild):n.appendChild(c),c.styleSheet?c.styleSheet.cssText=o:c.appendChild(document.createTextNode(o))}}(".docsify-copy-code-button,.docsify-copy-code-button span{cursor:pointer;transition:all .25s ease}.docsify-copy-code-button{position:absolute;z-index:1;top:0;right:0;overflow:visible;padding:.65em .8em;border:0;border-radius:0;outline:0;font-size:1em;background:grey;background:var(--theme-color,grey);color:#fff;opacity:0}.docsify-copy-code-button span{border-radius:3px;background:inherit;pointer-events:none}.docsify-copy-code-button .error,.docsify-copy-code-button .success{position:absolute;z-index:-100;top:50%;left:0;padding:.5em .65em;font-size:.825em;opacity:0;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.docsify-copy-code-button.error .error,.docsify-copy-code-button.success .success{opacity:1;-webkit-transform:translate(-115%,-50%);transform:translate(-115%,-50%)}.docsify-copy-code-button:focus,pre:hover .docsify-copy-code-button{opacity:1}"),document.querySelector('link[href*="docsify-copy-code"]')&&console.warn("[Deprecation] Link to external docsify-copy-code stylesheet is no longer necessary."),window.DocsifyCopyCodePlugin={init:function(){return function(o,e){o.ready(function(){console.warn("[Deprecation] Manually initializing docsify-copy-code using window.DocsifyCopyCodePlugin.init() is no longer necessary.")})}}},window.$docsify=window.$docsify||{},window.$docsify.plugins=[function(o,s){o.doneEach(function(){var o=Array.apply(null,document.querySelectorAll("pre[data-lang]")),c={buttonText:"Copy to clipboard",errorText:"Error",successText:"Copied"};s.config.copyCode&&Object.keys(c).forEach(function(t){var n=s.config.copyCode[t];"string"==typeof n?c[t]=n:"object"===r(n)&&Object.keys(n).some(function(o){var e=-1',''.concat(c.buttonText,""),''.concat(c.errorText,""),''.concat(c.successText,""),""].join("");o.forEach(function(o){o.insertAdjacentHTML("beforeend",e)})}),o.mounted(function(){document.querySelector(".content").addEventListener("click",function(o){if(o.target.classList.contains("docsify-copy-code-button")){var e="BUTTON"===o.target.tagName?o.target:o.target.parentNode,t=document.createRange(),n=e.parentNode.querySelector("code"),c=window.getSelection();t.selectNode(n),c.removeAllRanges(),c.addRange(t);try{document.execCommand("copy")&&(e.classList.add("success"),setTimeout(function(){e.classList.remove("success")},1e3))}catch(o){console.error("docsify-copy-code: ".concat(o)),e.classList.add("error"),setTimeout(function(){e.classList.remove("error")},1e3)}"function"==typeof(c=window.getSelection()).removeRange?c.removeRange(t):"function"==typeof c.removeAllRanges&&c.removeAllRanges()}})})}].concat(window.$docsify.plugins||[])}(); 9 | //# sourceMappingURL=docsify-copy-code.min.js.map 10 | -------------------------------------------------------------------------------- /asset/docsify-sidebar-collapse.min.js: -------------------------------------------------------------------------------- 1 | !function(e){("object"!=typeof exports||"undefined"==typeof module)&&"function"==typeof define&&define.amd?define(e):e()}(function(){"use strict";function e(e,n){var t,a=(n=void 0===n?{}:n).insertAt;e&&"undefined"!=typeof document&&(t=document.head||document.getElementsByTagName("head")[0],(n=document.createElement("style")).type="text/css","top"===a&&t.firstChild?t.insertBefore(n,t.firstChild):t.appendChild(n),n.styleSheet?n.styleSheet.cssText=e:n.appendChild(document.createTextNode(e)))}var t;function a(e){e&&null!=t&&(e=e.getBoundingClientRect().top,document.querySelector(".sidebar").scrollBy(0,e-t))}function n(){requestAnimationFrame(function(){var e=document.querySelector(".app-sub-sidebar > .active");if(e)for(e.parentNode.parentNode.querySelectorAll(".app-sub-sidebar").forEach(function(e){return e.classList.remove("open")});e.parentNode.classList.contains("app-sub-sidebar")&&!e.parentNode.classList.contains("open");)e.parentNode.classList.add("open"),e=e.parentNode})}function o(e){t=e.target.getBoundingClientRect().top;var n=d(e.target,"LI",2);n&&(n.classList.contains("open")?(n.classList.remove("open"),setTimeout(function(){n.classList.add("collapse")},0)):(function(e){if(e)for(e.classList.remove("open","active");e&&"sidebar-nav"!==e.className&&e.parentNode;)"LI"!==e.parentNode.tagName&&"app-sub-sidebar"!==e.parentNode.className||e.parentNode.classList.remove("open"),e=e.parentNode}(s()),i(n),setTimeout(function(){n.classList.remove("collapse")},0)),a(n))}function s(){var e=document.querySelector(".sidebar-nav .active");return e||(e=d(document.querySelector('.sidebar-nav a[href="'.concat(decodeURIComponent(location.hash).replace(/ /gi,"%20"),'"]')),"LI",2))&&e.classList.add("active"),e}function i(e){if(e)for(e.classList.add("open","active");e&&"sidebar-nav"!==e.className&&e.parentNode;)"LI"!==e.parentNode.tagName&&"app-sub-sidebar"!==e.parentNode.className||e.parentNode.classList.add("open"),e=e.parentNode}function d(e,n,t){if(e&&e.tagName===n)return e;for(var a=0;e;){if(t<++a)return;if(e.parentNode.tagName===n)return e.parentNode;e=e.parentNode}}e(".sidebar-nav > ul > li ul {\n display: none;\n}\n\n.app-sub-sidebar {\n display: none;\n}\n\n.app-sub-sidebar.open {\n display: block;\n}\n\n.sidebar-nav .open > ul:not(.app-sub-sidebar),\n.sidebar-nav .active:not(.collapse) > ul {\n display: block;\n}\n\n/* 抖动 */\n.sidebar-nav li.open:not(.collapse) > ul {\n display: block;\n}\n\n.active + ul.app-sub-sidebar {\n display: block;\n}\n"),document.addEventListener("scroll",n);e("@media screen and (max-width: 768px) {\n /* 移动端适配 */\n .markdown-section {\n max-width: none;\n padding: 16px;\n }\n /* 改变原来按钮热区大小 */\n .sidebar-toggle {\n padding: 0 0 10px 10px;\n }\n /* my pin */\n .sidebar-pin {\n appearance: none;\n outline: none;\n position: fixed;\n bottom: 0;\n border: none;\n width: 40px;\n height: 40px;\n background: transparent;\n }\n}\n");var r,c="DOCSIFY_SIDEBAR_PIN_FLAG";function l(){var e="true"===(e=localStorage.getItem(c));localStorage.setItem(c,!e),e?(document.querySelector(".sidebar").style.transform="translateX(0)",document.querySelector(".content").style.transform="translateX(0)"):(document.querySelector(".sidebar").style.transform="translateX(300px)",document.querySelector(".content").style.transform="translateX(300px)")}768 ul"),1),a(t),n(e)}),e.ready(function(){document.querySelector(".sidebar-nav").addEventListener("click",o)})})}); -------------------------------------------------------------------------------- /asset/flygon_qr_alipay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/asset/flygon_qr_alipay.png -------------------------------------------------------------------------------- /asset/prism-darcula.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Darcula theme 3 | * 4 | * Adapted from a theme based on: 5 | * IntelliJ Darcula Theme (https://github.com/bulenkov/Darcula) 6 | * 7 | * @author Alexandre Paradis 8 | * @version 1.0 9 | */ 10 | 11 | code[class*="lang-"], 12 | pre[data-lang] { 13 | color: #a9b7c6 !important; 14 | background-color: #2b2b2b !important; 15 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 16 | direction: ltr; 17 | text-align: left; 18 | white-space: pre; 19 | word-spacing: normal; 20 | word-break: normal; 21 | line-height: 1.5; 22 | 23 | -moz-tab-size: 4; 24 | -o-tab-size: 4; 25 | tab-size: 4; 26 | 27 | -webkit-hyphens: none; 28 | -moz-hyphens: none; 29 | -ms-hyphens: none; 30 | hyphens: none; 31 | } 32 | 33 | pre[data-lang]::-moz-selection, pre[data-lang] ::-moz-selection, 34 | code[class*="lang-"]::-moz-selection, code[class*="lang-"] ::-moz-selection { 35 | color: inherit; 36 | background: rgba(33, 66, 131, .85); 37 | } 38 | 39 | pre[data-lang]::selection, pre[data-lang] ::selection, 40 | code[class*="lang-"]::selection, code[class*="lang-"] ::selection { 41 | color: inherit; 42 | background: rgba(33, 66, 131, .85); 43 | } 44 | 45 | /* Code blocks */ 46 | pre[data-lang] { 47 | padding: 1em; 48 | margin: .5em 0; 49 | overflow: auto; 50 | } 51 | 52 | :not(pre) > code[class*="lang-"], 53 | pre[data-lang] { 54 | background: #2b2b2b; 55 | } 56 | 57 | /* Inline code */ 58 | :not(pre) > code[class*="lang-"] { 59 | padding: .1em; 60 | border-radius: .3em; 61 | } 62 | 63 | .token.comment, 64 | .token.prolog, 65 | .token.cdata { 66 | color: #808080; 67 | } 68 | 69 | .token.delimiter, 70 | .token.boolean, 71 | .token.keyword, 72 | .token.selector, 73 | .token.important, 74 | .token.atrule { 75 | color: #cc7832; 76 | } 77 | 78 | .token.operator, 79 | .token.punctuation, 80 | .token.attr-name { 81 | color: #a9b7c6; 82 | } 83 | 84 | .token.tag, 85 | .token.tag .punctuation, 86 | .token.doctype, 87 | .token.builtin { 88 | color: #e8bf6a; 89 | } 90 | 91 | .token.entity, 92 | .token.number, 93 | .token.symbol { 94 | color: #6897bb; 95 | } 96 | 97 | .token.property, 98 | .token.constant, 99 | .token.variable { 100 | color: #9876aa; 101 | } 102 | 103 | .token.string, 104 | .token.char { 105 | color: #6a8759; 106 | } 107 | 108 | .token.attr-value, 109 | .token.attr-value .punctuation { 110 | color: #a5c261; 111 | } 112 | 113 | .token.attr-value .punctuation:first-child { 114 | color: #a9b7c6; 115 | } 116 | 117 | .token.url { 118 | color: #287bde; 119 | text-decoration: underline; 120 | } 121 | 122 | .token.function { 123 | color: #ffc66d; 124 | } 125 | 126 | .token.regex { 127 | background: #364135; 128 | } 129 | 130 | .token.bold { 131 | font-weight: bold; 132 | } 133 | 134 | .token.italic { 135 | font-style: italic; 136 | } 137 | 138 | .token.inserted { 139 | background: #294436; 140 | } 141 | 142 | .token.deleted { 143 | background: #484a4a; 144 | } 145 | 146 | code.lang-css .token.property, 147 | code.lang-css .token.property + .token.punctuation { 148 | color: #a9b7c6; 149 | } 150 | 151 | code.lang-css .token.id { 152 | color: #ffc66d; 153 | } 154 | 155 | code.lang-css .token.selector > .token.class, 156 | code.lang-css .token.selector > .token.attribute, 157 | code.lang-css .token.selector > .token.pseudo-class, 158 | code.lang-css .token.selector > .token.pseudo-element { 159 | color: #ffc66d; 160 | } -------------------------------------------------------------------------------- /asset/prism-python.min.js: -------------------------------------------------------------------------------- 1 | Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},"string-interpolation":{pattern:/(?:f|rf|fr)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:{{)*){(?!{)(?:[^{}]|{(?!{)(?:[^{}]|{(?!{)(?:[^{}])+})+})+}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|rb|br)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|rb|br)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^\s*)@\w+(?:\.\w+)*/im,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:True|False|None)\b/,number:/(?:\b(?=\d)|\B(?=\.))(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python; -------------------------------------------------------------------------------- /asset/style.css: -------------------------------------------------------------------------------- 1 | /*隐藏头部的目录*/ 2 | #main>ul:nth-child(1) { 3 | display: none; 4 | } 5 | 6 | #main>ul:nth-child(2) { 7 | display: none; 8 | } 9 | 10 | .markdown-section h1 { 11 | margin: 3rem 0 2rem 0; 12 | } 13 | 14 | .markdown-section h2 { 15 | margin: 2rem 0 1rem; 16 | } 17 | 18 | img, 19 | pre { 20 | border-radius: 8px; 21 | } 22 | 23 | .content, 24 | .sidebar, 25 | .markdown-section, 26 | body, 27 | .search input { 28 | background-color: rgba(243, 242, 238, 1) !important; 29 | } 30 | 31 | @media (min-width:600px) { 32 | .sidebar-toggle { 33 | background-color: #f3f2ee; 34 | } 35 | } 36 | 37 | .docsify-copy-code-button { 38 | background: #f8f8f8 !important; 39 | color: #7a7a7a !important; 40 | } 41 | 42 | body { 43 | /*font-family: Microsoft YaHei, Source Sans Pro, Helvetica Neue, Arial, sans-serif !important;*/ 44 | } 45 | 46 | .markdown-section>p { 47 | font-size: 16px !important; 48 | } 49 | 50 | .markdown-section pre>code { 51 | font-family: Consolas, Roboto Mono, Monaco, courier, monospace !important; 52 | font-size: .9rem !important; 53 | 54 | } 55 | 56 | /*.anchor span { 57 | color: rgb(66, 185, 131); 58 | }*/ 59 | 60 | section.cover h1 { 61 | margin: 0; 62 | } 63 | 64 | body>section>div.cover-main>ul>li>a { 65 | color: #42b983; 66 | } 67 | 68 | .markdown-section img { 69 | box-shadow: 7px 9px 10px #aaa !important; 70 | } 71 | 72 | 73 | pre { 74 | background-color: #f3f2ee !important; 75 | } 76 | 77 | @media (min-width:600px) { 78 | pre code { 79 | /*box-shadow: 2px 1px 20px 2px #aaa;*/ 80 | /*border-radius: 10px !important;*/ 81 | padding-left: 20px !important; 82 | } 83 | } 84 | 85 | @media (max-width:600px) { 86 | pre { 87 | padding-left: 0px !important; 88 | padding-right: 0px !important; 89 | } 90 | } 91 | 92 | .markdown-section pre { 93 | padding-left: 0 !important; 94 | padding-right: 0px !important; 95 | box-shadow: 2px 1px 20px 2px #aaa; 96 | } -------------------------------------------------------------------------------- /docs/1.md: -------------------------------------------------------------------------------- 1 | # 0\. 简介 -------------------------------------------------------------------------------- /docs/13.md: -------------------------------------------------------------------------------- 1 | # C 编程,复习题 2 | 3 | > 原文:[Processes, Part 1: Introduction](https://github.com/angrave/SystemProgramming/wiki/Processes%2C-Part-1%3A-Introduction) 4 | 5 | > 校验:[_stund](https://github.com/hqiwen) 6 | 7 | > 自豪地采用[谷歌翻译](https://translate.google.cn/) 8 | 9 | ## 话题 10 | 11 | * C 字符串表示 12 | * C 字符串作为指针 13 | * char p [] vs char * p 14 | * 简单的 C 字符串函数(strcmp,strcat,strcpy) 15 | * sizeof char 16 | * sizeof x vs x * 17 | * 堆内存寿命 18 | * 调用堆分配 19 | * 取消引用指针 20 | * 操作符的地址 21 | * 指针算术 22 | * 字符串重复 23 | * 字符串截断 24 | * 双重释放错误 25 | * 字符串文字 26 | * 格式化打印 27 | * 内存超出界限错误 28 | * 静态内存 29 | * 文件io POSIX v C 库 30 | * C io fprintf 和 printf 31 | * POSIX 文件 io(读|写|打开) 32 | * stdout的缓冲 33 | 34 | ## 问题/练习 35 | 36 | * 以下是什么打印出来的 37 | 38 | ```c 39 | int main(){ 40 | fprintf(stderr, "Hello "); 41 | fprintf(stdout, "It's a small "); 42 | fprintf(stderr, "World\n"); 43 | fprintf(stdout, "place\n"); 44 | return 0; 45 | } 46 | ``` 47 | 48 | * 以下两个声明之间有什么区别?其中一个`sizeof`会返回什么? 49 | 50 | ```c 51 | char str1[] = "bhuvan"; 52 | char *str2 = "another one"; 53 | ``` 54 | 55 | * c 中的字符串是什么? 56 | * 编码一个简单的`my_strcmp`。 `my_strcat`,`my_strcpy`或`my_strdup`怎么样?额外奖励:编写的函数仅需遍历一遍字符串。 57 | * 以下通常应该返回什么? 58 | 59 | ```c 60 | int *ptr; 61 | sizeof(ptr); 62 | sizeof(*ptr); 63 | ``` 64 | 65 | * 什么是`malloc`?它与`calloc`有何不同?一旦内存被`malloc`编辑,我该如何使用`realloc`? 66 | * 什么是`&`运算符? `*`怎么样? 67 | * 指针算术。假设以下地址。有以下几种变化? 68 | 69 | ```c 70 | char** ptr = malloc(10); //0x100 71 | ptr[0] = malloc(20); //0x200 72 | ptr[1] = malloc(20); //0x300 73 | ``` 74 | 75 | ``` 76 | * `ptr + 2` 77 | * `ptr + 4` 78 | * `ptr[0] + 4` 79 | * `ptr[1] + 2000` 80 | * `*((int)(ptr + 1)) + 3` 81 | ``` 82 | 83 | * 我们如何防止双重释放错误? 84 | * 什么是打印字符串,`int`或`char`的 printf 说明符? 85 | * 以下代码是否有效?如果是这样,为什么? `output`在哪里? 86 | 87 | ```c 88 | char *foo(int var){ 89 | static char output[20]; 90 | snprintf(output, 20, "%d", var); 91 | return output; 92 | } 93 | ``` 94 | 95 | * 编写一个函数,该函数接受一个字符串并打开该文件,第一次打印出文件的40个字节,但其他每次打印都会反转该字符串(请尝试使用POSIX API) 96 | * POSIX filedescriptor 模型和 C `FILE*`之间有什么区别(即使用了哪些函数调用,哪些是缓冲的)? POSIX 是否在内部使用 C `FILE*`,反之亦然? -------------------------------------------------------------------------------- /docs/14.md: -------------------------------------------------------------------------------- 1 | # 2.进程 -------------------------------------------------------------------------------- /docs/15.md: -------------------------------------------------------------------------------- 1 | # 进程,第 1 部分:简介 2 | 3 | > 原文:[Processes, Part 1: Introduction](https://github.com/angrave/SystemProgramming/wiki/Processes%2C-Part-1%3A-Introduction) 4 | 5 | > 校验:[飞龙](https://github.com/wizardforcel) 6 | 7 | > 自豪地采用[谷歌翻译](https://translate.google.cn/) 8 | 9 | ## 概览 10 | 11 | 进程是运行中的程序(大致)。进程也只是这个运行中的计算机程序的一个实例。进程有很多东西可供使用。在每个程序启动时,您将获得一个进程,但每个程序可以创建更多进程。实际上,您的操作系统只启动了一个进程,所有其他进程都是由它分叉的 - 所有这些都是在启动时在背后完成的。 12 | 13 | ## 好吧,但是什么是程序? 14 | 15 | 程序通常包含以下内容 16 | 17 | + 二进制格式:告诉操作系统二进制中的哪些位集是什么 -- 哪个部分是可执行的,哪些部分是常量,要包括哪些库等。 18 | + 一套机器指令 19 | + 表示从哪条指令开始的数字 20 | + 常量 21 | + 要链接的库以及在何处填写这些库的地址 22 | 23 | ## 最开始 24 | 25 | 当您的操作系统在 Linux 机器上启动时,会创建一个名为`init.d`的进程。该进程是个处理信号和中断的特殊进程,和某些内核元素的持久性模块。无论何时想要创建一个新进程,都可以调用`fork`(将在后面的部分中讨论)并使用另一个函数来加载另一个程序。 26 | 27 | ## 进程隔离 28 | 29 | 进程非常强大,但它们是隔离的!这意味着默认情况下,任何进程都无法与另一个进程通信。这非常重要,因为如果你有一个大型系统(比方说 EWS),那么你希望某些进程拥有比普通用户更高的权限(监控,管理员),并且当然不希望普通用户通过有意或无意修改进程,能够搞崩整个系统。 30 | 31 | 如果我在两个不同的终端上运行以下代码, 32 | 33 | ``` 34 | int secrets; //maybe defined in the kernel or else where 35 | secrets++; 36 | printf("%d\n", secrets); 37 | ``` 38 | 39 | 正如你猜测的那样,它们都打印出 1 而不是 2。即使我们改变了代码来做一些真正的黑魔法(除了直接读取内存),也没有办法改变另一个进程的状态(好吧,也许 [DirtyCow](https://en.wikipedia.org/wiki/Dirty_COW) 可以,但这有点太深入了)。 40 | 41 | ## 进程内容 42 | 43 | ## 内存布局 44 | 45 | ![Address Space](img/70f6ba3a3d379fcd8d3214846a16c410.jpg) 46 | 47 | 当进程启动时,它会获得自己的地址空间。意味着每个进程得到: 48 | 49 | * **一个栈**。栈是存储自动变量和函数调用返回地址的位置。每次声明一个新变量时,程序都会向下移动栈指针来保留变量的空间。栈的这一部分是可写的但不可执行。如果栈增长得太远(意味着它增长超出预设边界或与堆相交),您将获得栈溢出,最有可能导致 SEGFAULT 或类似的东西。**栈默认静态分配,意味着只有一定数量的可写空间**。 50 | * **堆**。堆是一个扩展的内存区域。如果你想分配一个大对象,它就在这里。堆从文本段的顶部开始向上增长(有时当你调用`malloc`它要求操作系统向上推动堆边界)。此区域也是可写但不可执行。如果系统受限制或者地址耗尽(在 32 位系统上更常见),则可能会耗尽堆内存。 51 | * **数据段**包含所有全局变量。此部分从 Text 段的末尾开始,并且大小是静态的,因为在编译时已知全局变量。这部分是可写的但不是可执行的,这里没有太过花哨的其他任何东西。 52 | * **文本段**。可以说,这是地址中最重要的部分。这是存储所有代码的地方。由于汇编代码编译为 1 和 0,因此这是存储 1 和 0 的地方。程序计数器在该段中移动来执行指令,并向下一条指令移动。请务必注意,这是代码中唯一的可执行部分。如果您尝试在运行时更改代码,很可能会出现段错误(有很多方法可以解决它,但只是假设它是段错误)。 53 | * 为什么不从零开始?它在这个课程的[范围](https://en.wikipedia.org/wiki/Address_space_layout_randomization)之外,但它是为了安全。 54 | 55 | ## 进程 ID(PID) 56 | 57 | 为了跟踪所有这些进程,您的操作系统为每个进程提供一个编号,该进程称为 PID,即进程 ID。 进程也有一个 ppid,它是父进程 ID 的缩写。 每个进程都有一个父进程,该父进程可以是`init.d`。 58 | 59 | 进程也可以包含: 60 | 61 | + 运行状态 -- 进程是否正在准备,运行,停止,终止等。 62 | + 文件描述符 -- 整数到实际设备(文件,USB 记忆棒,套接字)的映射列表 63 | + 权限 -- 正在运行文件的用户以及进程所属的组。 然后,该进程只对这个用户或组是可接受的,就像打开用户已经设为排他的文件一样。 有一些技巧可以使程序不成为启动该程序的用户,即`sudo`接受一个由用户启动的程序并以`root`身份执行该程序。 64 | + 参数 -- 字符串列表,告诉您的程序要在哪些参数下运行。 65 | + 环境列表 -- 可以修改的格式为`NAME=VALUE`的字符串列表。 66 | -------------------------------------------------------------------------------- /docs/17.md: -------------------------------------------------------------------------------- 1 | # 分叉,第 2 部分:Fork,Exec,等等 2 | 3 | > 原文: 4 | 5 | ## 模式 6 | 7 | ## 以下'exec'示例有什么作用? 8 | 9 | ```c 10 | #include 11 | #include // O_CREAT, O_APPEND etc. defined here 12 | 13 | int main() { 14 | close(1); // close standard out 15 | open("log.txt", O_RDWR | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR); 16 | puts("Captain's log"); 17 | chdir("/usr/include"); 18 | // execl( executable, arguments for executable including program name and NULL at the end) 19 | 20 | execl("/bin/ls", /* Remaining items sent to ls*/ "/bin/ls", ".", (char *) NULL); // "ls ." 21 | perror("exec failed"); 22 | return 0; // Not expected 23 | } 24 | ``` 25 | 26 | 上面的代码中没有错误检查(我们假设 close,open,chdir 等按预期工作)。 27 | 28 | * open:将使用最低可用文件描述符(即 1);如此标准现在转到日志文件。 29 | * chdir:将当前目录更改为/ usr / include 30 | * execl:用/ bin / ls 替换程序映像并调用其 main()方法 31 | * perror:我们不希望到达这里 - 如果我们这样做,那么 exec 就失败了。 32 | 33 | ## 微妙的分叉虫 34 | 35 | 这段代码出了什么问题 36 | 37 | ```c 38 | #include 39 | #define HELLO_NUMBER 10 40 | 41 | int main(){ 42 | pid_t children[HELLO_NUMBER]; 43 | int i; 44 | for(i = 0; i < HELLO_NUMBER; i++){ 45 | pid_t child = fork(); 46 | if(child == -1){ 47 | break; 48 | } 49 | if(child == 0){ //I am the child 50 | execlp("ehco", "echo", "hello", NULL); 51 | } 52 | else{ 53 | children[i] = child; 54 | } 55 | } 56 | 57 | int j; 58 | for(j = 0; j < i; j++){ 59 | waitpid(children[j], NULL, 0); 60 | } 61 | return 0; 62 | } 63 | 64 | ``` 65 | 66 | 我们错误拼写了`ehco`,所以我们不能`exec`它。这是什么意思?我们只是创建了 2 ** 10 个进程,而不是创建 10 个进程,而是对我们的机器进行轰炸。我们怎么能阻止这个?在 exec 之后立即退出,以防 exec 失败,我们不会最终轰炸我们的机器。 67 | 68 | ## 孩子从父母那里继承了什么? 69 | 70 | * 打开文件句柄。如果父母后来寻求回到文件的开头那么这也会影响孩子(反之亦然)。 71 | * 信号处理程序 72 | * 当前的工作目录 73 | * 环境变量 74 | 75 | 有关详细信息,请参见 [fork 手册页](http://linux.die.net/man/2/fork)。 76 | 77 | ## 子进程与父进程有什么不同? 78 | 79 | 进程 ID 不同。在调用`getppid()`的子代中(注意两个'p')将给出与在父代中调用 getpid()相同的结果。有关更多详细信息,请参见 fork 手册页。 80 | 81 | ## 我该如何等待孩子完成? 82 | 83 | 使用`waitpid`或`wait`。父进程将暂停,直到`wait`(或`waitpid`)返回。请注意,这个解释掩盖了重新开始的讨论。 84 | 85 | ## 什么是 fork-exec-wait 模式 86 | 87 | 常见的编程模式是调用`fork`,然后调用`exec`和`wait`。原始进程调用 fork,它创建一个子进程。然后,子进程使用 exec 开始执行新程序。同时父母使用`wait`(或`waitpid`)等待子进程完成。请参阅下面的完整代码示例。 88 | 89 | ## 如何启动同时运行的后台进程? 90 | 91 | 不要等他们!您的父进程可以继续执行代码,而无需等待子进程。注意在实践中,通过在调用 exec 之前调用打开的文件描述符上的`close`,后台进程也可以与父进程和输出流断开连接。 92 | 93 | 但是,在父完成之前完成的子进程可能会变成僵尸。有关更多信息,请参阅僵尸页面。 94 | 95 | ## 植物大战僵尸 96 | 97 | ## 好父母不要让自己的孩子成为僵尸! 98 | 99 | 当一个孩子完成(或终止)时,它仍占用内核进程表中的一个槽。只有当孩子'等待'时,才能再次使用该插槽。 100 | 101 | 一个长期运行的程序可以通过不断创建进程来创建许多僵尸,而不会为它们进行`wait`处理。 102 | 103 | ## 太多僵尸会有什么影响? 104 | 105 | 最终,内核进程表中没有足够的空间来创建新进程。因此`fork()`会失败并且可能使系统难以/不可能使用 - 例如只需登录就需要新的进程! 106 | 107 | ## 系统如何帮助预防僵尸? 108 | 109 | 一旦一个进程完成,它的任何子进程都将被分配给“init” - 第一个进程的 pid 为 1.因此这些孩子会看到 getppid()返回值为 1.这些孤儿最终会完成,并在短时间内成为一个僵尸。幸运的是,init 进程自动等待其所有子进程,从而从系统中删除这些僵尸。 110 | 111 | ## 我该如何预防僵尸? (警告:简化回答) 112 | 113 | 等你的孩子! 114 | 115 | ```c 116 | waitpid(child, &status, 0); // Clean up and wait for my child process to finish. 117 | ``` 118 | 119 | 请注意,我们假设获得 SIGCHLD 事件的唯一原因是孩子已经完成(这不完全正确 - 请参阅手册页以获取更多详细信息)。 120 | 121 | 强大的实现还可以检查中断状态并将上述内容包含在循环中。继续阅读,讨论更强大的实现。 122 | 123 | ## 我怎样才能异步等待使用 SIGCHLD 的孩子? (高级) 124 | 125 | 警告:本节使用的信号尚未完全介绍。当子进程完成时,父进程获取信号 SIGCHLD,因此信号处理程序可以等待进程。稍微简化的版本如下所示。 126 | 127 | ```c 128 | pid_t child; 129 | 130 | void cleanup(int signal) { 131 | int status; 132 | waitpid(child, &status, 0); 133 | write(1,"cleanup!\n",9); 134 | } 135 | int main() { 136 | // Register signal handler BEFORE the child can finish 137 | signal(SIGCHLD, cleanup); // or better - sigaction 138 | child = fork(); 139 | if (child == -1) { exit(EXIT_FAILURE);} 140 | 141 | if (child == 0) { /* I am the child!*/ 142 | // Do background stuff e.g. call exec 143 | } else { /* I'm the parent! */ 144 | sleep(4); // so we can see the cleanup 145 | puts("Parent is done"); 146 | } 147 | return 0; 148 | } 149 | ``` 150 | 151 | 然而,上面的例子忽略了几个微妙的要点: 152 | 153 | * 不止一个孩子可能已经完成但父母只会获得一个 SIGCHLD 信号(信号没有排队) 154 | * 可以出于其他原因发送 SIGCHLD 信号(例如暂时停止子进程) 155 | 156 | 收获僵尸的更强大的代码如下所示。 157 | 158 | ```c 159 | void cleanup(int signal) { 160 | int status; 161 | while (waitpid((pid_t) (-1), 0, WNOHANG) > 0) {} 162 | } 163 | ``` 164 | 165 | ## 那么什么是环境变量? 166 | 167 | 环境变量是系统为所有进程保留的变量。您的系统现在已经设置好了!在 Bash 中,您可以查看其中的一些内容 168 | 169 | ``` 170 | $ echo $HOME 171 | /home/bhuvy 172 | $ echo $PATH 173 | /usr/local/sbin:/usr/bin:... 174 | ``` 175 | 176 | 你会如何在 C / C ++中获得这些?您可以使用`getenv`和`setenv`功能 177 | 178 | ```c 179 | char* home = getenv("HOME"); // Will return /home/bhuvy 180 | setenv("HOME", "/home/bhuvan", 1 /*set overwrite to true*/ ); 181 | ``` 182 | 183 | ## 是的,那么这些环境变量如何对父母/孩子意味着什么呢? 184 | 185 | 那么每个进程都会获得自己的环境变量字典,并将其复制到子进程中。这意味着,如果父级更改其环境变量,则不会将其传输给子级,反之亦然。如果你想用不同于父(或任何其他进程)的环境变量执行程序,这在 fork-exec-wait 三部曲中很重要。 186 | 187 | 例如,您可以编写一个循环遍历所有时区的 C 程序,并执行`date`命令以打印所有本地的日期和时间。环境变量用于各种程序,因此修改它们很重要。 -------------------------------------------------------------------------------- /docs/18.md: -------------------------------------------------------------------------------- 1 | # 进程控制,第 1 部分:使用信号等待宏 2 | 3 | > 原文: 4 | 5 | ## 等待宏 6 | 7 | ## 我可以找出孩子的退出价值吗? 8 | 9 | 您可以找到子项退出值的最低 8 位(`main()`的返回值或`exit()`中包含的值):使用“等待宏” - 通常您将使用“WIFEXITED”和“WEXITSTATUS”。有关更多信息,请参见`wait` / `waitpid`手册页。 10 | 11 | ```c 12 | int status; 13 | pid_t child = fork(); 14 | if (child == -1) return 1; //Failed 15 | if (child > 0) { /* I am the parent - wait for the child to finish */ 16 | pid_t pid = waitpid(child, &status, 0); 17 | if (pid != -1 && WIFEXITED(status)) { 18 | int low8bits = WEXITSTATUS(status); 19 | printf("Process %d returned %d" , pid, low8bits); 20 | } 21 | } else { /* I am the child */ 22 | // do something interesting 23 | execl("/bin/ls", "/bin/ls", ".", (char *) NULL); // "ls ." 24 | } 25 | ``` 26 | 27 | 一个进程只能有 256 个返回值,其余的位是信息性的。 28 | 29 | ## 位移 30 | 31 | 请注意,无需记住这一点,这只是对状态变量中信息存储方式的高级概述 32 | 33 | [Android 源代码](https://android.googlesource.com/platform/prebuilts/gcc/linuxx86/host/i686-linux-glibc2.7-%0A4.6/+/tools_r20/sysroot/usr/include/bits/waitstatus.h) 34 | 35 | / *如果是 WIFEXITED(STATUS),则为低位 8 位的状态。 * / 36 | 37 | #define __WEXITSTATUS(status)(((状态)&amp; 0xff00)&gt;&gt; 8) 38 | 39 | / *如果 WIFSIGNALED(STATUS),终止信号。 * / 40 | 41 | #define __WTERMSIG(status)((status)&amp; 0x7f) 42 | 43 | / *如果 WIFSTOPPED(STATUS),停止孩子的信号。 * / 44 | 45 | #define __WSTOPSIG(status)__ WEXITSTATUS(status) 46 | 47 | / *非零,如果 STATUS 表示正常终止。 * / 48 | 49 | #define __WIFEXITED(status)(__ WTERMSIG(status)== 0) 50 | 51 | 内核具有跟踪信号,退出或停止的内部方式。该 API 是抽象的,以便内核开发人员可以随意更改。 52 | 53 | ## 小心。 54 | 55 | 请记住,只有满足前提条件时,宏才有意义。这意味着如果发出进程信号,则不会定义进程的退出状态。宏不会为你做检查,所以由编程来确保逻辑检出。 56 | 57 | ## 信号 58 | 59 | ## 什么是信号? 60 | 61 | 信号是内核提供给我们的构造。它允许一个进程异步地向另一个进程发送信号(思考消息)。如果该进程想要接受该信号,则它可以,然后,对于大多数信号,可以决定如何处理该信号。这是一个简短的列表(非全面的)信号。 62 | 63 | | 名称 | 默认操作 | 常用用例 | 64 | | --- | --- | --- | 65 | | SIGINT | 终止进程(可以捕获) | 告诉进程好好停止 | 66 | | SIGQUIT | Terminate Process (Can be caught) | 告诉进程严厉阻止 | 67 | | SIGSTOP | 停止进程(无法捕获) | 停止继续进程 | 68 | | SIGCONT | 继续一个进程 | 继续运行进程 | 69 | | SIGKILL | 终止进程(不能忽略) | 你希望你的进程消失了 | 70 | 71 | ## 我可以暂停我的孩子吗? 72 | 73 | 是的!您可以通过发送 SIGSTOP 信号暂时暂停正在运行的进程。如果成功,它将冻结一个进程;即,该进程将不再分配 CPU 时间。 74 | 75 | 要允许进程恢复执行,请发送 SIGCONT 信号。 76 | 77 | 例如,这是一个程序,每秒慢慢打印一个点,最多 59 点。 78 | 79 | ```c 80 | #include 81 | #include 82 | int main() { 83 | printf("My pid is %d\n", getpid() ); 84 | int i = 60; 85 | while(--i) { 86 | write(1, ".",1); 87 | sleep(1); 88 | } 89 | write(1, "Done!",5); 90 | return 0; 91 | } 92 | ``` 93 | 94 | 我们将首先在后台启动该进程(注意&amp; at end)。然后使用 kill 命令从 shell 进程发送一个信号。 95 | 96 | ``` 97 | >./program & 98 | My pid is 403 99 | ... 100 | >kill -SIGSTOP 403 101 | >kill -SIGCONT 403 102 | ``` 103 | 104 | ## 如何从 C 中杀死/停止/暂停我的孩子? 105 | 106 | 在 C 中,使用`kill` POSIX 调用向孩子发送信号, 107 | 108 | ```c 109 | kill(child, SIGUSR1); // Send a user-defined signal 110 | kill(child, SIGSTOP); // Stop the child process (the child cannot prevent this) 111 | kill(child, SIGTERM); // Terminate the child process (the child can prevent this) 112 | kill(child, SIGINT); // Equivalent to CTRL-C (by default closes the process) 113 | ``` 114 | 115 | 如上所述,shell 中还有一个 kill 命令,例如获取正在运行的进程列表,然后终止进程 45 和进程 46 116 | 117 | ``` 118 | ps 119 | kill -l 120 | kill -9 45 121 | kill -s TERM 46 122 | ``` 123 | 124 | ## 如何检测“CTRL-C”并正常清理? 125 | 126 | 我们稍后会回到信号 - 这只是一个简短的介绍。在 Linux 系统上,如果您有兴趣了解更多内容,请参阅`man -s7 signal`(例如,异步信号安全的系统和库调用列表。 127 | 128 | 信号处理程序中的可执行代码有严格的限制。大多数库和系统调用都不是“异步信号安全” - 它们可能不会在信号处理程序中使用,因为它们不是可重入的安全。在单线程程序中,信号处理会暂时中断程序执行,以执行信号处理程序代码。假设您的原始程序在执行`malloc`的库代码时被中断; malloc 使用的内存结构不会处于一致状态。调用`printf`(使用`malloc`)作为信号处理程序的一部分是不安全的,并且将导致“未定义的行为”,即它不再是有用的,可预测的程序。在实践中,您的程序可能会崩溃,计算或生成不正确的结果或停止运行(“死锁”),具体取决于您的程序在执行信号处理程序代码时被中断时的确切执行情况。 129 | 130 | 信号处理程序的一个常见用途是设置一个布尔标志,偶尔轮询(读取)作为程序正常运行的一部分。例如, 131 | 132 | ```c 133 | int pleaseStop ; // See notes on why "volatile sig_atomic_t" is better 134 | 135 | void handle_sigint(int signal) { 136 | pleaseStop = 1; 137 | } 138 | 139 | int main() { 140 | signal(SIGINT, handle_sigint); 141 | pleaseStop = 0; 142 | while ( ! pleaseStop) { 143 | /* application logic here */ 144 | } 145 | /* cleanup code here */ 146 | } 147 | ``` 148 | 149 | 上面的代码似乎在纸上是正确的。但是,我们需要为编译器和将执行`main()`循环的 CPU 内核提供提示。我们需要阻止编译器优化:表达式`! pleaseStop`似乎是一个循环不变量,即永远为真,因此可以简化为`true`。其次,我们需要确保`pleaseStop`的值不使用 CPU 寄存器进行高速缓存,而是始终读取和写入主存储器。 `sig_atomic_t`类型意味着变量的所有位都可以作为“原子操作”读取或修改 - 单个不间断操作。不可能读取由一些新位值和旧位值组成的值。 150 | 151 | 通过使用正确的类型`volatile sig_atomic_t`指定`pleaseStop`,我们可以编写可移植代码,其中主循环将在信号处理程序返回后退出。在大多数现代平台上,`sig_atomic_t`类型可以与`int`一样大,但在嵌入式系统上可以与`char`一样小,并且只能表示(-127 到 127)值。 152 | 153 | ```c 154 | volatile sig_atomic_t pleaseStop; 155 | ``` 156 | 157 | 这种模式的两个例子可以在“COMP”基于终端的 1Hz 4bit 计算机中找到( [https://github.com/gto76/comp-cpp/blob/1bf9a77eaf8f57f7358a316e5bbada97f2dc8987/src/output.c#L121](https://github.com/gto76/comp-cpp/blob/1bf9a77eaf8f57f7358a316e5bbada97f2dc8987/src/output.c#L121) )。使用两个布尔标志。一个标记`SIGINT`(CTRL-C)的传送,并正常关闭程序,另一个标记`SIGWINCH`信号以检测终端调整大小并重绘整个显示。 -------------------------------------------------------------------------------- /docs/19.md: -------------------------------------------------------------------------------- 1 | # 进程复习题 2 | 3 | > 原文: 4 | 5 | ## 话题 6 | 7 | * 正确使用 fork,exec 和 waitpid 8 | * 使用 exec 与路径 9 | * 了解 fork 和 exec 以及 waitpid 的作用。例如。如何使用他们的返回值。 10 | * SIGKILL vs SIGSTOP vs SIGINT。 11 | * 按 CTRL-C 时发送的信号 12 | * 使用 shell 中的 kill 或 kill POSIX 调用。 13 | * 进程内存隔离。 14 | * 进程内存布局(堆,栈等;无效的内存地址)。 15 | * 什么是叉炸弹,僵尸和孤儿?如何创建/删除它们。 16 | * getpid 和 getppid 17 | * 如何使用 WAIT 退出状态宏 WIFEXITED 等 18 | 19 | ## 问题/练习 20 | 21 | * 有没有 p 和没有 p 的高管有什么区别?什么是操作系统 22 | * 如何将命令行参数传递给`execl*`? `execv*`怎么样?按惯例,第一个命令行参数应该是什么? 23 | * 你怎么知道`exec`或`fork`是否失败了? 24 | * 什么是`int *status`指针进入等待状态?什么时候等待失败? 25 | * `SIGKILL`,`SIGSTOP`,`SIGCONT`,`SIGINT`之间有什么区别?什么是默认行为?您可以为哪些设置信号处理程序? 26 | * 按`CTRL-C`时发送了什么信号? 27 | * 我的终端锚定到 PID = 1337 并且刚刚变得没有响应。写下终端命令和 C 代码,将`SIGQUIT`发送给它。 28 | * 一个进程可以通过正常方式改变另一个进程内存吗为什么? 29 | * 堆,栈,数据和文本段在哪里?你能写些哪些细分?什么是无效的内存地址? 30 | * 用 C 编码叉炸弹(请不要运行它)。 31 | * 什么是孤儿?它是如何变成僵尸的?我如何成为一个好父母? 32 | * 当你父母告诉你不能做某事时,你不讨厌它吗?给我写一个程序,将`SIGSTOP`发送给你的父母。 33 | * 编写 fork exec 等待可执行文件的函数,并使用等待宏告诉我进程是否正常退出或是否已发出信号。如果进程正常退出,则使用返回值打印该进程。如果没有,则打印导致进程终止的信号编号。 -------------------------------------------------------------------------------- /docs/20.md: -------------------------------------------------------------------------------- 1 | # 3.内存和分配器 -------------------------------------------------------------------------------- /docs/22.md: -------------------------------------------------------------------------------- 1 | # 内存,第 2 部分:实现内存分配器 2 | 3 | > 原文: 4 | 5 | ## 内存分配器教程 6 | 7 | 内存分配器需要跟踪当前分配的字节以及可供使用的字节。本页介绍了构建分配器的实现和概念细节,即实现`malloc`和`free`的实际代码。 8 | 9 | ## 这个页面讨论了块的链接 - 我是不是要为它们设置 malloc 内存? 10 | 11 | 虽然从概念上讲我们正在考虑创建链表和块列表,但我们不需要“malloc memory”来创建它们!相反,我们将整数和指针写入我们已经控制的内存中,以便以后可以从一个地址一直跳到另一个地址。此内部信息代表一些开销。因此,即使我们从系统请求了 1024 KB 的连续内存,我们也无法将其全部提供给正在运行的程序。 12 | 13 | ## 在块中思考 14 | 15 | 我们可以将堆内存视为每个块分配或未分配的块列表。我们不是存储明确的指针列表,而是存储有关块大小 _ 的信息,作为块 _ 的一部分。因此,概念上存在空闲块的列表,但它是隐式的,即以块大小信息的形式存储,作为每个块的一部分。 16 | 17 | 我们可以通过添加块的大小从一个块导航到下一个块。例如,如果要指向块的开头的指针`p`,则`next_block`位于`((char *)p) + *(size_t *) p`,如果要以字节为单位存储块的大小。转换为`char *`可确保以字节为单位计算指针算法。转换为`size_t *`确保将`p`的内存读取为大小值,并且如果`p`是`void *`或`char *`类型则必须。 18 | 19 | 调用程序永远不会看到这些值;它们是内存分配器的内部实现。 20 | 21 | 例如,假设您的分配器被要求保留 80 个字节(`malloc(80)`)并且需要 8 个字节的内部标头数据。分配器需要找到至少 88 字节的未分配空间。更新堆数据后,它将返回指向块的指针。但是,返回的指针不指向块的开头,因为这是存储内部大小数据的位置!相反,我们将返回块的开始+ 8 个字节。在实现中,请记住指针算法取决于类型。例如,`p += 8`添加`8 * sizeof(p)`,不一定是 8 个字节! 22 | 23 | ## 实现 malloc 24 | 25 | 最简单的实现使用第一个拟合:从第一个块开始,假设它存在,并迭代直到找到表示足够大小的未分配空间的块,或者我们已经检查了所有块。 26 | 27 | 如果找不到合适的块,则应该再次调用`sbrk()`以充分扩展堆的大小。快速实现可能会对其进行大量扩展,因此我们不需要在不久的将来请求更多的堆内存。 28 | 29 | 找到空闲块时,它可能比我们需要的空间大。如果是这样,我们将在隐式列表中创建两个条目。第一个条目是分配的块,第二个条目是剩余的空间。 30 | 31 | 如果块正在使用或可用,有两种简单的方法可以注意。第一种是将它作为一个字节存储在头信息中,同时将其与大小和第二个一起存储,以将其编码为大小中的最低位!因此,块大小信息将仅限于偶数值: 32 | 33 | ``` 34 | // Assumes p is a reasonable pointer type, e.g. 'size_t *'. 35 | isallocated = (*p) & 1; 36 | realsize = (*p) & ~1; // mask out the lowest bit 37 | ``` 38 | 39 | ## 对齐和四舍五入的考虑因素 40 | 41 | 许多架构期望多字节基元与 2 ^ n 的某个倍数对齐。例如,通常需要将 4 字节类型与 4 字节边界(以及 8 字节边界上的 8 字节类型)对齐。如果多字节基元没有存储在合理的边界上(例如从奇数地址开始),那么性能会受到很大影响,因为它可能需要两个存储器读取请求而不是一个。在一些架构上,惩罚甚至更大 - 程序将因[总线错误](http://en.wikipedia.org/wiki/Bus_error#Unaligned_access)而崩溃。 42 | 43 | 由于`malloc`不知道用户将如何使用分配的内存(双精度数组?字符数组?),返回程序的指针需要针对最坏情况进行对齐,这是与体系结构相关的。 44 | 45 | 从 glibc 文档中,glibc `malloc`使用以下启发式:“malloc 为您提供的块保证对齐,以便它可以保存任何类型的数据。在 GNU 系统上,地址始终是 8 的倍数系统,以及 64 位系统上 16 个的倍数。“ 46 | 47 | 例如,如果您需要计算需要多少 16 字节单位,请不要忘记向上舍入 - 48 | 49 | ``` 50 | int s = (requested_bytes + tag_overhead_bytes + 15) / 16 51 | ``` 52 | 53 | 额外的常量确保不完整的单位被四舍五入。请注意,实际代码更有可能符号大小,例如`sizeof(x) - 1`,而不是编码数值常数 15。 54 | 55 | [如果您对此感兴趣,可以参考以下关于内存对齐的精彩文章](http://www.ibm.com/developerworks/library/pa-dalign/) 56 | 57 | ## 关于内部碎片的说明 58 | 59 | 当您提供的块大于其分配大小时,会发生内部碎片。假设我们有一个大小为 16B 的空闲块(不包括元数据)。如果它们分配 7 个字节,您可能需要向上舍入到 16B 并返回整个块。 60 | 61 | 当你实现合并和拆分时,这会变得非常险恶(下一节)。如果你没有实现任何一个,那么你可能最终返回一个大小为 64B 的块用于 7B 分配!这个分配有一个 _ 批次 _ 的开销,这是我们试图避免的。 62 | 63 | ## 实施免费 64 | 65 | 当调用`free`时,我们需要重新应用偏移量以返回到块的“实际”开始(还记得我们没有给用户指向块的实际开始吗?),即到哪里我们存储了大小信息。 66 | 67 | 一个简单的实现只会将块标记为未使用。如果我们将块分配状态存储在最小的位中,那么我们只需要清除该位: 68 | 69 | ```c 70 | *p = (*p) & ~1; // Clear lowest bit 71 | ``` 72 | 73 | 但是,我们还有一些工作要做:如果当前块和下一个块(如果存在)都是空闲的,我们需要将这些块合并为一个块。同样,我们也需要检查前一个块。如果存在并表示未分配的内存,那么我们需要将块合并为单个大块。 74 | 75 | 为了能够使用先前的空闲块合并空闲块,我们还需要找到前一个块,因此我们也将块的大小存储在块的末尾。这些被称为“边界标签”(ref Knuth73)。由于块是连续的,因此一个块的末尾位于下一个块的开头旁边。因此,当前块(除第一个之外)可以进一步查看几个字节以查找前一个块的大小。有了这些信息,您现在可以向后跳! 76 | 77 | ## 性能 78 | 79 | 通过以上描述,可以构建存储器分配器。它的主要优点是简单 - 与其他分配器相比至少简单!分配存储器是最坏情况的线性时间操作(搜索链接列表用于足够大的空闲块)并且解除分配是恒定时间(不需要将 3 个块合并到单个块中)。使用此分配器可以尝试不同的放置策略。例如,您可以从最后一个冻结块的位置开始搜索,或者从上次分配的位置开始搜索。如果你确实存储了指向块的指针,你需要非常小心它们始终保持有效(例如,当合并块或其他 malloc 或免费调用改变堆结构时) 80 | 81 | ## 明确的自由列表分配器 82 | 83 | 通过实现明确的双向链接的空闲节点列表,可以实现更好的性能。在这种情况下,我们可以立即遍历下一个空闲块和前一个空闲块。这可以将搜索时间减半,因为链接列表仅包括未分配的块。 84 | 85 | 第二个优点是我们现在可以控制链表的排序。例如,当一个块被释放时,我们可以选择将它插入到链表的开头而不是总是在它的邻居之间。这将在下面讨论。 86 | 87 | 我们在哪里存储链表的指针?一个简单的技巧是要意识到块本身没有被使用并将下一个和前一个指针存储为块的一部分(尽管现在你必须确保空闲块总是足够大以容纳两个指针)。 88 | 89 | 我们仍然需要实现边界标记(即使用大小的隐式列表),这样我们就可以正确地释放块并将它们与它们的两个邻居合并。因此,显式空闲列表需要更多代码和复杂性。 90 | 91 | 使用显式链接列表,使用快速简单的“查找优先”算法来查找第一个足够大的链接。但是,由于可以修改链接顺序,因此这对应于不同的放置策略。例如,如果链接从最大到最小维护,那么这将产生“最适合”的放置策略。 92 | 93 | ### 显式链表插入策略 94 | 95 | 新冻结的块可以轻松插入两个可能的位置:开头或地址顺序(通过使用边界标签首先找到邻居)。 96 | 97 | 在开头插入会创建一个 LIFO(后进先出)策略:最近的 free'd 空间将被重用。研究表明碎片比使用地址顺序更糟糕。 98 | 99 | 以地址顺序插入(“地址排序策略”)插入空闲块,以便按递增的地址顺序访问块。此策略需要更多时间来释放块,因为必须使用边界标记(大小数据)来查找下一个和以前的未分配块。但是,碎片化程度较低。 100 | 101 | ## 案例研究:Buddy Allocator(隔离列表的一个例子) 102 | 103 | 隔离分配器是将堆分成不同区域的分配器,这些区域由不同的子分配器处理,具体取决于分配请求的大小。大小被分组为类(例如,2 的幂),并且每个大小由不同的子分配器处理,并且每个大小维护其自己的空闲列表。 104 | 105 | 众所周知的这种类型的分配器是伙伴分配器。我们将讨论二进制伙伴分配器,它将分配分成大小为 2 ^ n(n = 1,2,3,...)的一些基本单位字节数的块,但其他也存在(例如 Fibonacci 分裂 - 你能不能看看为什么命名?)。基本概念很简单:如果没有大小为 2 ^ n 的空闲块,则转到下一级并窃取该块并将其拆分为两个。如果相同大小的两个相邻块变为未分配,则它们可以一起合并成一个大小为两倍的单个块。 106 | 107 | 好友分配器很快,因为可以从 free'd 块的地址计算要合并的相邻块,而不是遍历大小标记。最终性能通常需要少量汇编程序代码才能使用专用 CPU 指令来查找最低的非零位。 108 | 109 | Buddy 分配器的主要缺点是它们受 _ 内部碎片 _ 的影响,因为分配被四舍五入到最接近的块大小。例如,68 字节的分配将需要 128 字节的块。 110 | 111 | ### 进一步阅读和参考 112 | 113 | * 参见[软件技术基础和理论计算机科学 1999 年会议论文](http://books.google.com/books?id=0uHME7EfjQEC&lpg=PP1&pg=PA85#v=onepage&q&f=false)(谷歌书籍,第 85 页) 114 | * ThanksForTheMemory UIUC 讲座幻灯片( [pptx](https://subversion.ews.illinois.edu/svn/sp17-cs241/_shared/wikifiles/CS241-05-ThanksForTheMemorySlides.pptx) )( [pdf](https://subversion.ews.illinois.edu/svn/sp17-cs241/_shared/wikifiles/CS241-05-ThanksForTheMemorySlides.pdf) )和 115 | * [维基百科的好友内存分配页面](http://en.wikipedia.org/wiki/Buddy_memory_allocation) 116 | 117 | ## 其他分配器 118 | 119 | 还有许多其他分配方案。例如 [SLUB](http://en.wikipedia.org/wiki/SLUB_%28software%29) (维基百科) - Linux 内核内部使用的三个分配器之一。 -------------------------------------------------------------------------------- /docs/23.md: -------------------------------------------------------------------------------- 1 | # 内存,第 3 部分:粉碎栈示例 2 | 3 | > 原文: 4 | 5 | 每个线程使用栈内存。栈“向下增长” - 如果函数调用另一个函数,则栈扩展到较小的内存地址。栈内存包括非静态自动(临时)变量,参数值和返回地址。如果缓冲区太小某些数据(例如来自用户的输入值),那么很可能会覆盖其他栈变量甚至返回地址。栈内容的精确布局和自动变量的顺序取决于体系结构和编译器。然而,通过一些调查工作,我们可以学习如何故意粉碎特定架构的栈。 6 | 7 | 下面的示例演示了返回地址如何存储在栈中。对于特定的 32 位架构 [Live Linux Machine](http://cs-education.github.io/sys/) ,我们确定返回地址存储在自动变量地址上方两个指针(8 个字节)的地址处。代码故意更改栈值,以便在输入函数返回时,而不是继续在 main 方法内部,它会跳转到 exploit 函数。 8 | 9 | ```c 10 | // Overwrites the return address on the following machine: 11 | // http://cs-education.github.io/sys/ 12 | #include 13 | #include 14 | #include 15 | 16 | void breakout() { 17 | puts("Welcome. Have a shell..."); 18 | system("/bin/sh"); 19 | } 20 | void input() { 21 | void *p; 22 | printf("Address of stack variable: %p\n", &p); 23 | printf("Something that looks like a return address on stack: %p\n", *((&p)+2)); 24 | // Let's change it to point to the start of our sneaky function. 25 | *((&p)+2) = breakout; 26 | } 27 | int main() { 28 | printf("main() code starts at %p\n",main); 29 | 30 | input(); 31 | while (1) { 32 | puts("Hello"); 33 | sleep(1); 34 | } 35 | 36 | return 0; 37 | } 38 | ``` 39 | 40 | [有很多](https://en.wikipedia.org/wiki/Stack_buffer_overflow)计算机倾向于解决这个问题。 -------------------------------------------------------------------------------- /docs/24.md: -------------------------------------------------------------------------------- 1 | # 内存复习题 2 | 3 | > 原文: 4 | 5 | ## 话题 6 | 7 | * 最合适 8 | * 最适合 9 | * First Fit 10 | * 好友分配器 11 | * 内部碎片 12 | * 外部碎片 13 | * SBRK 14 | * 自然对齐 15 | * 边界标记 16 | * 聚结 17 | * 拆分 18 | * 平板分配/内存池 19 | 20 | ## 问题/练习 21 | 22 | * 什么是内部碎片?什么时候成为问题? 23 | * 什么是外部碎片?什么时候成为问题? 24 | * 什么是最适合的安置策略?外部碎片怎么样?时间复杂性? 25 | * 什么是最差的贴合策略?外部碎片化是否更好?时间复杂性? 26 | * 什么是 First Fit Placement 策略?使用 Fragmentation 会更好一些,对吧?预期的时间复杂性? 27 | * 假设我们正在使用一个带有 64kb 新平板的伙伴分配器。如何分配 1.5kb? 28 | * malloc 的 5 行`sbrk`实现何时有用? 29 | * 什么是自然对齐? 30 | * 什么是合并/拆分?他们如何增加/减少碎片?什么时候可以合并或拆分? 31 | * 边界标签如何工作?它们如何用于合并或分裂? -------------------------------------------------------------------------------- /docs/25.md: -------------------------------------------------------------------------------- 1 | # 4.介绍 Pthreads -------------------------------------------------------------------------------- /docs/26.md: -------------------------------------------------------------------------------- 1 | # Pthreads,第 1 部分:简介 2 | 3 | > 原文: 4 | 5 | ## 线程简介 6 | 7 | ## 什么是线程? 8 | 9 | 线程是“执行线程”的缩写。它表示 CPU 具有(并将执行)的指令序列。要记住如何从函数调用返回,并存储自动变量和参数的值,线程使用栈。 10 | 11 | ## 什么是轻量级过程(LWP)?它与线程有什么关系? 12 | 13 | 好吧,对于所有意图和目的,线程是一个过程(意味着创建一个线程类似于`fork`),除了**没有复制**意味着没有写入副本。这允许进程共享相同的地址空间,变量,堆,文件描述符等。 14 | 15 | 创建线程的实际系统调用类似于`fork`;这是`clone`。我们不会详细说明,但您可以阅读[手册页](http://man7.org/linux/man-pages/man2/clone.2.html),请记住它不在本课程的直接范围内。 16 | 17 | LWP 或线程更倾向于分配许多场景,因为创建它们的开销要少得多。但在某些情况下(特别是 python 使用它)多处理是使代码更快的方法。 18 | 19 | ## 线程的栈如何工作? 20 | 21 | 您的主要功能(以及您可能调用的其他功能)具有自动变量。我们将使用栈将它们存储在内存中,并使用简单的指针(“栈指针”)跟踪栈的大小。如果线程调用另一个函数,我们将栈指针向下移动,这样我们就有更多的空间用于参数和自动变量。一旦从函数返回,我们就可以将栈指针移回其先前的值。我们保留旧栈指针值的副本 - 在栈上!这就是为什么从函数返回非常快 - 很容易“释放”自动变量使用的内存 - 我们只需要更改栈指针。 22 | 23 | ![](img/e209a7428a9ce45094abf36a151c7d63.jpg) 24 | 25 | 在多线程程序中,有多个栈但只有一个地址空间。 pthread 库分配一些栈空间(在堆中或使用主程序栈的一部分)并使用`clone`函数调用在该栈地址处启动线程。总地址空间可能看起来像这样。 26 | 27 | ![](img/4013002f4b22a09d0fc6a117c0a29816.jpg) 28 | 29 | ## 我的进程可以有多少个线程? 30 | 31 | 您可以在进程内运行多个线程。你免费得到第一个帖子!它运行你在'main'中编写的代码。如果需要更多线程,可以使用 pthread 库调用`pthread_create`创建新线程。您需要将指针传递给函数,以便线程知道从哪里开始。 32 | 33 | 您创建的线程都存在于同一个虚拟内存中,因为它们是同一进程的一部分。因此,他们都可以看到堆,全局变量和程序代码等。因此,您可以在同一个进程内同时处理两个(或多个)CPU。由操作系统决定将线程分配给 CPU。如果你有比 CPU 更多的活动线程,那么内核会将线程分配给 CPU 很短的时间(或直到它用完了要做的事情),然后自动切换 CPU 在另一个线程上工作。例如,一个 CPU 可能正在处理游戏 AI 而另一个线程正在计算图形输出。 34 | 35 | ## 简单用法 36 | 37 | ## 你好世界 pthread 的例子 38 | 39 | 要使用 pthread,您需要包含`pthread.h`并且需要使用`-pthread`(或`-lpthread`)编译器选项进行编译。此选项告诉编译器您的程序需要线程支持 40 | 41 | 要创建线程,请使用函数`pthread_create`。这个函数有四个参数: 42 | 43 | ```c 44 | int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 45 | void *(*start_routine) (void *), void *arg); 46 | ``` 47 | 48 | * 第一个是指向一个变量的指针,该变量将保存新创建的线程的 id。 49 | * 第二个是指向属性的指针,我们可以使用它来调整和调整 pthreads 的一些高级功能。 50 | * 第三个是指向我们想要运行的函数的指针 51 | * 第四个是指向我们的函数的指针 52 | 53 | 论点`void *(*start_routine) (void *)`很难读懂!它表示一个指针,它接受一个`void *`指针并返回一个`void *`指针。它看起来像一个函数声明,除了函数的名称用`(* .... )`包装 54 | 55 | 这是最简单的例子: 56 | 57 | ```c 58 | #include 59 | #include 60 | // remember to set compilation option -pthread 61 | 62 | void *busy(void *ptr) { 63 | // ptr will point to "Hi" 64 | puts("Hello World"); 65 | return NULL; 66 | } 67 | int main() { 68 | pthread_t id; 69 | pthread_create(&id, NULL, busy, "Hi"); 70 | while (1) {} // Loop forever 71 | } 72 | ``` 73 | 74 | 如果我们想等待我们的线程完成使用`pthread_join` 75 | 76 | ```c 77 | void *result; 78 | pthread_join(id, &result); 79 | ``` 80 | 81 | 在上面的例子中,`result`将是`null`,因为 busy 函数返回`null`。我们需要传递结果地址,因为`pthread_join`将写入指针的内容。 82 | 83 | 见 [Pthreads 第 2 部分](https://github.com/angrave/SystemProgramming/wiki/Pthreads%2C-Part-2%3A-Usage-in-Practice) -------------------------------------------------------------------------------- /docs/28.md: -------------------------------------------------------------------------------- 1 | # Pthreads,第 3 部分:并行问题(奖金) 2 | 3 | > 原文: 4 | 5 | ## 概观 6 | 7 | 下一节讨论 pthreads 碰撞时会发生什么,但如果我们让每个线程完全不同,没有重叠怎么办? 8 | 9 | 我们发现最大加速并行问题? 10 | 11 | ## 令人尴尬的并行问题 12 | 13 | 在过去几年中,对并行算法的研究已经爆发。一个令人尴尬的并行问题是任何需要很少努力转向并行的问题。他们中的很多人都有一些同步概念,但并非总是如此。你已经知道一个可并行化的算法,Merge Sort! 14 | 15 | ```c 16 | void merge_sort(int *arr, size_t len){ 17 | if(len > 1){ 18 | //Mergesort the left half 19 | //Mergesort the right half 20 | //Merge the two halves 21 | } 22 | ``` 23 | 24 | 通过对线程的新理解,您需要做的就是为左半部分创建一个线程,为右半部分创建一个线程。鉴于您的 CPU 有多个真实核心,您将看到符合 [Amdahl 定律](https://en.wikipedia.org/wiki/Amdahl's_law)的加速。时间复杂度分析也很有趣。并行算法运行在 O(log ^ 3(n))运行时间(因为我们假设我们有很多核心的花式分析。 25 | 26 | 但在实践中,我们通常会做两处更改。一,一旦数组变得足够小,我们就会抛弃并行 mergesort 算法并执行快速排序或其他算法,这些算法可以在小数组上快速运行(某些东西可以缓存一致性)。我们知道的另一件事是 CPU 没有无限核心。为了解决这个问题,我们通常会保留一个工作人员池。 27 | 28 | ## 工人池 29 | 30 | 我们知道 CPU 的内核数量有限。很多时候,我们启动了许多线程,并在空闲时为他们提供任务。 31 | 32 | ## 另一个问题,平行地图 33 | 34 | 假设我们想要将一个函数应用于整个数组,一次一个元素。 35 | 36 | ```c 37 | int *map(int (*func)(int), int *arr, size_t len){ 38 | int *ret = malloc(len*sizeof(*arr)); 39 | for(size_t i = 0; i < len; ++i) 40 | ret[i] = func(arr[i]); 41 | return ret; 42 | } 43 | ``` 44 | 45 | 由于没有任何元素依赖于任何其他元素,您将如何进行并行化?您认为在线程之间拆分工作的最佳方法是什么? 46 | 47 | ## 调度 48 | 49 | 分离工作有几种方法。 50 | 51 | * 静态调度:将问题分解为固定大小的块(预定义),并让每个线程都在每个块上运行。当每个子问题花费大致相同的时间时,这很有效,因为没有额外的开销。您需要做的就是编写一个循环并将 map 函数赋予每个子数组。 52 | * 动态调度:当一个新问题变得可用时,有一个线程为它服务。当您不知道调度需要多长时间时,这非常有用 53 | * 引导式调度:这是上述各种利益和权衡的混合。您可以从静态计划开始,并根据需要缓慢移动到动态 54 | * 运行时调度:你完全不知道问题需要多长时间。不要自己决定,让程序决定做什么! 55 | 56 | [来源](https://software.intel.com/en-us/articles/openmp-loop-scheduling),但无需记忆。 57 | 58 | ## 几个缺点 59 | 60 | 由于缓存一致性和调度额外线程之类的东西,你不会马上看到加速。 61 | 62 | ## 其他问题 63 | 64 | 来自[维基百科](https://en.wikipedia.org/wiki/Embarrassingly_parallel) 65 | 66 | * 一次将 Web 服务器上的静态文件提供给多个用户。 67 | * Mandelbrot 集,Perlin 噪声和类似图像,其中每个点都是独立计算的。 68 | * 渲染计算机图形。在计算机动画中,每个帧可以独立渲染(参见并行渲染)。 69 | * 密码学中的暴力搜索。[8]值得注意的现实世界的例子包括 distributed.net 和加密货币中使用的工作证明系统。 70 | * BLAST 在生物信息学中搜索多个查询(但不是针对单个大型查询)[9] 71 | * 大规模面部识别系统,其将数千个任意获取的面部(例如,经由闭路电视的安全或监视视频)与类似大量的先前存储的面部(例如,盗贼画廊或类似的观察列表)进行比较。[10] 72 | * 计算机模拟比较许多独立场景,例如气候模型。 73 | * 进化计算元启发式算法,如遗传算法。 74 | * 数值天气预报的集合计算。 75 | * 粒子物理中的事件模拟与重建。 76 | * 行进方块算法 77 | * 筛分步骤的二次筛和数字筛。 78 | * 随机森林机器学习技术的树木生长步骤。 79 | * 离散傅里叶变换,其中每个谐波是独立计算的。 -------------------------------------------------------------------------------- /docs/29.md: -------------------------------------------------------------------------------- 1 | # Pthread 复习题 2 | 3 | > 原文: 4 | 5 | ## 话题 6 | 7 | * pthread 生命周期 8 | * 每个线程都有一个栈 9 | * 从线程捕获返回值 10 | * 使用`pthread_join` 11 | * 使用`pthread_create` 12 | * 使用`pthread_exit` 13 | * 在什么条件下进程会退出 14 | 15 | ## 问题 16 | 17 | * 创建 pthread 会发生什么? (你不需要进入超级细节) 18 | * 每个线程的栈在哪里? 19 | * 如果给出`pthread_t`,你如何得到一个回报值?线程可以设置返回值的方式是什么?如果您丢弃返回值会发生什么? 20 | * 为什么`pthread_join`很重要(想想栈空间,寄存器,返回值)? 21 | * 在正常情况下`pthread_exit`会做什么(即你不是最后一个线程)?调用 pthread_exit 时会调用哪些其他函数? 22 | * 给我三个条件,在这个条件下多线程进程将退出。你还能想到吗? 23 | * 什么是令人尴尬的并行问题? -------------------------------------------------------------------------------- /docs/3.md: -------------------------------------------------------------------------------- 1 | # #Informal 词汇表 2 | 3 | > 原文: 4 | 5 | 警告:与全长词汇表不同,此非正式词汇表会跳过详细信息,并提供每个术语的简化和可访问的说明。有关更多信息和详细信息,请使用您喜欢的网络搜索引 6 | 7 | ## 什么是内核? 8 | 9 | 内核是操作系统的核心部分,用于管理进程,资源(包括内存)和硬件输入输出设备。用户程序通过进行系统调用与内核交互。 10 | 11 | 了解更多: [http://en.wikipedia.org/wiki/Kernel_%28operating_system%29](http://en.wikipedia.org/wiki/Kernel_%28operating_system%29) 12 | 13 | ## 什么是进程? 14 | 15 | 进程是在计算机上运行的程序的实例。同一程序可以有多个进程。例如,你和我可能都在运行'cat'或'gnuchess' 16 | 17 | 进程包含程序代码和可修改的状态信息,例如变量,信号,文件的打开文件描述符,网络连接以及存储在进程内存中的其他系统资源。操作系统还存储关于过程的元信息,系统使用该元信息来管理和监视过程的活动和资源使用。 18 | 19 | 了解更多: [http://en.wikipedia.org/wiki/Process_%28computing%29](http://en.wikipedia.org/wiki/Process_%28computing%29) 20 | 21 | ## 什么是虚拟内存? 22 | 23 | 在智能手机和笔记本电脑上运行的进程使用虚拟内存:每个进程都与其他进程隔离,并且似乎可以完全访问所有可能的内存地址!实际上,只有进程地址空间的一小部分映射到物理内存,分配给进程的实际物理内存量可以随时间变化并被分页到磁盘,重新映射并与其他进程安全共享。虚拟内存提供了显着的好处,包括强大的进程隔离(安全性),资源和性能优势(简化和高效的物理内存使用),我们将在后面讨论。 24 | 25 | 了解更多: [http://en.wikipedia.org/wiki/Virtual_memory](http://en.wikipedia.org/wiki/Virtual_memory) -------------------------------------------------------------------------------- /docs/30.md: -------------------------------------------------------------------------------- 1 | # 5.同步 -------------------------------------------------------------------------------- /docs/32.md: -------------------------------------------------------------------------------- 1 | # 同步,第 2 部分:计算信号量 2 | 3 | > 原文: 4 | 5 | ## 什么是计数信号量? 6 | 7 | 计数信号量包含一个值,并支持两个操作“等待”和“发布”。 Post 递增信号量并立即返回。如果计数为零,“等待”将等待。如果计数不为零,则信号量递减计数并立即返回。 8 | 9 | 类比是饼干罐中的饼干(或宝箱中的金币)的计数。在拿饼干之前,请拨打“等待”。如果没有剩下的 cookie,那么`wait`将不会返回:它将`wait`直到另一个线程通过调用 post 增加信号量。 10 | 11 | 简而言之,`post`递增并立即返回,而如果计数为零,`wait`将等待。在返回之前它将减少计数。 12 | 13 | ## 如何创建信号量? 14 | 15 | 本页介绍了未命名的信号量。不幸的是 Mac OS X 还不支持这些。 16 | 17 | 首先确定初始值是零还是其他值(例如,数组中剩余空格的数量)。与 pthread 互斥锁不同,没有创建信号量的快捷方式 - 使用`sem_init` 18 | 19 | ```c 20 | #include 21 | 22 | sem_t s; 23 | int main() { 24 | sem_init(&s, 0, 10); // returns -1 (=FAILED) on OS X 25 | sem_wait(&s); // Could do this 10 times without blocking 26 | sem_post(&s); // Announce that we've finished (and one more resource item is available; increment count) 27 | sem_destroy(&s); // release resources of the semaphore 28 | } 29 | ``` 30 | 31 | ## 我可以从不同的线程调用等待和发布吗? 32 | 33 | 是!与互斥锁不同,增量和减量可以来自不同的线程。 34 | 35 | ## 我可以使用信号量而不是互斥量吗? 36 | 37 | 是的 - 虽然信号量的开销更大。要使用信号量: 38 | 39 | * 用一个计数器初始化信号量。 40 | * 用`sem_wait`替换`...lock` 41 | * 用`sem_post`替换`...unlock` 42 | 43 | 互斥量是一个信号量,它始终是`waits` `posts` 44 | 45 | ```c 46 | sem_t s; 47 | sem_init(&s, 0, 1); 48 | 49 | sem_wait(&s); 50 | // Critical Section 51 | sem_post(&s); 52 | ``` 53 | 54 | ## 我可以在信号处理程序中使用 sem_post 吗? 55 | 56 | 是! `sem_post`是可以在信号处理程序中正确使用的少数几个函数之一。这意味着我们可以释放一个等待线程,该线程现在可以使我们不允许在信号处理程序本身内调用的所有调用(例如`printf`)。 57 | 58 | ```c 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | 65 | sem_t s; 66 | 67 | void handler(int signal) 68 | { 69 | sem_post(&s); /* Release the Kraken! */ 70 | } 71 | 72 | void *singsong(void *param) 73 | { 74 | sem_wait(&s); 75 | printf("I had to wait until your signal released me!\n"); 76 | } 77 | 78 | int main() 79 | { 80 | int ok = sem_init(&s, 0, 0 /* Initial value of zero*/); 81 | if (ok == -1) { 82 | perror("Could not create unnamed semaphore"); 83 | return 1; 84 | } 85 | signal(SIGINT, handler); // Too simple! See note below 86 | 87 | pthread_t tid; 88 | pthread_create(&tid, NULL, singsong, NULL); 89 | pthread_exit(NULL); /* Process will exit when there are no more threads */ 90 | } 91 | ``` 92 | 93 | 注意,健壮的程序不会在多线程程序中使用`signal()`(“多线程进程中的信号()的效果未指定。” - 信号手册页);一个更正确的程序需要使用`sigaction`。 94 | 95 | ## 我怎么知道更多? 96 | 97 | 阅读手册页: 98 | 99 | * [sem_init](http://man7.org/linux/man-pages/man3/sem_init.3.html) 100 | * [sem_wait](http://man7.org/linux/man-pages/man3/sem_wait.3.html) 101 | * [sem_post](http://man7.org/linux/man-pages/man3/sem_post.3.html) 102 | * [sem_destroy](http://man7.org/linux/man-pages/man3/sem_destroy.3.html) -------------------------------------------------------------------------------- /docs/35.md: -------------------------------------------------------------------------------- 1 | # 同步,第 5 部分:条件变量 2 | 3 | > 原文: 4 | 5 | ## 条件变量简介 6 | 7 | ## 暖身 8 | 9 | 命名这些属性! 10 | 11 | * “CS 中一次只能有一个进程(/ thread)” 12 | * “如果等待,那么另一个进程只能进入有限次数的 CS” 13 | * “如果 CS 中没有其他进程,那么进程可以立即进入 CS” 14 | 15 | 有关答案,请参见[同步,第 4 部分:临界区问题](/angrave/SystemProgramming/wiki/Synchronization%2C-Part-4%3A-The-Critical-Section-Problem)。 16 | 17 | ## 什么是条件变量?你怎么用它们?什么是虚假唤醒? 18 | 19 | * 条件变量允许一组线程睡眠直到发痒!你可以勾选一个线程或所有正在休眠的线程。如果您只唤醒一个线程,那么操作系统将决定唤醒哪个线程。你不直接唤醒线程,而是“发出”条件变量,然后唤醒条件变量内部的一个(或所有)线程。 20 | 21 | * 条件变量与互斥锁和循环一起使用(以检查条件)。 22 | 23 | * 偶尔等待的线程可能会无缘无故地唤醒(这被称为 _ 虚假唤醒 _)!这不是问题,因为您总是在循环中使用`wait`来测试必须为 true 才能继续的条件。 24 | 25 | * 通过调用`pthread_cond_broadcast`(全部唤醒)或`pthread_cond_signal`(唤醒一个)唤醒在条件变量内睡眠的线程。注意尽管有函数名称,这与 POSIX `signal`无关! 26 | 27 | ## `pthread_cond_wait`有什么作用? 28 | 29 | 调用`pthread_cond_wait`执行三个操作: 30 | 31 | * 解锁互斥锁 32 | * 等待(在相同的条件变量上调用`pthread_cond_signal`时休眠) 33 | * 在返回之前,锁定互斥锁 34 | 35 | ## (高级主题)为什么条件变量也需要互斥锁? 36 | 37 | 条件变量需要互斥锁有三个原因。最简单的理解是它可以防止早期唤醒消息(`signal`或`broadcast`功能)被“丢失”。想象一下,在调用 _ `pthread_cond_wait`之前,满足条件的下列事件序列(时间向下运行)。在这个例子中,唤醒信号丢失了! 38 | 39 | | 线程 1 | 线程 2 | 40 | | --- | --- | 41 | | `while( answer < 42) {` | | 42 | | | `answer++` | 43 | | | `p_cond_signal(cv)` | 44 | | `p_cond_wait(cv,m)` | | 45 | 46 | 如果两个线程都锁定了互斥锁,则在 `pthread_cond_wait(cv, m)`被调用(然后在内部解锁互斥锁之后)_ 之前无法发送信号 _ 47 | 48 | 第二个常见原因是更新程序状态(`answer`变量)通常需要互斥 - 例如,多个线程可能正在更新`answer`的值。 49 | 50 | 第三个也是微妙的原因是为了满足我们在此仅概述的实时调度问题:在时间关键型应用中,应该允许具有 _ 最高优先级 _ 的等待线程首先继续。为满足此要求,还必须在调用`pthread_cond_signal`或`pthread_cond_broadcast`之前锁定互斥锁。对于好奇的人来说,[在](https://groups.google.com/forum/?hl=ky#!msg/comp.programming.threads/wEUgPq541v8/ZByyyS8acqMJ)[中进行了较长时间的历史性讨论。](https://groups.google.com/forum/?hl=ky#!msg/comp.programming.threads/wEUgPq541v8/ZByyyS8acqMJ) 51 | 52 | ## 为什么存在虚假的尾流? 53 | 54 | 为了表现。在多 CP​​U 系统上,竞争条件可能导致唤醒(信号)请求被忽视。内核可能无法检测到此丢失的唤醒呼叫,但可以检测到它何时可能发生。为了避免潜在的丢失信号,线程被唤醒,以便程序代码可以再次测试条件。 55 | 56 | ## 例 57 | 58 | 条件变量 _ 总是 _ 与互斥锁一起使用。 59 | 60 | 在调用 _ 等待 _ 之前,必须锁定互斥锁并且 _ 等待 _ 必须用循环包裹。 61 | 62 | ```c 63 | pthread_cond_t cv; 64 | pthread_mutex_t m; 65 | int count; 66 | 67 | // Initialize 68 | pthread_cond_init(&cv, NULL); 69 | pthread_mutex_init(&m, NULL); 70 | count = 0; 71 | 72 | pthread_mutex_lock(&m); 73 | while (count < 10) { 74 | pthread_cond_wait(&cv, &m); 75 | /* Remember that cond_wait unlocks the mutex before blocking (waiting)! */ 76 | /* After unlocking, other threads can claim the mutex. */ 77 | /* When this thread is later woken it will */ 78 | /* re-lock the mutex before returning */ 79 | } 80 | pthread_mutex_unlock(&m); 81 | 82 | //later clean up with pthread_cond_destroy(&cv); and mutex_destroy 83 | 84 | // In another thread increment count: 85 | while (1) { 86 | pthread_mutex_lock(&m); 87 | count++; 88 | pthread_cond_signal(&cv); 89 | /* Even though the other thread is woken up it cannot not return */ 90 | /* from pthread_cond_wait until we have unlocked the mutex. This is */ 91 | /* a good thing! In fact, it is usually the best practice to call */ 92 | /* cond_signal or cond_broadcast before unlocking the mutex */ 93 | pthread_mutex_unlock(&m); 94 | } 95 | ``` 96 | 97 | ## 实现计数信号量 98 | 99 | * 我们可以使用条件变量实现计数信号量。 100 | * 每个信号量都需要一个计数,一个条件变量和一个互斥量 101 | 102 | ```c 103 | typedef struct sem_t { 104 | int count; 105 | pthread_mutex_t m; 106 | pthread_condition_t cv; 107 | } sem_t; 108 | ``` 109 | 110 | 实现`sem_init`以初始化互斥锁和条件变量 111 | 112 | ```c 113 | int sem_init(sem_t *s, int pshared, int value) { 114 | if (pshared) { errno = ENOSYS /* 'Not implemented'*/; return -1;} 115 | 116 | s->count = value; 117 | pthread_mutex_init(&s->m, NULL); 118 | pthread_cond_init(&s->cv, NULL); 119 | return 0; 120 | } 121 | ``` 122 | 123 | 我们`sem_post`的实现需要增加计数。我们还将唤醒在条件变量内部休眠的任何线程。请注意,我们锁定和解锁互斥锁,因此一次只有一个线程可以在临界区内。 124 | 125 | ```c 126 | sem_post(sem_t *s) { 127 | pthread_mutex_lock(&s->m); 128 | s->count++; 129 | pthread_cond_signal(&s->cv); /* See note */ 130 | /* A woken thread must acquire the lock, so it will also have to wait until we call unlock*/ 131 | 132 | pthread_mutex_unlock(&s->m); 133 | } 134 | ``` 135 | 136 | 如果信号量的计数为零,我们的`sem_wait`实现可能需要休眠。就像`sem_post`一样,我们使用锁来包装临界区(因此一次只有一个线程可以执行我们的代码)。请注意,如果线程确实需要等待,那么互斥锁将被解锁,允许另一个线程进入`sem_post`并从我们的睡眠中唤醒我们! 137 | 138 | 请注意,即使线程被唤醒,在它从`pthread_cond_wait`返回之前,它必须重新获取锁,因此它必须再等一点(例如,直到 sem_post 结束)。 139 | 140 | ```c 141 | sem_wait(sem_t *s) { 142 | pthread_mutex_lock(&s->m); 143 | while (s->count == 0) { 144 | pthread_cond_wait(&s->cv, &s->m); /*unlock mutex, wait, relock mutex*/ 145 | } 146 | s->count--; 147 | pthread_mutex_unlock(&s->m); 148 | } 149 | ``` 150 | 151 | **等`sem_post`一直调用`pthread_cond_signal`不会破坏 sem_wait?** 答案:不!在计数非零之前,我们无法通过循环。在实践中,这意味着即使没有等待线程,`sem_post`也会不必要地调用`pthread_cond_signal`。更有效的实施只会在必要时调用`pthread_cond_signal`,即 152 | 153 | ```c 154 | /* Did we increment from zero to one- time to signal a thread sleeping inside sem_post */ 155 | if (s->count == 1) /* Wake up one waiting thread!*/ 156 | pthread_cond_signal(&s->cv); 157 | ``` 158 | 159 | ## 其他信号量考虑因素 160 | 161 | * 真实的信号量实现包括队列和调度问题,以确保公平性和优先级,例如唤醒最高优先级的最长睡眠线程。 162 | * 此外,`sem_init`的高级使用允许跨进程共享信号量。我们的实现仅适用于同一进程内的线程。 -------------------------------------------------------------------------------- /docs/36.md: -------------------------------------------------------------------------------- 1 | # 同步,第 6 部分:实现障碍 2 | 3 | > 原文: 4 | 5 | ## 在继续下一步之前,如何等待 N 个线程达到某个点? 6 | 7 | 假设我们想要执行具有两个阶段的多线程计算,但我们不希望在第一阶段完成之前进入第二阶段。 8 | 9 | 我们可以使用称为 **barrier** 的同步方法。当一个线程到达一个屏障时,它将在屏障处等待,直到所有线程都到达屏障,然后它们将一起进行。 10 | 11 | 把它想象成和一些朋友一起去远足。你同意在每个山顶上等待对方(并且你会记下你的小组中有多少人)。说你是第一个到达第一座山顶的人。你会在那里等你的朋友。一个接一个,他们将到达顶部,但没有人会继续,直到你的小组中的最后一个人到达。一旦他们这样做,你们都会继续前进。 12 | 13 | Pthreads 有一个实现它的函数`pthread_barrier_wait()`。你需要声明一个`pthread_barrier_t`变量并用`pthread_barrier_init()`初始化它。 `pthread_barrier_init()`将参与屏障的线程数作为参数。 [这是一个例子。](https://github.com/angrave/SystemProgramming/wiki/Sample-program-using-pthread-barriers) 14 | 15 | 现在让我们实现自己的障碍,并使用它来保持所有线程在大型计算中同步。 16 | 17 | ```c 18 | double data[256][8192] 19 | 20 | 1 Threads do first calculation (use and change values in data) 21 | 22 | 2 Barrier! Wait for all threads to finish first calculation before continuing 23 | 24 | 3 Threads do second calculation (use and change values in data) 25 | ``` 26 | 27 | 螺纹功能有四个主要部分 - 28 | 29 | ```c 30 | void *calc(void *arg) { 31 | /* Do my part of the first calculation */ 32 | /* Am I the last thread to finish? If so wake up all the other threads! */ 33 | /* Otherwise wait until the other threads has finished part one */ 34 | /* Do my part of the second calculation */ 35 | } 36 | ``` 37 | 38 | 我们的主线程将创建 16 个线程,我们将每个计算分成 16 个单独的部分。每个线程都将被赋予一个唯一值(0,1,2,... 15),因此它可以在自己的块上工作。由于(void *)类型可以包含小整数,我们将通过将它转换为 void 指针来传递`i`的值。 39 | 40 | ```c 41 | #define N (16) 42 | double data[256][8192] ; 43 | int main() { 44 | pthread_t ids[N]; 45 | for(int i = 0; i < N; i++) 46 | pthread_create(&ids[i], NULL, calc, (void *) i); 47 | ``` 48 | 49 | 注意,我们永远不会将此指针值取消引用作为实际的内存位置 - 我们只是将其直接转换回整数: 50 | 51 | ```c 52 | void *calc(void *ptr) { 53 | // Thread 0 will work on rows 0..15, thread 1 on rows 16..31 54 | int x, y, start = N * (int) ptr; 55 | int end = start + N; 56 | for(x = start; x < end; x++) for (y = 0; y < 8192; y++) { /* do calc #1 */ } 57 | ``` 58 | 59 | 计算 1 完成后,我们需要等待较慢的线程(除非我们是最后一个线程!)。因此,请跟踪到达我们屏障的线程数量'checkpoint': 60 | 61 | ```c 62 | // Global: 63 | int remain = N; 64 | 65 | // After calc #1 code: 66 | remain--; // We finished 67 | if (remain ==0) {/*I'm last! - Time for everyone to wake up! */ } 68 | else { 69 | while (remain != 0) { /* spin spin spin*/ } 70 | } 71 | ``` 72 | 73 | 但是上面的代码有一个竞争条件(两个线程可能会尝试减少`remain`)并且循环是一个繁忙的循环。我们可以做得更好!让我们使用一个条件变量然后我们将使用广播/信号函数来唤醒睡眠线程。 74 | 75 | 提醒一下,条件变量类似于房子!线程去那里睡觉(`pthread_cond_wait`)。您可以选择唤醒一个线程(`pthread_cond_signal`)或所有线程(`pthread_cond_broadcast`)。如果当前没有线程正在等待,则这两个调用无效。 76 | 77 | 条件变量版本通常非常类似于忙循环不正确的解决方案 - 我们将在下面展示。首先,让我们添加一个互斥和条件全局变量,不要忘记在`main`中初始化它们...... 78 | 79 | ```c 80 | //global variables 81 | pthread_mutex_t m; 82 | pthread_cond_t cv; 83 | 84 | main() { 85 | pthread_mutex_init(&m, NULL); 86 | pthread_cond_init(&cv, NULL); 87 | ``` 88 | 89 | 我们将使用互斥锁来确保一次只有一个线程修改`remain`。最后到达的线程需要唤醒 _ 所有 _ 休眠线程 - 所以我们将使用`pthread_cond_broadcast(&cv)`而不是`pthread_cond_signal` 90 | 91 | ```c 92 | pthread_mutex_lock(&m); 93 | remain--; 94 | if (remain ==0) { pthread_cond_broadcast(&cv); } 95 | else { 96 | while(remain != 0) { pthread_cond_wait(&cv, &m); } 97 | } 98 | pthread_mutex_unlock(&m); 99 | ``` 100 | 101 | 当一个线程进入`pthread_cond_wait`时,它会释放互斥锁并休眠。在将来的某个时刻,它会被唤醒。一旦我们从睡眠中恢复一个线程,在返回之前它必须等到它可以锁定互斥锁。请注意,即使睡眠线程提前醒来,它也会检查 while 循环条件并在必要时重新进入等待状态。 102 | 103 | **上面的障碍是不可重用的**意味着如果我们将它粘贴到任何旧的计算循环中,那么代码很可能会遇到障碍要么死锁,要么线程在一次迭代中前进更快的情况。考虑如何使上述障碍可重用,这意味着如果多个线程在循环中调用`barrier_wait`,那么可以保证它们在同一个迭代中。 -------------------------------------------------------------------------------- /docs/37.md: -------------------------------------------------------------------------------- 1 | # 同步,第 7 部分:读者编写器问题 2 | 3 | > 原文: 4 | 5 | ## 什么是读者作家问题? 6 | 7 | 想象一下,你有一个许多线程使用的键值映射数据结构。如果没有写入数据结构,多个线程应该能够同时查找(读取)值。作者不是那么合群 - 为了避免数据损坏,一次只有一个线程可以修改(`write`)数据结构(当时没有读者可能正在阅读)。 8 | 9 | 这是 _ 读写器问题 _ 的一个例子。也就是说,我们如何有效地同步多个读者和作者,以便多个读者可以一起阅读,但作家获得独家访问? 10 | 11 | 下面显示了不正确的尝试(“lock”是`pthread_mutex_lock`的简写): 12 | 13 | ## 尝试#1 14 | 15 | | 16 | 17 | ``` 18 | read() { 19 | lock(&m) 20 | // do read stuff 21 | unlock(&m) 22 | } 23 | 24 | ``` 25 | 26 | | 27 | 28 | ``` 29 | write() { 30 | lock(&m) 31 | // do write stuff 32 | unlock(&m) 33 | } 34 | 35 | ``` 36 | 37 | | 38 | 39 | 至少我们的第一次尝试不会遭受数据损坏(读者必须在作家写作时等待,反之亦然)!但读者也必须等待其他读者。那么让我们尝试另一种实现.. 40 | 41 | ## 尝试#2: 42 | 43 | | 44 | 45 | ``` 46 | read() { 47 | while(writing) {/*spin*/} 48 | reading = 1 49 | // do read stuff 50 | reading = 0 51 | } 52 | 53 | ``` 54 | 55 | | 56 | 57 | ``` 58 | write() { 59 | while(reading || writing) {/*spin*/} 60 | writing = 1 61 | // do write stuff 62 | writing = 0 63 | } 64 | 65 | ``` 66 | 67 | | 68 | 69 | 我们的第二次尝试遭遇竞争条件 - 想象两个线程是否同时调用`read`和`write`(或两者都称为写入)。两个线程都可以继续!其次,我们可以有多个读者和多个作者,所以让我们跟踪读者或作者的总数。这让我们尝试#3, 70 | 71 | ## 尝试#3 72 | 73 | 请记住`pthread_cond_wait`执行 _ 三个 _ 动作。首先,它以原子方式解锁互斥锁然后休眠(直到它被`pthread_cond_signal`或`pthread_cond_broadcast`唤醒)。第三,唤醒线程必须在返回之前重新获取互斥锁。因此,实际上只有一个线程可以在 lock 和 unlock()方法定义的临界区内运行。 74 | 75 | 下面的实现#3 确保如果有任何作者写作,读者将进入 cond_wait。 76 | 77 | ```c 78 | read() { 79 | lock(&m) 80 | while (writing) 81 | cond_wait(&cv, &m) 82 | reading++; 83 | 84 | /* Read here! */ 85 | 86 | reading-- 87 | cond_signal(&cv) 88 | unlock(&m) 89 | } 90 | ``` 91 | 92 | 然而,只有一个读者一次可以阅读,因为候选人#3 没有解锁互斥锁。更好的版本在阅读之前解锁: 93 | 94 | ```c 95 | read() { 96 | lock(&m); 97 | while (writing) 98 | cond_wait(&cv, &m) 99 | reading++; 100 | unlock(&m) 101 | /* Read here! */ 102 | lock(&m) 103 | reading-- 104 | cond_signal(&cv) 105 | unlock(&m) 106 | } 107 | ``` 108 | 109 | 这是否意味着作者和阅读可以同时读写?没有!首先,请记住 cond_wait 要求线程在返回之前重新获取互斥锁。因此,一次只有一个线程可以在临界区内执行代码(用**标记)! 110 | 111 | ```c 112 | read() { 113 | lock(&m); 114 | ** while (writing) 115 | ** cond_wait(&cv, &m) 116 | ** reading++; 117 | unlock(&m) 118 | /* Read here! */ 119 | lock(&m) 120 | ** reading-- 121 | ** cond_signal(&cv) 122 | unlock(&m) 123 | } 124 | ``` 125 | 126 | 作家必须等待每个人。锁定可确保相互排斥。 127 | 128 | ```c 129 | write() { 130 | lock(&m); 131 | ** while (reading || writing) 132 | ** cond_wait(&cv, &m); 133 | ** writing++; 134 | ** 135 | ** /* Write here! */ 136 | ** writing--; 137 | ** cond_signal(&cv); 138 | unlock(&m); 139 | } 140 | ``` 141 | 142 | 上面的候选人#3 也使用`pthread_cond_signal`;这只会唤醒一个线程。例如,如果许多读者等待作者完成,那么只有一个睡觉的读者会从他们的睡眠中醒来。读写器应该使用`cond_broadcast`,以便唤醒所有线程并检查它们的 while 循环条件。 143 | 144 | ## 饥饿的作家 145 | 146 | 上面的候选人#3 患有饥饿。如果读者经常到达,那么作家永远无法继续(“阅读”计数永远不会减少到零)。这被称为 _ 饥饿 _,并将在重负荷下被发现。我们的解决方法是为作者实现有限等待。如果作家到了,他们仍然需要等待现有的读者,但是未来的读者必须被置于“握笔”中并等待作者完成。 “握笔”可以使用变量和条件变量来实现(这样我们就可以在编写完成后唤醒线程)。 147 | 148 | 我们的计划是,当作家到来时,在等待当前读者完成之前,记录我们的写作意图(通过递增计数器“作者”)。草绘如下 - 149 | 150 | ```c 151 | write() { 152 | lock() 153 | writer++ 154 | 155 | while (reading || writing) 156 | cond_wait 157 | unlock() 158 | ... 159 | } 160 | ``` 161 | 162 | 当作家非零时,传入的读者将不被允许继续。注意'作家'表示作家已到达,而'阅读'和'写'计数器表示有 _ 有效 _ 读者或作者。 163 | 164 | ```c 165 | read() { 166 | lock() 167 | // readers that arrive *after* the writer arrived will have to wait here! 168 | while(writer) 169 | cond_wait(&cv,&m) 170 | 171 | // readers that arrive while there is an active writer 172 | // will also wait. 173 | while (writing) 174 | cond_wait(&cv,&m) 175 | reading++ 176 | unlock 177 | ... 178 | } 179 | ``` 180 | 181 | ## 尝试#4 182 | 183 | 以下是我们对 Reader-Writer 问题的第一个解决方案。请注意,如果您继续阅读“Reader Writer 问题”,那么您会发现我们通过给予编写者优先访问锁来解决“第二个 Reader Writer 问题”。该解决方案不是最佳的。然而,它满足了我们原来的问题(N 个活跃的读者,单个活跃的作家,如果有一个持续的读者流,避免作家的饥饿)。 184 | 185 | 你能找出任何改进吗?例如,你如何改进代码,以便我们只唤醒读者或一个作家? 186 | 187 | ```c 188 | int writers; // Number writer threads that want to enter the critical section (some or all of these may be blocked) 189 | int writing; // Number of threads that are actually writing inside the C.S. (can only be zero or one) 190 | int reading; // Number of threads that are actually reading inside the C.S. 191 | // if writing !=0 then reading must be zero (and vice versa) 192 | 193 | reader() { 194 | lock(&m) 195 | while (writers) 196 | cond_wait(&turn, &m) 197 | // No need to wait while(writing here) because we can only exit the above loop 198 | // when writing is zero 199 | reading++ 200 | unlock(&m) 201 | 202 | // perform reading here 203 | 204 | lock(&m) 205 | reading-- 206 | cond_broadcast(&turn) 207 | unlock(&m) 208 | } 209 | 210 | writer() { 211 | lock(&m) 212 | writers++ 213 | while (reading || writing) 214 | cond_wait(&turn, &m) 215 | writing++ 216 | unlock(&m) 217 | // perform writing here 218 | lock(&m) 219 | writing-- 220 | writers-- 221 | cond_broadcast(&turn) 222 | unlock(&m) 223 | } 224 | ``` -------------------------------------------------------------------------------- /docs/38.md: -------------------------------------------------------------------------------- 1 | # 同步,第 8 部分:环形缓冲区示例 2 | 3 | > 原文: 4 | 5 | ## 什么是环形缓冲区? 6 | 7 | 环形缓冲区是一种简单的,通常是固定大小的存储机制,其中连续内存被视为圆形,两个索引计数器跟踪队列的当前开始和结束。由于数组索引不是循环的,因此当移过数组末尾时,索引计数器必须回绕到零。当数据被添加(入队)到队列的前面或从队列的尾部移除(出队)时,缓冲区中的当前项形成一条似乎环绕轨道的列车![RingBuffer](img/261964eb034ed12b257f30989e6eb740.jpg)一个简单的(单线程)实现如下所示。注意 enqueue 和 dequeue 不会防止下溢或溢出 - 当队列已满时可以添加项目,并且当队列为空时可以删除项目。例如,如果我们将 20 个整数(1,2,3 ...)添加到队列中并且没有使任何项目出列,则值`17,18,19,20`将覆盖`1,2,3,4`。我们现在不会解决这个问题,相反,当我们创建多线程版本时,我们将确保在环形缓冲区满或空时分别阻塞入队和出队线程。 8 | 9 | ```c 10 | void *buffer[16]; 11 | int in = 0, out = 0; 12 | 13 | void enqueue(void *value) { /* Add one item to the front of the queue*/ 14 | buffer[in] = value; 15 | in++; /* Advance the index for next time */ 16 | if (in == 16) in = 0; /* Wrap around! */ 17 | } 18 | 19 | void *dequeue() { /* Remove one item to the end of the queue.*/ 20 | void *result = buffer[out]; 21 | out++; 22 | if (out == 16) out = 0; 23 | return result; 24 | } 25 | ``` 26 | 27 | ## 实现环形缓冲区有什么问题? 28 | 29 | 以下面的紧凑形式编写入队或出队方法非常诱人(N 是缓冲区的容量,例如 16): 30 | 31 | ```c 32 | void enqueue(void *value) 33 | b[ (in++) % N ] = value; 34 | } 35 | ``` 36 | 37 | 这种方法似乎有效(通过简单的测试等),但包含一个微妙的 bug。有足够的入队操作(超过 20 亿),`in`的 int 值将溢出并变为负值!模数(或“余数”)运算符`%`保留符号。因此,您最终可能会写入`b[-14]`! 38 | 39 | 紧凑的形式是正确的使用位屏蔽,只要 N 是 2 ^ x(16,32,64,...) 40 | 41 | ```c 42 | b[ (in++) & (N-1) ] = value; 43 | ``` 44 | 45 | 此缓冲区尚未阻止缓冲区下溢或溢出。为此,我们将转向我们的多线程尝试,它将阻塞线程,直到有空间或至少有一个项目要删除。 46 | 47 | ## 检查多线程实现的正确性(示例 1) 48 | 49 | 以下代码是不正确的实现。会发生什么? `enqueue`和/或`dequeue`会阻塞吗?相互排斥是否满足?缓冲区可以下溢吗?缓冲区可以溢出吗?为清楚起见,`pthread_mutex`缩短为`p_m`,我们假设 sem_wait 不能被中断。 50 | 51 | ```c 52 | #define N 16 53 | void *b[N] 54 | int in = 0, out = 0 55 | p_m_t lock 56 | sem_t s1,s2 57 | void init() { 58 | p_m_init(&lock, NULL) 59 | sem_init(&s1, 0, 16) 60 | sem_init(&s2, 0, 0) 61 | } 62 | 63 | enqueue(void *value) { 64 | p_m_lock(&lock) 65 | 66 | // Hint: Wait while zero. Decrement and return 67 | sem_wait( &s1 ) 68 | 69 | b[ (in++) & (N-1) ] = value 70 | 71 | // Hint: Increment. Will wake up a waiting thread 72 | sem_post(&s1) 73 | p_m_unlock(&lock) 74 | } 75 | void *dequeue(){ 76 | p_m_lock(&lock) 77 | sem_wait(&s2) 78 | void *result = b[(out++) & (N-1) ] 79 | sem_post(&s2) 80 | p_m_unlock(&lock) 81 | return result 82 | } 83 | ``` 84 | 85 | ## 分析 86 | 87 | 在阅读之前,看看你能找到多少错误。然后确定如果线程调用 enqueue 和 dequeue 方法会发生什么。 88 | 89 | * enqueue 方法在同一个信号量(s1)上等待和发布,类似于 equeue 和(s2),即我们递减值然后立即递增值,所以在函数结束时信号量值不变! 90 | * s1 的初始值为 16,因此信号量永远不会减少到零 - 如果环形缓冲区已满,则 enqueue 不会阻塞 - 因此溢出是可能的。 91 | * s2 的初始值为零,因此对 dequeue 的调用将始终阻塞并且永不返回! 92 | * 需要交换互斥锁和 sem_wait 的顺序(但是这个例子很破坏,这个 bug 没有效果!)##检查多线程实现的正确性(例 1) 93 | 94 | The following code is an incorrect implementation. What will happen? Will `enqueue` and/or `dequeue` block? Is mutual exclusion satisfied? Can the buffer underflow? Can the buffer overflow? For clarity `pthread_mutex` is shortened to `p_m` and we assume sem_wait cannot be interrupted. 95 | 96 | ```c 97 | void *b[16] 98 | int in = 0, out = 0 99 | p_m_t lock 100 | sem_t s1, s2 101 | void init() { 102 | sem_init(&s1,0,16) 103 | sem_init(&s2,0,0) 104 | } 105 | 106 | enqueue(void *value){ 107 | 108 | sem_wait(&s2) 109 | p_m_lock(&lock) 110 | 111 | b[ (in++) & (N-1) ] = value 112 | 113 | p_m_unlock(&lock) 114 | sem_post(&s1) 115 | } 116 | 117 | void *dequeue(){ 118 | sem_wait(&s1) 119 | p_m_lock(&lock) 120 | void *result = b[(out++) & 15] 121 | p_m_unlock(&lock) 122 | sem_post(&s2) 123 | 124 | return result; 125 | } 126 | ``` 127 | 128 | ### 分析 129 | 130 | * s2 的初始值为 0.因此,即使缓冲区为空,enqueue 也会在第一次调用 sem_wait 时阻塞! 131 | * s1 的初始值为 16.因此即使缓冲区为空,dequeue 也不会在第一次调用 sem_wait 时阻塞 - oops 下溢! dequeue 方法将返回无效数据。 132 | * 该代码不满足互斥;两个线程可以同时修改`in`或`out`!该代码似乎使用互斥锁。不幸的是锁从未用`pthread_mutex_init()`或`PTHREAD_MUTEX_INITIALIZER`初始化 - 所以锁可能不起作用(`pthread_mutex_lock`可能什么都不做) 133 | 134 | ## 正确实现环形缓冲区 135 | 136 | 伪代码(`pthread_mutex`缩短为`p_m`等)如下所示。 137 | 138 | 由于互斥锁存储在全局(静态)内存中,因此可以使用`PTHREAD_MUTEX_INITIALIZER`进行初始化。如果我们为堆上的互斥锁分配了空间,那么我们就会使用`pthread_mutex_init(ptr, NULL)` 139 | 140 | ```c 141 | #include 142 | #include 143 | // N must be 2^i 144 | #define N (16) 145 | 146 | void *b[N] 147 | int in = 0, out = 0 148 | p_m_t lock = PTHREAD_MUTEX_INITIALIZER 149 | sem_t countsem, spacesem 150 | 151 | void init() { 152 | sem_init(&countsem, 0, 0) 153 | sem_init(&spacesem, 0, 16) 154 | } 155 | ``` 156 | 157 | 入队方法如下所示。注意: 158 | 159 | * 锁定仅在关键部分(访问数据结构)期间保持。 160 | * 由于 POSIX 信号,完整的实现需要防止`sem_wait`的早期返回。 161 | 162 | ```c 163 | enqueue(void *value){ 164 | // wait if there is no space left: 165 | sem_wait( &spacesem ) 166 | 167 | p_m_lock(&lock) 168 | b[ (in++) & (N-1) ] = value 169 | p_m_unlock(&lock) 170 | 171 | // increment the count of the number of items 172 | sem_post(&countsem) 173 | } 174 | ``` 175 | 176 | `dequeue`实现如下所示。请注意同步调用`enqueue`的对称性。在这两种情况下,如果空格计数或项目数为零,则函数首先等待。 177 | 178 | ```c 179 | void *dequeue(){ 180 | // Wait if there are no items in the buffer 181 | sem_wait(&countsem) 182 | 183 | p_m_lock(&lock) 184 | void *result = b[(out++) & (N-1)] 185 | p_m_unlock(&lock) 186 | 187 | // Increment the count of the number of spaces 188 | sem_post(&spacesem) 189 | 190 | return result 191 | } 192 | ``` 193 | 194 | ## 值得深思 195 | 196 | * 如果交换`pthread_mutex_unlock`和`sem_post`调用的顺序会发生什么? 197 | * 如果交换`sem_wait`和`pthread_mutex_lock`调用的顺序会发生什么? -------------------------------------------------------------------------------- /docs/39.md: -------------------------------------------------------------------------------- 1 | # 同步复习题 2 | 3 | > 原文: 4 | 5 | ## 话题 6 | 7 | * 原子操作 8 | * 关键部分 9 | * 生产者消费者问题 10 | * 使用条件变量 11 | * 使用计数信号量 12 | * 实施障碍 13 | * 实现环形缓冲区 14 | * 使用 pthread_mutex 15 | * 实施生产者消费者 16 | * 分析多线程编码 17 | 18 | ## 问题 19 | 20 | * 什么是原子操作? 21 | * 为什么以下不能在并行代码中工作 22 | 23 | ```c 24 | //In the global section 25 | size_t a; 26 | //In pthread function 27 | for(int i = 0; i < 100000000; i++) a++; 28 | ``` 29 | 30 | 这会吗? 31 | 32 | ```c 33 | //In the global section 34 | atomic_size_t a; 35 | //In pthread function 36 | for(int i = 0; i < 100000000; i++) atomic_fetch_add(a, 1); 37 | ``` 38 | 39 | * 原子操作有哪些缺点?什么会更快:保持局部变量或许多原子操作? 40 | * 什么是关键部分? 41 | * 一旦确定了一个关键部分,确保一次只有一个线程在该部分中的一种方法是什么? 42 | * 在此确定关键部分 43 | 44 | ```c 45 | struct linked_list; 46 | struct node; 47 | void add_linked_list(linked_list *ll, void* elem){ 48 | node* packaged = new_node(elem); 49 | if(ll->head){ 50 | ll->head = 51 | }else{ 52 | packaged->next = ll->head; 53 | ll->head = packaged; 54 | ll->size++; 55 | } 56 | 57 | } 58 | 59 | void* pop_elem(linked_list *ll, size_t index){ 60 | if(index >= ll->size) return NULL; 61 | 62 | node *i, *prev; 63 | for(i = ll->head; i && index; i = i->next, index--){ 64 | prev = i; 65 | } 66 | 67 | //i points to the element we need to pop, prev before 68 | if(prev->next) prev->next = prev->next->next; 69 | ll->size--; 70 | void* elem = i->elem; 71 | destroy_node(i); 72 | return elem; 73 | } 74 | ``` 75 | 76 | 你有多紧张关键部分? 77 | 78 | * 什么是生产者消费者问题?在上一节中如何使用上述生产者消费者问题?生产者消费者问题与读者作家问题有什么关系? 79 | * 什么是条件变量?为什么在`while`循环上使用一个有优势? 80 | * 为什么这段代码很危险? 81 | 82 | ```c 83 | if(not_ready){ 84 | pthread_cond_wait(&cv, &mtx); 85 | } 86 | ``` 87 | 88 | * 什么是计数信号量?给我一个饼干罐/披萨盒/限量食品的类比。 89 | * 什么是线程障碍? 90 | * 使用计数信号量来实现屏障。 91 | 92 | * 编写生产者/消费者队列,生产者消费者栈怎么样? 93 | 94 | * 给我一个带条件变量的读写器锁的实现,用你需要的任何东西制作一个结构,它只需要能够支持以下函数 95 | 96 | ```c 97 | void reader_lock(rw_lock_t* lck); 98 | void writer_lock(rw_lock_t* lck); 99 | void reader_unlock(rw_lock_t* lck); 100 | void writer_unlock(rw_lock_t* lck); 101 | ``` 102 | 103 | 唯一的规范是在`reader_lock`和`reader_unlock`之间,没有编写者可以写。在写入器锁之间,一次只能写一个作者。 104 | 105 | * 编写代码以使用仅三个计数信号量来实现生产者使用者。假设可以有多个线程调用 enqueue 和 dequeue。确定每个信号量的初始值。 106 | * 编写代码以使用条件变量和互斥锁实现生产者使用者。假设可以有多个线程调用 enqueue 和 dequeue。 107 | * 使用 CV 实现 add(unsigned int)和 subtract(unsigned int)阻塞函数,这些函数永远不会允许全局值大于 100。 108 | * 使用 CV 为 15 个线程实现屏障。 109 | * 以下有多少陈述是正确的? 110 | * 可以有多个活跃的读者 111 | * 可以有多个活动作者 112 | * 当有活动的写入器时,活动读取器的数量必须为零 113 | * 如果有活动的阅读器,则活动写入器的数量必须为零 114 | * 作者必须等到当前活跃的读者完成 115 | * Todo:分析多线程代码片段 -------------------------------------------------------------------------------- /docs/4.md: -------------------------------------------------------------------------------- 1 | # #Piazza:何时以及如何寻求帮助 2 | 3 | > 原文: 4 | 5 | ### 目的 6 | 7 | 助教和学生助理会收到很多问题。有些是经过深入研究的,有些则不是。这是一个方便的指南,可以帮助你摆脱后者和前者。 (哦,我是否提到这是一个与实习经理一起得分的简单方法?) 8 | 9 | ### 问你自己... 10 | 11 | * 我在 EWS 上运行吗? 12 | * **我查了手册页吗?** 13 | * 我是否在 Piazza 寻找过类似的问题/跟进? 14 | * 我是否完全阅读了 MP / DS 规范? 15 | * 我看过所有的视频吗? 16 | * **我是否谷歌错误消息**(如有必要,还有一些排列)? 17 | * 我是否一点一点地尝试注释,打印和/或逐步执行部分代码以找出错误发生的确切位置? 18 | * **如果 TA 需要更多上下文,我是否将代码提交给 SVN?** 19 | * 我是否在我的 Piazza 帖子中包含了控制台/ GDB / Valgrind 输出**和**代码? 20 | * 我是否修复了与我遇到的问题无关的其他分段错误? 21 | * 我是否遵循良好的编程习惯? (即封装,限制重复的功能等) -------------------------------------------------------------------------------- /docs/40.md: -------------------------------------------------------------------------------- 1 | # 6.死锁 -------------------------------------------------------------------------------- /docs/41.md: -------------------------------------------------------------------------------- 1 | # 死锁,第 1 部分:资源分配图 2 | 3 | > 原文: 4 | 5 | ## 什么是资源分配图? 6 | 7 | 资源分配图跟踪哪个进程持有哪个资源以及哪个进程正在等待特定类型的资源。它是一个非常强大而简单的工具,用于说明交互过程如何解锁。如果进程是 _ 使用 _ 资源,则从资源节点向进程节点绘制箭头。如果进程是 _ 请求 _ 资源,则从进程节点向资源节点绘制箭头。 8 | 9 | 如果资源分配图中存在循环,并且循环中的每个资源仅提供一个实例,则进程将死锁。例如,如果进程 1 保存资源 A,则进程 2 保存资源 B,进程 1 等待 B,进程 2 等待 A,然后进程 1 和进程 2 将被死锁。 10 | 11 | 这是另一个示例,显示进程 1 和 2 获取资源 1 和 2,而进程 3 正在等待获取两个资源。在这个例子中没有死锁,因为没有循环依赖。 12 | 13 | ![ResourceAllocationGraph-Ex1.png](img/1e88c8266f91417008f275b2eec36df7.jpg) 14 | 15 | ## 僵局! 16 | 17 | 很多时候,我们不知道可以获取资源的具体顺序,因此我们可以绘制图表。 18 | 19 | ![](img/27f5f2c018c60372297e6cf5790934bc.jpg) 20 | 21 | 作为一种可能性矩阵。然后我们可以绘制箭头,看看是否有导致我们陷入僵局的定向版本。 22 | 23 | ![RAG Deadlock](img/2d19ee5fcdb522a5a43850cd1746a70a.jpg) 24 | 25 | 请考虑以下资源分配图(假设进程要求对文件进行独占访问)。如果你有一堆进程正在运行并且它们请求资源并且操作系统最终处于这种状态,那么你就会陷入僵局!您可能看不到这一点,因为操作系统可能* _ 抢占 _ 某些进程打破了循环,但仍有一个变化,您的三个孤独进程可能会死锁。您还可以使用`make`和规则依赖关系(例如我们的 parmake MP)制作这些图形。 26 | 27 | ![](img/bfa1f1caf816f2b844ae5b14da43f11b.svg) -------------------------------------------------------------------------------- /docs/42.md: -------------------------------------------------------------------------------- 1 | # 死锁,第 2 部分:死锁条件 2 | 3 | > 原文: 4 | 5 | ## 科夫曼的条件 6 | 7 | 有四个 _ 必需 _ 和 _ 充分 _ 条件的死锁。这些被称为科夫曼条件。 8 | 9 | * 相互排斥 10 | * 循环等待 11 | * 等等 12 | * 没有先发制人 13 | 14 | 如果你破坏其中任何一个,你就不会陷入僵局! 15 | 16 | 所有这些条件都是死锁所必需的,所以让我们依次讨论每个条件。首先是简单的 - 17 | 18 | * 相互排斥:无法共享资源 19 | * 循环等待:资源分配图中存在一个循环。存在一组进程{P1,P2,...},使得 P1 正在等待 P2 所持有的资源,其等待 P3,...,等待 P1。 20 | * 保持和等待:进程获取一组不完整的资源,并在等待其他资源时保留它们。 21 | * 没有先发制人:一旦进程获得了资源,就无法从进程中获取资源,并且进程不会自愿放弃资源。 22 | 23 | ## 打破科夫曼条件 24 | 25 | 两名学生需要一支笔和纸: 26 | 27 | * 学生们分享笔和纸。避免死锁,因为不需要互斥。 28 | * 学生们都同意在拿纸之前抓住笔。避免死锁,因为不能进行循环等待。 29 | * 学生们在一次操作中抓住笔和纸(“得到两个或得不到”)。因为没有 _ 保持和等待 _ 而避免死锁 30 | * 学生是朋友,会互相要求放弃持有的资源。避免死锁是因为允许抢占。 31 | 32 | ## 活锁 33 | 34 | 活锁是 _ 而不是 _ 死锁 - 35 | 36 | 考虑以下“解决方案” 37 | 38 | * 如果学生在 10 秒内无法获取其他资源,他们将放下一个持有的资源。该解决方案避免了死锁,但是它可能遭受活锁。 39 | 40 | 当进程继续执行但无法取得进展时会发生 Livelock。实际上可能会发生 Livelock,因为程序员已采取措施避免死锁。在上面的示例中,在繁忙的系统中,学生将不断释放第一个资源,因为他们永远无法获得第二个资源。系统没有死锁(学生进程仍在执行),但它也没有取得任何进展。 41 | 42 | ## 死锁预防/避免与死锁检测 43 | 44 | 死锁预防确保不会发生死锁,这意味着你打破了 coffman 条件。这在单个程序中是最好的,软件工程师可以选择打破某个 coffman 条件。考虑[银行家的算法](https://en.wikipedia.org/wiki/Banker's_algorithm)。它是另一种避免死锁的算法。整个实现超出了本课程的范围,只知道操作系统有更多通用算法。 45 | 46 | 另一方面,死锁检测允许系统进入死锁状态。进入后,系统会使用它所具有的信息来打破僵局。例如,考虑访问文件的多个进程。操作系统能够通过某种级别的文件描述符(通过 API 或直接抽象)跟踪所有文件/资源​​。如果操作系统在操作系统文件描述符表中检测到定向循环,则可能会中断一个进程(例如通过调度)并让系统继续运行。 47 | 48 | ## 餐饮哲学家 49 | 50 | Dining Philosophers 问题是一个经典的同步问题。想象一下,我邀请 N(让我们说 5 位)哲学家吃饭。我们将坐在一张桌子上,用五根筷子(每个哲学家之间一个)。哲学家在想要吃饭或思考之间交替。吃饭的哲学家必须在他们的位置两侧拾起两根筷子(原始问题要求每个哲学家都有两把分叉)。然而,这些筷子与他的邻居分享。 51 | 52 | ![5DiningPhilosophers](img/faa7ac1f5f07a2ceee3dcc5057f329c6.jpg) 53 | 54 | 是否有可能设计出一种有效的解决方案,使所有哲学家都能吃到它?或者,一些哲学家会挨饿,从未获得第二根筷子吗?或者他们都会陷入僵局?例如,想象每个客人拿起他们左边的筷子,然后等待他们右边的筷子自由。糟糕 - 我们的哲学家陷入僵局! -------------------------------------------------------------------------------- /docs/43.md: -------------------------------------------------------------------------------- 1 | # 僵局,第 3 部分:餐饮哲学家 2 | 3 | > 原文: 4 | 5 | ## 背景故事 6 | 7 | ![](img/c17a56237998885b1dd4cd23e86f4c90.jpg) 8 | 9 | 所以你让你的哲学家坐在桌子周围,想要吃一些意大利面(或其他任何东西),他们真的很饿。每个哲学家本质上是相同的,意味着每个哲学家都有基于另一个哲学家的相同指令集,即你不能告诉每个甚至哲学家做一件事,每个奇怪的哲学家做另一件事。 10 | 11 | ## 解决方案失败 12 | 13 | ## 左右僵局 14 | 15 | 我们做什么?我们来试试一个简单的解决方案 16 | 17 | ```c 18 | void* philosopher(void* forks){ 19 | info phil_info = forks; 20 | pthread_mutex_t* left_fork = phil_info->left_fork; 21 | pthread_mutex_t* right_fork = phil_info->right_fork; 22 | while(phil_info->simulation){ 23 | pthread_mutex_lock(left_fork); 24 | pthread_mutex_lock(right_fork); 25 | eat(left_fork, right_fork); 26 | pthread_mutex_unlock(left_fork); 27 | pthread_mutex_unlock(right_fork); 28 | } 29 | } 30 | ``` 31 | 32 | 但这遇到了问题!如果每个人拿起他们的左叉并且正在他们的右叉上等待怎么办?我们已经使该计划陷入僵局。重要的是要注意,死锁不会一直发生,并且随着哲学家数量的增加,这种解决方案死锁的可能性会下降。值得注意的是,最终这个解决方案将陷入僵局,让线程挨饿哪个坏。 33 | 34 | ## 的 tryLock?更喜欢活锁 35 | 36 | 所以现在你正在考虑打破其中一个 coffman 条件。我们有 37 | 38 | * 相互排斥 39 | * 没有先发制人 40 | * 等等 41 | * 循环等待 42 | 43 | 好吧,我们不能让两个哲学家同时使用一个分叉,相互排斥是不合适的。在我们当前的简单模型中,一旦他/她掌握它,我们就不能让哲学家放开互斥锁,所以我们现在就把这个解决方案拿出来 - 底部有一些注释。有关此解决方案的页面让我们休息一下等待! 44 | 45 | ```c 46 | void* philosopher(void* forks){ 47 | info phil_info = forks; 48 | pthread_mutex_t* left_fork = phil_info->left_fork; 49 | pthread_mutex_t* right_fork = phil_info->right_fork; 50 | while(phil_info->simulation){ 51 | pthread_mutex_lock(left_fork); 52 | int failed = pthread_mutex_trylock(right_fork); 53 | if(!failed){ 54 | eat(left_fork, right_fork); 55 | pthread_mutex_unlock(right_fork); 56 | } 57 | pthread_mutex_unlock(left_fork); 58 | } 59 | } 60 | ``` 61 | 62 | 现在我们的哲学家拿起左叉并试图抓住右边。如果它可用,他们会吃。如果它不可用,他们将左叉放下并重试。没有死锁! 63 | 64 | 但有个问题。如果所有的哲学家同时拿起他们的左手,试图抓住他们的权利,把他们的左手放下,拿起他们的左手,试图抓住他们的权利......我们现在已经解决了我们的解决方案!我们可怜的哲学家仍在挨饿,所以让我们给他们一些适当的解决方案。 65 | 66 | ## 可行的解决方案 67 | 68 | ## 仲裁员(天真和高级)。 69 | 70 | 天真的仲裁者解决方案有一个仲裁员(例如互斥)。让每个哲学家都要求仲裁员吃饭。这种解决方案允许一个哲学家一次吃。当他们完成后,另一位哲学家可以请求允许进食。 71 | 72 | 这可以防止死锁,因为没有循环等待!没有哲学家必须等待任何其他哲学家。 73 | 74 | 高级仲裁者解决方案是实施一个类,确定哲学家的分叉是否在仲裁员的掌握之中。如果他们是,他们把它们交给哲学家,让他吃,并把分叉拿回来。这有额外的好处,可以让多个哲学家同时吃。 75 | 76 | ### 问题: 77 | 78 | * 这些解决方案很慢 79 | * 他们有一个单点的失败,仲裁员使其成为瓶颈 80 | * 仲裁者也需要公平,并能够在第二个解决方案中确定死锁 81 | * 在实际系统中,仲裁员倾向于将重复的分叉交给那些因为过程调度而吃的哲学家 82 | 83 | ## 离开桌子(Stallings 的解决方案) 84 | 85 | 为什么第一个解决方案陷入僵局?那么有 n 个哲学家和 n 个筷子。如果餐桌上只有 1 个 philsopher 怎么办?我们可以陷入僵局吗?没有。 86 | 87 | 2 个 philsophers 怎么样? 3? ......你可以看到它的发展方向。斯托林斯的解决方案说要将哲学家从桌子上移除,直到陷入僵局为止 - 想想桌上哲学家的神奇数量是多少。在实际系统中这样做的方法是通过信号量并让一定数量的哲学家通过。 88 | 89 | ### Problems: 90 | 91 | * 该解决方案需要大量的上下文切换,这对 CPU 来说非常昂贵 92 | * 你需要事先知道资源的数量,才能让那些哲学家知道 93 | * 再次优先考虑已经吃过的过程。 94 | 95 | ## 部分订购(Dijkstra 的解决方案) 96 | 97 | 这是 Dijkstra 的解决方案(他是在考试中提出这个问题的人)。为什么第一个解决方案陷入僵局? Dijkstra 认为最后一个拿起左叉的哲学家(使解决方案陷入僵局)应该选择他的权利。他用数字 1..n 来完成它,并告诉每个哲学家拿起他的较低数字叉。 98 | 99 | 让我们再次遇到死锁情况。每个人都试图先拿起他们的低号码叉。哲学家 1 获得分叉 1,哲学家 2 获得分叉 2,依此类推,直到我们到达哲学家 n。他们必须在 fork 1 和 n 之间进行选择。 fork 1 已被哲学家 1 所阻挡,所以他们无法拿起那个分叉,这意味着他不会拿起分叉。我们已经打破了循环等待!意味着死锁是不可能的。 100 | 101 | ### Problems: 102 | 103 | * 在获取任何资源之前,哲学家需要按顺序了解资源集。 104 | * 您需要为所有资源定义部分订单。 105 | * 优先考虑已经吃过的哲学家。 106 | 107 | ## 高级解决方案 108 | 109 | 还有许多更为先进的解决方案,非详尽列表包括 110 | 111 | * 清洁/脏叉(钱德拉/米斯拉解决方案) 112 | * 演员模型(其他消息传递模型) 113 | * 超级仲裁员(复杂的管道) -------------------------------------------------------------------------------- /docs/44.md: -------------------------------------------------------------------------------- 1 | # 死锁复习题 2 | 3 | > 原文: 4 | 5 | ## 话题 6 | 7 | Coffman 条件资源分配图用餐哲学家 8 | 9 | * DP 解决方案失败 10 | * 活锁 DP 解决方案 11 | * 工作 DP 解决方案:优势/缺点 12 | 13 | ## 问题 14 | 15 | * 什么是科夫曼条件? 16 | * 每个科夫曼条件意味着什么? (例如,你能提供每个的定义) 17 | * 给出一个真实的例子,依次打破每个 Coffman 条件。需要考虑的情况:画家,油漆,油漆刷等。您如何确保工作完成? 18 | * 能够识别 Dining Philosophers 代码何时导致死锁(或不)。例如,如果您看到以下代码片段不满足 Coffman 条件? 19 | 20 | ```c 21 | // Get both locks or none. 22 | pthread_mutex_lock( a ); 23 | if( pthread_mutex_trylock( b ) ) { /*failed*/ 24 | pthread_mutex_unlock( a ); 25 | ... 26 | } 27 | ``` 28 | 29 | * 如果一个线程调用 30 | 31 | ```c 32 | pthread_mutex_lock(m1) // success 33 | pthread_mutex_lock(m2) // blocks 34 | ``` 35 | 36 | 和另一个线程调用 37 | 38 | ```c 39 | pthread_mutex_lock(m2) // success 40 | pthread_mutex_lock(m1) // blocks 41 | ``` 42 | 43 | 会发生什么?为什么?如果第三个线程调用`pthread_mutex_lock(m1)`会发生什么? 44 | 45 | * 有多少进程被阻止?像往常一样,假设一个进程能够获得下面列出的所有资源,就能够完成。 46 | * P1 获得 R1 47 | * P2 获得 R2 48 | * P1 收购 R3 49 | * P2 等待 R3 50 | * P3 收购 R5 51 | * P1 等待 R4 52 | * P3 等待 R1 53 | * P4 等待 R5 54 | * P5 等待 R1 55 | 56 | (画出资源图!) -------------------------------------------------------------------------------- /docs/45.md: -------------------------------------------------------------------------------- 1 | # 7.进程间通信&amp;调度 -------------------------------------------------------------------------------- /docs/46.md: -------------------------------------------------------------------------------- 1 | # 虚拟内存,第 1 部分:虚拟内存简介 2 | 3 | > 原文: 4 | 5 | ## 什么是虚拟内存? 6 | 7 | 在非常简单的嵌入式系统和早期计算机中,进程直接访问存储器,即“地址 1234”对应于存储在物理存储器的特定部分中的特定字节。在现代系统中,情况已不再如此。而是每个过程都是孤立的;并且在特定 CPU 指令的地址或进程的数据与物理存储器的实际字节(“RAM”)之间存在转换过程。内存地址不再是“真实的”;该进程在虚拟内存中运行。虚拟内存不仅可以保证进程的安全(因为一个进程无法直接读取或修改另一个进程的内存),它还允许系统有效地为不同的进程分配和重新分配内存部分。 8 | 9 | ## 什么是 MMU? 10 | 11 | 内存管理单元是 CPU 的一部分。它将虚拟内存地址转换为物理地址。如果当前没有从特定虚拟地址到物理地址的映射,或者当前 CPU 指令尝试写入该进程仅具有读访问权的位置,则 MMU 也可以中断 CPU。 12 | 13 | ## 那么我们如何将虚拟地址转换为物理地址呢? 14 | 15 | 想象一下,你有一台 32 位机器。指针可以保持 32 位,即它们可以寻址 2 ^ 32 个不同的位置,即 4GB 的存储器(我们将遵循一个地址的标准约定可以保持一个字节)。 16 | 17 | 想象一下,我们有一个大表 - 这里是聪明的部分 - 存储在内存中!对于每个可能的地址(全部 40 亿个),我们将存储“真实”即物理地址。每个物理地址需要 4 个字节(保存 32 位)。该方案需要 160 亿个字节来存储所有条目。哎呀 - 我们的查找方案会占用我们可能为 4GB 机器购买的所有内存。我们需要做得比这更好。我们的查找表最好小于我们的内存,否则我们的实际程序和操作系统数据将没有空间。解决方案是将内存分块为称为“页面”和“帧”的小区域,并为每个页面使用查找表。 18 | 19 | ## 什么是页面?他们中有多少人? 20 | 21 | 页面是一块虚拟内存。 Linux 操作系统上的典型块大小为 4KB(即 2 ^ 12 个地址),但您可以找到更大块的示例。 22 | 23 | 因此,我们不是谈论单个字节,而是讨论 4KB 的块,而每个块都称为页面。我们也可以为我们的页面编号(“第 0 页”“第 1 页”等) 24 | 25 | ## EX:32 位机器中有多少页(假设页面大小为 4KB)? 26 | 27 | 答案:2 ^ 32 地址/ 2 ^ 12 = 2 ^ 20 页。 28 | 29 | 请记住,2 ^ 10 是 1024,所以 2 ^ 20 有点超过一百万。 30 | 31 | 对于 64 位机器,2 ^ 64/2 ^ 12 = 2 ^ 52,大约 10 ^ 15 页。 32 | 33 | ## 什么是框架? 34 | 35 | 帧(或有时称为“页面帧”)是 _ 物理存储器 _ 或 RAM(=随机存取存储器)的块。这种内存有时被称为“主存储”(与较慢的二级存储形成对比,例如具有较低访问时间的旋转磁盘) 36 | 37 | 帧与虚拟页面的字节数相同。如果 32 位机器具有 2 ^ 32(4GB)的 RAM,那么在机器的可寻址空间中将有相同数量的 RAM。 64 位机器不太可能拥有 2 ^ 64 字节的 RAM - 你能明白为什么吗? 38 | 39 | ## 什么是页面表,它有多大? 40 | 41 | 页表是页面与帧之间的映射。例如,第 1 页可能映射到第 45 帧,第 2 页映射到第 30 帧。其他帧当前可能未使用或分配给其他正在运行的进程,或者由操作系统在内部使用。 42 | 43 | 一个简单的页表只是一个数组,`int frame = table[ page_num ];` 44 | 45 | 对于具有 4KB 页面的 32 位机器,每个条目需要保持帧号 - 即 20 位,因为我们计算出有 2 ^ 20 帧。这是每个条目 2.5 个字节!实际上,我们将每个条目最多四个字节舍入,并找到这些备用位的用途。每个条目 4 个字节 x 2 ^ 20 个条目= 4 MB 的物理内存需要保存页表。 46 | 47 | 对于具有 4KB 页面的 64 位机器,每个条目需要 52 位。让每个条目向上舍入到 64 位(8 字节)。有 2 ^ 52 个条目,即 2 ^ 55 个字节(大约 40 peta 字节...)哎呀我们的页面表太大了。 48 | 49 | 在 64 位体系结构中,内存地址是稀疏的,因此我们需要一种机制来减少页表大小,因为大多数条目永远不会被使用。 50 | 51 | ![](img/bddaf711271daa8286dff3dbac48867e.jpg) 52 | 53 | 页面表的可视示例在这里。想象一下,访问一个数组并抓住数组元素。 54 | 55 | ## 什么是偏移量以及它是如何使用的? 56 | 57 | 请记住,我们的页面表将页面映射到帧,但每个页面都是一个连续的地址块。我们如何计算在特定帧内使用哪个特定字节?解决方案是直接重用虚拟内存地址的最低位。例如,假设我们的进程正在读取以下地址 - `VirtualAddress = 11110000111100001111000010101010 (binary)` 58 | 59 | 在页面大小为 256 字节的计算机上,最低的 8 位(10101010)将用作偏移量。剩余的高位将是页码(111100001111000011110000)。 60 | 61 | ## 多级页表 62 | 63 | 多级页面是 64 位体系结构的页表大小问题的一种解决方案。我们将看一下最简单的实现 - 一个两级页表。每个表都是指向下一级表的指针列表,并非所有子表都需要存在。一个示例,32 位架构的两级页表如下所示 - 64 | 65 | ``` 66 | VirtualAddress = 11110000111111110000000010101010 (binary) 67 | |_Index1_|| || | 10 bit Directory index 68 | |_Index2_|| | 10 bit Sub-table index 69 | |__________| 12 bit offset (passed directly to RAM) 70 | ``` 71 | 72 | 在上述方案中,确定帧号需要两次存储器读取:最顶层的 10 位用于页表的目录中。如果每个条目使用 2 个字节,我们只需要 2KB 来存储整个目录。每个子表将指向物理帧(即,需要 4 个字节来存储 20 位)。但是,对于只有微小内存需求的进程,我们只需要为低内存地址(用于堆和程序代码)和高内存地址(用于栈)指定条目。每个子表是 1024 个条目×4 个字节,即每个子表 4KB。因此,我们的多级页表的总内存开销从 4MB(单级)缩减到 3 帧内存(12KB)! 73 | 74 | 页表会使内存访问速度变慢吗? (什么是 TLB) 75 | 76 | 是的 - 很重要! (但是由于聪明的硬件,通常没有...)与直接读取或写入内存相比。对于单页表,我们的机器现在慢两倍! (需要两次内存访问)对于两级页表,内存访问速度现在是后者的三倍。 (需要三次内存访问) 77 | 78 | 为了克服这种开销,MMU 包括最近使用的虚拟页面到帧查找的关联缓存。此缓存称为 TLB(“转换后备缓冲区”)。每次需要将虚拟地址转换为物理内存位置时,都会与页表并行查询 TLB。对于大多数程序的大多数内存访问,TLB 很可能缓存了结果。但是,如果程序没有良好的高速缓存一致性(例如从许多不同页面的随机存储器位置读取),则 TLB 将不具有结果高速缓存,并且现在 MMU 必须使用更慢的页表来确定物理帧。 79 | 80 | ![](img/ef4830cf8767dd32936281aca42c8eac.jpg) 81 | 82 | 这可能是分割多级页表的方式。 83 | 84 | ## 高级框架和页面保护 85 | 86 | ## 框架可以在进程之间共享吗?他们可以专业吗? 87 | 88 | 是!除了存储帧号之外,页表还可用于存储进程是写入还是仅读取特定帧。然后可以在多个进程之间安全地共享只读帧。例如,可以在将代码动态加载到进程内存中的所有进程之间共享 C 库指令代码。每个进程只能读取该内存。这意味着如果您尝试写入内存中的只读页面,您将获得`SEGFAULT`。这就是为什么有时内存访问段错误,有时它们没有,这一切都取决于你的硬件是否说你可以访问。 89 | 90 | 此外,进程可以使用`mmap`系统调用与子进程共享页面。 `mmap`是一个有趣的调用,因为它不是将每个虚拟地址绑定到物理帧,而是将其与其他东西联系起来。其他东西可以是文件,GPU 单元或您可以想到的任何其他内存映射操作!写入内存地址可能会写入设备,或者写入可能会被操作系统暂停,但这是一个非常强大的抽象,因为操作系统通常能够执行优化(多个进程内存映射同一个文件可以有内核)创建一个映射)。 91 | 92 | ## 页面表中还存储了什么?为什么? 93 | 94 | 除了上面讨论的只读位和使用统计信息之外,通常至少存储只读,修改和执行信息。 95 | 96 | ## 什么是页面错误? 97 | 98 | 页面错误是指正在运行的程序试图访问未映射到物理内存的地址空间中的某个虚拟内存。页面错误也会在其他情况下发生。 99 | 100 | 页面错误有三种类型 101 | 102 | **次要**如果页面尚未映射,但它是有效地址。这可能是`sbrk(2)`要求的内存,但尚未写入,这意味着操作系统可以在分配空间之前等待第一次写入。操作系统只需创建页面,将其加载到内存中,然后继续操作。 103 | 104 | **Major** 如果映射到页面不在内存中但在磁盘上。这样做是将页面交换到内存并交换另一页。如果这种情况经常发生,你的程序就会被称为 _thrash_ MMU。 105 | 106 | **无效**当您尝试写入不可写存储器地址或读取不可读存储器地址时。 MMU 生成无效错误,操作系统通常会生成`SIGSEGV`意味着分段违规,这意味着您在可以写入的段外编写。 107 | 108 | ### 只读位 109 | 110 | 只读位将页面标记为只读。尝试写入页面将导致页面错误。然后页面错误将由内核处理。只读页面的两个示例包括在多个进程之间共享 c 运行时库(为了安全起见,您不希望允许一个进程修改该库);和 Copy-On-Write,其中复制页面的成本可以延迟到第一次写入发生。 111 | 112 | ### 肮脏的一点 113 | 114 | [http://en.wikipedia.org/wiki/Page_table#Page_table_data](http://en.wikipedia.org/wiki/Page_table#Page_table_data) 115 | 116 | > 脏位允许性能优化。磁盘上的页面被分页到物理内存,然后从中读取,然后再次被分页,不需要写回磁盘,因为页面没有更改。但是,如果页面在页面被写入之后被写入,则将设置其脏位,指示必须将页面写回到后备存储。此策略要求后备存储在将页面分页到内存后保留页面的副本。当不使用脏位时,后备存储只需要与任何时刻所有页面调出页面的瞬时总大小一样大。使用脏位时,物理内存和后备存储中都会存在一些页面。 117 | 118 | ### 执行位 119 | 120 | 执行位定义页面中的字节是否可以作为 CPU 指令执行。通过禁用页面,它可以防止恶意存储在进程存储器中的代码(例如,通过栈溢出)被轻易执行。 (进一步阅读: [http://en.wikipedia.org/wiki/NX_bit#Hardware_background](http://en.wikipedia.org/wiki/NX_bit#Hardware_background) ) 121 | 122 | ### 了解更多 123 | 124 | 在[ [http://wiki.osdev.org/Paging](http://wiki.osdev.org/Paging) ]中讨论了对 x86 平台上的分页和页面位的越来越多的技术讨论。 -------------------------------------------------------------------------------- /docs/47.md: -------------------------------------------------------------------------------- 1 | # 管道,第 1 部分:管道介绍 2 | 3 | > 原文: 4 | 5 | ## 什么是 IPC? 6 | 7 | 进程间通信是一个进程与另一个进程通信的任何方式。你已经看过这种虚拟内存的一种形式了!一段虚拟内存可以在父母和孩子之间共享,从而实现通信。您可能希望将该内存包装在`pthread_mutexattr_setpshared(&attrmutex, PTHREAD_PROCESS_SHARED);`互斥锁(或进程宽互斥锁)中以防止竞争条件。 8 | 9 | 有更多标准的 IPC 方式,比如管道!考虑您是否在终端中键入以下内容 10 | 11 | ```source-shell 12 | $ ls -1 | cut -d'.' -f1 | uniq | sort | tee dir_contents 13 | ``` 14 | 15 | 以下代码的作用是什么(如果你愿意,你可以跳过这个并不重要)?那么它`ls`是当前目录(-1 表示它每行输出一个条目)。然后`cut`命令在第一个周期之前获取所有内容。 Uniq 确保所有行都是 uniq,排序将它们和 tee 输出排序到文件。 16 | 17 | 重要的是,bash 创建 **5 个独立的进程**,并将它们的标准出口/标准连接到管道,其中的跟踪看起来像这样。 18 | 19 | (0)ls(1)------&gt;(0)cut(1)-------&gt;(0)uniq(1)------&gt;(0)排序(1)------&gt;(0)tee(1) 20 | 21 | 管道中的数字是每个进程的文件描述符,箭头表示重定向或管道输出的位置。 22 | 23 | ## 什么是管道? 24 | 25 | POSIX 管道几乎就像它的真正对应物 - 你可以在一端填充字节,它们将以相同的顺序出现在另一端。然而,与真实管道不同,进程始终在同一方向,一个文件描述符用于读取,另一个文件描述符用于写入。 `pipe`系统调用用于创建管道。 26 | 27 | ```c 28 | int filedes[2]; 29 | pipe (filedes); 30 | printf("read from %d, write to %d\n", filedes[0], filedes[1]); 31 | ``` 32 | 33 | 这些文件描述符可以与`read`一起使用 - 34 | 35 | ```c 36 | // To read... 37 | char buffer[80]; 38 | int bytesread = read(filedes[0], buffer, sizeof(buffer)); 39 | ``` 40 | 41 | 和`write` - 42 | 43 | ```c 44 | write(filedes[1], "Go!", 4); 45 | ``` 46 | 47 | ## 如何使用管道与子进程通信? 48 | 49 | 使用管道的常用方法是在分叉之前创建管道。 50 | 51 | ```c 52 | int filedes[2]; 53 | pipe (filedes); 54 | pid_t child = fork(); 55 | if (child > 0) { /* I must be the parent */ 56 | char buffer[80]; 57 | int bytesread = read(filedes[0], buffer, sizeof(buffer)); 58 | // do something with the bytes read 59 | } 60 | ``` 61 | 62 | 然后,孩子可以将消息发送回父母: 63 | 64 | ```c 65 | if (child == 0) { 66 | write(filedes[1], "done", 4); 67 | } 68 | ``` 69 | 70 | ## 我可以在一个过程中使用管道吗? 71 | 72 | 简短回答:是的,但我不确定你为什么要大声笑! 73 | 74 | 这是一个向自己发送消息的示例程序: 75 | 76 | ```c 77 | #include 78 | #include 79 | #include 80 | 81 | int main() { 82 | int fh[2]; 83 | pipe(fh); 84 | FILE *reader = fdopen(fh[0], "r"); 85 | FILE *writer = fdopen(fh[1], "w"); 86 | // Hurrah now I can use printf rather than using low-level read() write() 87 | printf("Writing...\n"); 88 | fprintf(writer,"%d %d %d\n", 10, 20, 30); 89 | fflush(writer); 90 | 91 | printf("Reading...\n"); 92 | int results[3]; 93 | int ok = fscanf(reader,"%d %d %d", results, results + 1, results + 2); 94 | printf("%d values parsed: %d %d %d\n", ok, results[0], results[1], results[2]); 95 | 96 | return 0; 97 | } 98 | ``` 99 | 100 | 以这种方式使用管道的问题是写入管道可能阻塞,即管道仅具有有限的缓冲容量。如果管道已满,写入过程将阻止!缓冲区的最大大小取决于系统;典型值从 4KB 到 128KB。 101 | 102 | ```c 103 | int main() { 104 | int fh[2]; 105 | pipe(fh); 106 | int b = 0; 107 | #define MESG "..............................." 108 | while(1) { 109 | printf("%d\n",b); 110 | write(fh[1], MESG, sizeof(MESG)) 111 | b+=sizeof(MESG); 112 | } 113 | return 0; 114 | } 115 | ``` 116 | 117 | 参见[管道,第 2 部分:管道编程秘密](/angrave/SystemProgramming/wiki/Pipes%2C-Part-2%3A-Pipe-programming-secrets) -------------------------------------------------------------------------------- /docs/49.md: -------------------------------------------------------------------------------- 1 | # 文件,第 1 部分:使用文件 2 | 3 | > 原文: 4 | 5 | ## 两种类型的文件 6 | 7 | 在 linux 上,有两个带文件的抽象。第一个是 linux `fd`级抽象,意味着你可以使用 8 | 9 | * `open` 10 | * `read` 11 | * `write` 12 | * `close` 13 | * `lseek` 14 | * `fcntl` ...... 15 | 16 | 等等。 linux 界面非常强大且富有表现力,但有时我们需要可移植性(例如,如果我们正在为 mac 或 windows 编写)。这就是 C 的抽象发挥作用的地方。在不同的操作系统上,C 使用低级函数来创建可以在任何地方使用的文件的包装器,这意味着 Linux 上的 C 使用上述调用。 C 有以下几点 17 | 18 | * `fopen` 19 | * `fread`或`fgetc/fgets`或`fscanf` 20 | * `fwrite`或`fprintf` 21 | * `fclose` 22 | * `fflush` 23 | 24 | 但是你没有得到 linux 给你系统调用的表达能力,你可以使用`int fileno(FILE* stream)`和`FILE* fdopen(int fd...)`在它们之间来回转换。 25 | 26 | 需要注意的另一个重要方面是 C 文件是**缓冲**,这意味着默认情况下可能无法写入内容。您可以使用 C 选项更改它。 27 | 28 | ## 如何判断文件有多大? 29 | 30 | 对于小于 long 的文件,使用 fseek 和 ftell 是一种简单的方法来完成此任务: 31 | 32 | 移动到文件末尾并找出当前位置。 33 | 34 | ```c 35 | fseek(f, 0, SEEK_END); 36 | long pos = ftell(f); 37 | ``` 38 | 39 | 这告诉我们文件中的当前位置(以字节为单位) - 即文件的长度! 40 | 41 | `fseek`也可用于设置绝对位置。 42 | 43 | ```c 44 | fseek(f, 0, SEEK_SET); // Move to the start of the file 45 | fseek(f, posn, SEEK_SET); // Move to 'posn' in the file. 46 | ``` 47 | 48 | 父进程或子进程中的所有未来读取和写入都将遵循此位置。注意从文件中写入或读取将改变当前位置。 49 | 50 | 有关更多信息,请参见 fseek 和 ftell 的手册页。 51 | 52 | ## 但尽量不要这样做 53 | 54 | **注意:由于 C 语言**的怪癖,在通常情况下不建议这样做。这个怪癖是多头只需要 **4 字节大**意味着 ftell 可以返回的最大大小略低于 2 千兆字节(我们现在知道我们的文件可能是几百千兆字节甚至太字节数分布式文件系统)。我们该怎么做呢?使用`stat`!我们将在后面的部分介绍 stat,但这里有一些代码可以告诉你文件的大小 55 | 56 | ```c 57 | struct stat buf; 58 | if(stat(filename, &buf) != -1){ 59 | return -1; 60 | } 61 | return (ssize_t)buf.st_size; 62 | ``` 63 | 64 | buf.st_size 的类型为 off_t,足够 _ 疯狂 _ 大文件。 65 | 66 | ## 如果子进程使用`fclose`或`close`关闭文件流会发生什么? 67 | 68 | 关闭文件流对每个进程都是唯一的。其他进程可以继续使用自己的文件句柄。请记住,在创建子项时,甚至文件的相对位置都会复制所有内容。 69 | 70 | ## mmap for files 怎么样? 71 | 72 | mmap 的一般用途是将文件映射到内存。这并不意味着文件立即被 malloc'ed 到内存中。以下面的代码为例。 73 | 74 | ``` 75 | int fd = open(...); //File is 2 Pages 76 | char* addr = mmap(..fd..); 77 | addr[0] = 'l'; 78 | ``` 79 | 80 | 内核可能会说,“好吧,我看到你想把文件映射到内存中,所以我会在你的地址空间中预留一些空间,即文件的长度”。这意味着当您写入 addr [0]时,您实际上正在写入文件的第一个字节。内核实际上也可以做一些优化。它不是将文件加载到内存中,而是一次只能加载页面,因为如果文件是 1024 页;您只能访问 3 或 4 页,这使得加载整个文件浪费时间(这就是页面错误如此强大的原因!它们让操作系统控制您使用文件的程度)。 81 | 82 | ## 对于每个 mmap 83 | 84 | 请记住,一旦完成`mmap` ping 您`munmap`告诉操作系统您不再使用已分配的页面,因此操作系统可以将其写回磁盘并在需要时将地址返回给您 malloc 以后。 -------------------------------------------------------------------------------- /docs/5.md: -------------------------------------------------------------------------------- 1 | # 编程技巧,第 1 部分 2 | 3 | > 原文: 4 | 5 | ## 使用`cat`作为 IDE 6 | 7 | 谁需要编辑? IDE?我们可以使用`cat`!您已经看到`cat`用于读取文件的内容,但它也可用于读取标准输入并将其发送回标准输出。 8 | 9 | ``` 10 | $ cat 11 | HELLO 12 | HELLO 13 | ``` 14 | 15 | 要完成从输入流中读取,请按`CTRL-D`关闭输入流 16 | 17 | 让我们使用`cat`将标准输入发送到文件。我们将使用'&gt;'将其输出重定向到文件: 18 | 19 | ``` 20 | $ cat > myprog.c 21 | #include 22 | int main() {printf("Hi!");return 0;} 23 | ``` 24 | 25 | (注意!不允许删除和撤消...)完成后按`CTRL-D`。 26 | 27 | ## 使用`perl`正则表达式编辑代码(又名“记住你的 perl 馅饼”) 28 | 29 | 如果要更改多个文本文件(例如源代码),则使用正则表达式是一个有用的技巧。 `perl`使得编辑文件非常容易。记住'perl pie'并在网上搜索...... 30 | 31 | 一个例子。假设我们要在当前目录的所有.c 文件中将序列“Hi”更改为“Bye”。然后我们可以编写一个简单的替换模式,它将在所有文件中的每一行上执行: 32 | 33 | ``` 34 | $ perl -p -i -e 's/Hi/Bye/' *.c 35 | ``` 36 | 37 | (如果你弄错了,不要惊慌,原始文件仍然存在;他们只有扩展名.bak)显然你可以用正则表达式做更多的事情而不是改变 Hi 到 Bye。 38 | 39 | ## 使用你的 shell `!!` 40 | 41 | 要重新运行最后一个命令,只需键入`!!`并按`return`重新运行以 g 类型`!g`开头的最后一个命令,然后按`return` 42 | 43 | ## 使用你的 shell `&&` 44 | 45 | 厌倦了运行`make`或`gcc`然后运行程序,如果编译正常?相反,使用&amp;&amp;将这些命令链接在一起 46 | 47 | ``` 48 | $ gcc program.c && ./a.out 49 | ``` 50 | 51 | ## Make 可以做的不仅仅是制作 52 | 53 | 您也可以尝试在 Makefile 中添加一行以进行编译,然后运行您的程序。 54 | 55 | ``` 56 | run : $(program) 57 | ./$(program) 58 | ``` 59 | 60 | 然后跑 61 | 62 | ``` 63 | $ make run 64 | ``` 65 | 66 | 将确保您所做的任何更改都已编译,并一次运行您的程序。也适合一次测试多个输入。虽然您可能只想为此编写常规 shell 脚本。 67 | 68 | ## 你的邻居太高效了吗? C 预处理器救援! 69 | 70 | 使用 C 预处理器重新定义常用关键字,例如 71 | 72 | ```c 73 | #define if while 74 | ``` 75 | 76 | Protip:将此行放入标准包含的内容之一,例如/usr/include/stdio.h 77 | 78 | ## 当 C 具有预处理器时,谁需要功能 79 | 80 | 好的,所以这更像是一个问题。使用看起来像函数的宏时要小心...... 81 | 82 | ```c 83 | #define min(a,b) a 原文: 4 | 5 | ## 关于调度的思考。 6 | 7 | [CPU 调度](https://en.wikipedia.org/wiki/Scheduling_(computing))是有效选择在系统 CPU 内核上运行哪个进程的问题。在繁忙的系统中,将有比 CPU 内核更多的可立即运行的进程,因此系统内核必须评估应该在 CPU 上运行哪些进程以及哪些进程应放在就绪队列中以便稍后执行。 8 | 9 | 多线程和多 CPU 内核的额外复杂性被认为是对这个初始展示的干扰,因此在此忽略。 10 | 11 | 非母语人士的另一个问题是“时间”的双重含义:“时间”一词可用于时钟和经过时间的上下文。例如“第一个过程的到达时间是上午 9 点。”并且,“算法的运行时间是 3 秒”。 12 | 13 | ## 如何测量调度以及哪种调度程序最佳? 14 | 15 | 调度会影响系统的性能,特别是系统的 _ 延迟 _ 和 _ 吞吐量 _。吞吐量可以通过系统值来度量,例如 I / O 吞吐量 - 每秒写入的字节数,或者每单位时间可以完成的小进程数,或者使用更高级别的抽象,例如客户数量每分钟处理的记录。延迟可以通过响应时间(进程可以开始发送响应之前的经过时间)或等待时间或周转时间(完成任务所经过的时间)来度量。不同的调度程序提供不同的优化权衡,可能适合或不适合所需的使用 - 没有针对所有可能的环境和目标的最佳调度程序。例如,“shortest-job-first”将最小化所有作业的总等待时间,但在交互式(UI)环境中,最好将响应时间最小化(以某些吞吐量为代价),而 FCFS 看起来直观公平且易于实现但是遭遇了康宏效应。 16 | 17 | ## 什么是到达时间? 18 | 19 | 进程首次到达就绪队列并准备开始执行的时间。如果 CPU 空闲,则到达时间也将是执行的开始时间。 20 | 21 | ## 什么是先发制人? 22 | 23 | 在没有抢占的情况下,进程将一直运行,直到它们无法再进一步利用 CPU。例如,以下条件将从 CPU 中删除进程,并且可以为其他进程调度 CPU:进程由于信号而终止,被阻塞等待并发原语或正常退出。因此,一旦调度了一个进程,即使具有高优先级的另一个进程(例如,较短的作业)出现在就绪队列上,它也将继续。 24 | 25 | 通过抢占,如果将更优选的进程添加到就绪队列,则可以立即移除现有进程。例如,假设在 t = 0 时使用 Shortest Job First 调度程序,有两个进程(P1 P2),执行时间为 10 和 20 ms。 P1 已预定。 P1 立即创建一个新进程 P3,执行时间为 5 毫秒,并将其添加到就绪队列中。没有先发制人,P3 将在 10ms 后运行(P1 完成后)。通过抢占,P1 将立即从 CPU 中逐出,而是放回到就绪队列中,而 P3 将由 CPU 执行。 26 | 27 | ## 哪些调度员遭受饥饿? 28 | 29 | 任何使用优先级形式的调度程序都可能导致饥饿,因为可能永远不会调度早期进程(分配 CPU)。例如,对于 SJF,如果系统继续有许多要安排的短作业,则可能永远不会安排更长的作业。这一切都取决于[类型的调度程序](https://en.wikipedia.org/wiki/Scheduling_(computing)#Types_of_operating_system_schedulers)。 30 | 31 | ## 为什么可以将一个进程(或线程)放在就绪队列上? 32 | 33 | 当一个进程能够使用 CPU 时,该进程被置于就绪队列中。一些例子包括: 34 | 35 | * 阻止进程等待存储或套接字中的`read`完成,现在可以使用数据。 36 | * 已创建新进程并准备启动。 37 | * 进程线程在同步原语(条件变量,信号量,互斥锁)上被阻止,但现在能够继续。 38 | * 阻止进程等待系统调用完成,但已传递信号并且信号处理程序需要运行。 39 | 40 | 在考虑线程时可以生成类似的示例。 41 | 42 | ## 效率措施 43 | 44 | `start_time`是进程的挂钟开始时间(CPU 开始工作)`end_time`是进程的结束挂钟(CPU 完成进程)`run_time`是所需的 CPU 时间总量`arrival_time`是进程进入调度程序的时间(CPU 可能无法启动它) 45 | 46 | ## 什么是'周转时间'? 47 | 48 | 从进程到达到结束的总时间。 49 | 50 | `turnaround_time = end_time - arrival_time` 51 | 52 | ## 什么是'响应时间'? 53 | 54 | 从进程到达到 CPU 实际开始工作所花费的总延迟(时间)。 55 | 56 | `response_time = start_time - arrival_time` 57 | 58 | ## 什么是“等待时间”? 59 | 60 | 等待时间是 _ 总 _ 等待时间,即进程在就绪队列上的总时间。一个常见的错误是认为它只是就绪队列中的初始等待时间。 61 | 62 | 如果没有 I / O 的 CPU 密集型进程需要 7 分钟的 CPU 时间才能完成,但需要 9 分钟的挂钟时间才能完成,我们可以得出结论,它已被放置在就绪队列中 2 分钟。对于那些 2 分钟,该过程已准备好运行但没有分配 CPU。工作等待时无关紧要,等待时间为 2 分钟。 63 | 64 | `wait_time = (end_time - arrival_time) - run_time` 65 | 66 | ## 什么是车队效应? 67 | 68 | “Convoy 效应是持续备份 I / O 密集型进程的地方,等待占用 CPU 的 CPU 密集型进程。这导致 I / O 性能不佳,即使对于 CPU 需求很小的进程也是如此。” 69 | 70 | 假设 CPU 当前已分配给 CPU 密集型任务,并且存在一组处于就绪队列中的 I / O 密集型进程。这些进程只需要很少的 CPU 时间,但它们无法继续,因为它们正在等待从处理器中删除 CPU 密集型任务。在 CPU 绑定进程释放 CPU 之前,这些进程一直处于饥饿状态。但很少会释放 CPU(例如,在 FCFS 调度程序的情况下,我们必须等到进程因 I / O 请求而被阻止)。 I / O 密集型进程现在可以最终满足他们的 CPU 需求,他们可以快速完成这些需求,因为他们的 CPU 需求很小,并且 CPU 再次被分配回 CPU 密集型进程。因此,整个系统的 I / O 性能受到所有进程的 CPU 需求饥饿的间接影响。 71 | 72 | 这种效果通常在 FCFS 调度程序的上下文中讨论,但循环调度程序也可以展示长时间量子的康宏效应。 73 | 74 | ## Linux 调度 75 | 76 | 截至 2016 年 2 月,Linux 默认使用 _ 完全公平调度程序 _ 进行 CPU 调度,使用预算公平调度“BFQ”进行 I / O 调度。适当的调度会对吞吐量和延迟产生重大影响。延迟对于交互式和软实时应用(如音频和视频流)尤为重要。有关详细信息,请参阅此处的讨论和比较基准[ [https://lkml.org/lkml/2014/5/27/314](https://lkml.org/lkml/2014/5/27/314) ]。 77 | 78 | 以下是 CFS 的日程安排 79 | 80 | * CPU 创建一个红黑树,其中包含进程虚拟运行时(runtime / nice_value)和睡眠公平性(如果进程正在等待某些内容,则在等待时将其提供给 CPU)。 81 | * (好的值是内核优先处理某些进程的方式,优先级越低) 82 | * 内核根据此度量标准选择最低的一个,并安排该进程下一次运行,将其从队列中取出。由于红黑树是自平衡的,因此保证$ O(log(n))$(选择 min 进程是相同的运行时) 83 | 84 | 虽然它被称为公平调度程序,但存在一些问题。 85 | 86 | * 调度的进程组可能具有不平衡负载,因此调度程序粗略地分配负载。当另一个 CPU 获得空闲时,它只能查看组计划的平均负载而不是单个核心。因此,只要平均值很好,空闲 CPU 就不会从正在燃烧的 CPU 中获取工作。 87 | * 如果一组进程正在运行,则在非相邻核心上存在错误。如果两个核心超过一跳,负载平衡算法甚至不会考虑该核心。这意味着如果 CPU 是空闲的并且正在做更多工作的 CPU 超过一跳,它将不会进行工作(可能已经修补)。 88 | * 线程在一个核心子集上进入休眠状态后,当它被唤醒时,它只能在它正在睡眠的核心上进行调度。如果这些核心现在是公共汽车 -------------------------------------------------------------------------------- /docs/51.md: -------------------------------------------------------------------------------- 1 | # 调度,第 2 部分:调度过程:算法 2 | 3 | > 原文: 4 | 5 | ## 什么是众所周知的调度算法? 6 | 7 | 对于所有的例子, 8 | 9 | 过程 1:运行时 1000ms 10 | 11 | 过程 2:运行时 2000ms 12 | 13 | 过程 3:运行时 3000ms 14 | 15 | 过程 4:运行时 4000ms 16 | 17 | 过程 5:运行时 5000ms 18 | 19 | ## 最短的工作优先(SJF) 20 | 21 | ![](img/aacd324af23dbabdf0c8511652cfadb2.jpg) 22 | 23 | * P1 到达:0ms 24 | * P2 到达:0ms 25 | * P3 到达:0ms 26 | * P4 到达:0ms 27 | * P5 到货:0ms 28 | 29 | 这些进程都在开始时到达,并且调度程序以最短的总 CPU 时间调度作业。明显的问题是这个调度程序需要知道该程序在运行程序之前将持续运行多长时间。 30 | 31 | 技术说明:实际的 SJF 实现不会使用进程的总执行时间,而是使用突发时间(包括将来计算执行之前的总 CPU 时间将不再准备好运行)。可以通过使用基于先前突发时间的指数衰减加权滚动平均来估计预期突发时间,但是对于该展示,我们将简化该讨论以使用该过程的总运行时间作为突发时间的代理。 32 | 33 | **优势** 34 | 35 | * 较短的工作往往先运行 36 | 37 | **缺点** 38 | 39 | * 需要算法是无所不知的 40 | 41 | ## 抢先最短的工作优先(PSJF) 42 | 43 | 先抢先最短的作业首先是最短的作业,但是如果新作业的运行时间短于过程的剩余运行时间,则运行该作业。 (如果它与我们的算法相同,我们的算法可以选择)。调度程序使用进程的总运行时间,如果你想剩下最短 _ 剩余 _ 时间,那就是 PSJF 的变体,称为 Shortest Remaining Time First。 44 | 45 | ![](img/a939273ec986aaa3938cdbe2867a08e2.jpg) 46 | 47 | * P2 在 0ms 48 | * P1 在 1000ms 49 | * P5 在 3000ms 50 | * P4 在 4000ms 51 | * P3 在 5000ms 52 | 53 | 这是我们的算法所做的。它运行 P2 因为它是唯一运行的东西。然后 P1 进入 1000ms,P2 运行 2000ms,所以我们的调度程序先发制人地停止 P2,让 P1 一直运行(这完全取决于算法因为时间相等)。然后,P5 进入 - 由于没有进程正在运行,调度程序将运行进程 5.P4 进入,并且由于运行时间等于 P5,调度程序停止 P5 并运行 P4。最后 P3 进入,抢占 P4,并运行完成。然后 P4 运行,然后 P5 运行。 54 | 55 | **Advantages** 56 | 57 | * 确保较短的工作首先运行 58 | 59 | **Disadvantages** 60 | 61 | * 需要再次了解运行时 62 | 63 | **注意:**此算法因历史原因比较总运行时间 _ 而非 _ 剩余运行时间。如果您想考虑剩余时间,您将使用抢先最短剩余时间优先(PSRTF)。 64 | 65 | ## 先到先得(FCFS) 66 | 67 | ![](img/3332236e74b2fb5bdc6e33bec4ce909e.jpg) 68 | 69 | * P2 在 0ms 70 | * P1 在 1000ms 71 | * P5 在 3000ms 72 | * P4 在 4000ms 73 | * P3 在 5000ms 74 | 75 | 进程按到达顺序安排。 FCFS 的一个优点是调度算法很简单:就绪队列只是一个 FIFO(先进先出)队列。 FCFS 遭遇康宏效应。 76 | 77 | 在这里 P2 到达,然后 P1 到达,然后是 P5,然后是 P4,然后是 P3。你可以看到 P5 的护航效果。 78 | 79 | **Advantages** 80 | 81 | * 简单的实施 82 | 83 | **Disadvantages** 84 | 85 | * 长时间运行的进程可以阻止所有其他进程 86 | 87 | ## Round Robin(RR) 88 | 89 | 进程按其到达就绪队列的顺序进行安排。但是,经过一小段时间后,将强制从运行状态中删除正在运行的进程并将其放回就绪队列。这可确保长时间运行的进程不会使所有其他进程无法运行。进程在返回就绪队列之前可以执行的最长时间称为时间量。在大时间量子点(时间量子长于所有过程的运行时间)的限制下,循环将等同于 FCFS。 90 | 91 | ![](img/332fa7df9feb67953e1554176b74fd84.jpg) 92 | 93 | * P1 到达:0ms 94 | * P2 到达:0ms 95 | * P3 到达:0ms 96 | * P4 到达:0ms 97 | * P5 到货:0ms 98 | 99 | 量子= 1000ms 100 | 101 | 这里所有进程都在同一时间到达。 P1 运行 1 个量程并完成。 P2 为一个量子;然后,P3 停止了。在为量子运行所有其他进程之后,我们循环回到 P2,直到完成所有进程。 102 | 103 | **Advantages** 104 | 105 | * 确保一些公平的概念 106 | 107 | **Disadvantages** 108 | 109 | * 大量进程=大量切换 110 | 111 | ## 优先 112 | 113 | 进程按优先级顺序排列。例如,导航过程执行可能比记录过程更重要。 -------------------------------------------------------------------------------- /docs/52.md: -------------------------------------------------------------------------------- 1 | # IPC 复习题 2 | 3 | > 原文: 4 | 5 | ## 话题 6 | 7 | 虚拟内存页表 MMU / TLB 地址转换页面错误帧/页单级与多级页表计算多级页表的偏移量管道读取写入结束写入零读取器管道从零写入器管道读取命名管道和未命名管道缓冲区大小/原子性调度算法效率测量 8 | 9 | ## 问题 10 | 11 | * 什么是虚拟内存? 12 | * 以下是什么,他们的目的是什么? 13 | * 翻译旁视缓冲区 14 | * 实际地址 15 | * 内存管理单元。多级页表。帧号。页码和页面偏移量。 16 | * 肮脏的一点 17 | * NX 位 18 | * 什么是页面表?物理框架怎么样?页面是否总是需要指向物理框架? 19 | * 什么是页面错误?有哪些类型?什么时候导致段错误? 20 | * 单级页表有哪些优点?缺点是什么?多层平台怎么样? 21 | * 多层平台在内存中看起来像什么? 22 | * 如何确定页面偏移中使用了多少位? 23 | * 给定 64 位地址空间,4kb 页面和帧以及 3 级页表,虚拟页号 1,VPN2,VPN3 和偏移量的位数是多少? 24 | * 什么是管道?如何创建管道? 25 | * SIGPIPE 何时交付给进程? 26 | * 在什么条件下会在管道块上调用 read()?在什么条件下 read()会立即返回 0 27 | * 命名管道和未命名管道有什么区别? 28 | * 管道螺纹安全吗? 29 | * 编写一个使用 fseek 和 ftell 的函数,用'X'替换文件的中间字符 30 | * 编写一个创建管道的函数,并使用 write 向管道发送 5 个字节“HELLO”。返回管道的读取文件描述符。 31 | * mmap 文件会发生什么? 32 | * 为什么不建议使用 ftell 获取文件大小?你应该怎么做呢? 33 | * 什么是日程安排? 34 | * 什么是周转时间?响应时间?等待时间? 35 | * 护航效果是什么? 36 | * 哪种算法平均具有最佳的周转/响应/等待时间 -------------------------------------------------------------------------------- /docs/53.md: -------------------------------------------------------------------------------- 1 | # 8.网络 -------------------------------------------------------------------------------- /docs/54.md: -------------------------------------------------------------------------------- 1 | # POSIX,第 1 部分:错误处理 2 | 3 | > 原文: 4 | 5 | ## 什么是 POSIX 错误处理? 6 | 7 | 在其他语言中,您可能会看到使用异常实现的错误处理。虽然你在技术上可以在 c 中使用它们(你保留一堆非常 try / catch 块并使用`setjmp`和`longjmp`分别转到这些块),但是 C 中的错误处理通常是通过 posix 错误处理代码来完成的看起来像这样。 8 | 9 | ```c 10 | int ret = some_system_call() 11 | if(ret == ERROR_CODE){ 12 | switch(errno){ 13 | // Do different stuff based on the errno number. 14 | } 15 | } 16 | 17 | ``` 18 | 19 | 在内核中,`goto`的使用被大量用于清理应用程序的不同部分。 **你不应该使用 gotos** 因为它们使代码更难阅读。内核中的 getos 是不必要的,所以不要上课。 20 | 21 | ## 什么是`errno`以及何时设定? 22 | 23 | POSIX 定义了一个特殊的整数`errno`,它在系统调用失败时设置。 `errno`的初始值为零(即没有错误)。当系统调用失败时,它通常会返回-1 表示错误并设置`errno` 24 | 25 | ## 多线程怎么样? 26 | 27 | 每个线程都有自己的`errno`副本。这非常有用;否则一个线程中的错误会干扰另一个线程的错误状态。 28 | 29 | ## 什么时候`errno`重置为零? 30 | 31 | 除非你专门将它重置为零,否则它不会!当系统调用成功时,他们执行 _ 而不是 _ 重置`errno`的值。 32 | 33 | 这意味着如果您知道系统调用失败(例如,它返回-1),您应该只依赖于 errno 的值。 34 | 35 | ## 使用`errno`有什么问题和最佳做法? 36 | 37 | 当复杂的错误处理使用库调用或系统调用可能会改变`errno`的值时要小心。实际上,将 errno 的值复制到 int 变量更安全: 38 | 39 | ```c 40 | // Unsafe - the first fprintf may change the value of errno before we use it! 41 | if (-1 == sem_wait(&s)) { 42 | fprintf(stderr, "An error occurred!"); 43 | fprintf(stderr, "The error value is %d\n", errno); 44 | } 45 | // Better, copy the value before making more system and library calls 46 | if (-1 == sem_wait(&s)) { 47 | int errno_saved = errno; 48 | fprintf(stderr, "An error occurred!"); 49 | fprintf(stderr, "The error value is %d\n", errno_saved); 50 | } 51 | ``` 52 | 53 | 同样,如果您的信号处理程序进行任何系统或库调用,那么最好保存 errno 的原始值并在返回之前恢复该值: 54 | 55 | ```c 56 | void handler(int signal) { 57 | int errno_saved = errno; 58 | 59 | // make system calls that might change errno 60 | 61 | errno = errno_saved; 62 | } 63 | ``` 64 | 65 | ## 如何打印出与特定错误号相关联的字符串消息? 66 | 67 | 使用`strerror`获取错误值的简短(英文)描述 68 | 69 | ```c 70 | char *mesg = strerror(errno); 71 | fprintf(stderr, "An error occurred (errno=%d): %s", errno, mesg); 72 | ``` 73 | 74 | ## perror 和 strerror 有什么关系? 75 | 76 | 在之前的页面中,我们使用 perror 将错误打印到标准错误。使用`strerror`,我们现在可以编写`perror`的简单实现: 77 | 78 | ```c 79 | void perror(char *what) { 80 | fprintf(stderr, "%s: %s\n", what, strerror(errno)); 81 | } 82 | ``` 83 | 84 | ## 使用 strerror 有什么问题? 85 | 86 | 不幸的是`strerror`不是线程安全的。换句话说,两个线程不能同时调用它! 87 | 88 | 有两种解决方法:首先,我们可以使用互斥锁定义临界区和本地缓冲区。所有调用`strerror`的地方的所有线程都应该使用相同的互斥锁 89 | 90 | ```c 91 | pthread_mutex_lock(&m); 92 | char *result = strerror(errno); 93 | char *message = malloc(strlen(result) + 1); 94 | strcpy(message, result); 95 | pthread_mutex_unlock(&m); 96 | fprintf(stderr, "An error occurred (errno=%d): %s", errno, message); 97 | free(message); 98 | ``` 99 | 100 | 或者使用较不便携但线程安全的`strerror_r` 101 | 102 | ## 什么是 EINTR? sem_wait 是什么意思?读?写? 103 | 104 | 当信号(例如 SIGCHLD,SIGPIPE,...)传递给过程时,某些系统调用可能会中断。此时系统调用可能会返回而不执行任何操作!例如,字节可能未被读/写,信号量等待可能没有等待。 105 | 106 | 可以通过检查返回值以及`errno`是否为 EINTR 来检测此中断。在这种情况下,应重试系统调用。通常会看到包含系统调用的以下类型的循环(例如 sem_wait)。 107 | 108 | ```c 109 | while ((-1 == systemcall(...)) && (errno == EINTR)) { /* repeat! */} 110 | ``` 111 | 112 | 小心写`== EINTR`,而不是`= EINTR`。 113 | 114 | 或者,如果结果值需要稍后使用... 115 | 116 | ```c 117 | while ((-1 == (result = systemcall(...))) && (errno == EINTR)) { /* repeat! */} 118 | ``` 119 | 120 | 在 Linux 上,将`read`和`write`调用到本地磁盘通常不会返回 EINTR(而是自动为您重新启动该功能)。但是,在对应于网络流 _ 的文件描述符上调用`read`和`write`可以 _ 返回 EINTR。 121 | 122 | ## 哪些系统调用可能被中断并需要包装? 123 | 124 | 使用 man 页面!手册页包括可由系统调用设置的错误列表(即错误值)。经验法则是“慢”(阻塞)调用(例如,写入套接字)可能会被中断,但快速非阻塞调用(例如 pthread_mutex_lock)则不会。 125 | 126 | 来自 linux 信号 7 手册页。 127 | 128 | “如果在阻止系统调用或库函数调用时调用信号处理程序,则: 129 | 130 | * 信号处理程序返回后,调用自动重启;要么 131 | * 调用失败并显示错误 EINTR。发生这两种行为中的哪一种取决于接口以及是否使用 SA_RESTART 标志建立了信号处理程序(请参阅 sigaction(2))。 UNIX 系统的细节各不相同;下面是 Linux 的详细信息。 132 | 133 | 如果对信号处理程序中断对以下某个接口的阻塞调用,则在使用 SA_RESTART 标志后,如果信号处理程序返回,则将自动重新启动该调用。否则呼叫将失败并显示错误 EINTR: 134 | 135 | * read(2),readv(2),write(2),writev(2)和 ioctl(2)调用“慢”设备。 “慢”设备是 I / O 调用可能无限期阻塞的设备,例如终端,管道或套接字。 (根据此定义,磁盘不是慢速设备。)如果慢速设备上的 I / O 调用在信号处理程序中断时已经传输了某些数据,则该调用将返回成功状态(通常,传输的字节数)。 “ 136 | 137 | 注意,很容易相信设置'SA_RESTART'标志足以使整个问题消失。不幸的是,这不是真的:仍有系统调用可能提前返回并设置`EINTR`!有关详细信息,请参见[信号(7)](https://cs-education.github.io/sysassets/man_pages/html/man7/signal.7.html)。 138 | 139 | ## Errno 例外吗? 140 | 141 | 有些 POSIX 实用程序每个人都有自己的错误。一种是当你调用`getaddrinfo`时检查错误并转换为字符串的函数是 [gai_strerr](https://linux.die.net/man/3/gai_strerror) 。不要混淆他们! -------------------------------------------------------------------------------- /docs/55.md: -------------------------------------------------------------------------------- 1 | # 网络,第 1 部分:简介 2 | 3 | > 原文: 4 | 5 | 警告:很明显,页面是 _ 而不是 _ 完整的 IP,UDP 或 TCP 描述!相反,它只是一个简短的介绍,足以让我们在以后的讲座中建立这些概念。 6 | 7 | ## 什么是“IP4”“IP6”? 8 | 9 | 以下是互联网协议(IP)的“30 秒”介绍 - 这是从一台机器向另一台机器发送信息包(“数据报”)的主要方式。 10 | 11 | “IP4”,或者更准确地说,“IPv4”是因特网协议的第 4 版,其描述了如何通过网络从一台机器向另一台机器发送信息包。目前,互联网上大约 95%的数据包都是 IPv4 数据包。 IPv4 的一个重要限制是源和目标地址限制为 32 位(IPv4 是在连接到同一网络的 40 亿设备的想法是不可想象的时候设计的 - 或者至少不值得使数据包大小更大) 12 | 13 | 每个 IPv4 数据包包括一个非常小的标头 - 通常为 20 个字节(更确切地说,“八位字节”),包括源和目标地址。 14 | 15 | 从概念上讲,源地址和目标地址可以分为两部分:网络号(高位)和低位表示该网络上的特定主机号。 16 | 17 | 较新的分组协议“IPv6”解决了 IPv4 的许多限制(例如,使路由表更简单和 128 位地址),但是不到 5%的网络流量是基于 IPv6 的。 18 | 19 | 机器可以具有 IPv6 地址和 IPv4 地址。 20 | 21 | ## “没有像 127.0.0.1 这样的地方”! 22 | 23 | 特殊的 IPv4 地址是`127.0.0.1`,也称为 localhost。发送到 127.0.0.1 的数据包永远不会离开机器;地址被指定为同一台机器。 24 | 25 | 注意,32 位地址被分成 4 个八位字节,即点表示法中的每个数字可以是 0-255(含)。但是,IPv4 地址也可以写为整数。 26 | 27 | ## ......和......“没有像 0:0:0:0:0:0:0:1 那样的地方?” 28 | 29 | IPv6 中的 128 位本地主机地址是`0:0:0:0:0:0:0:1`,可以缩写形式写入`::1` 30 | 31 | ## 什么是港口? 32 | 33 | 要使用 IPv4(或 IPv6)将数据报(数据包)发送到 Internet 上的主机,您需要指定主机地址和端口。端口是无符号的 16 位数(即最大端口号是 65535)。 34 | 35 | 进程可以侦听特定端口上的传入数据包。但是,只有具有超级用户(root)访问权限的进程才能侦听端口&lt; 1024.任何进程都可以侦听 1024 或更高的端口。 36 | 37 | 常用端口是端口 80:端口 80 用于未加密的 http 请求(即网页)。例如,如果 Web 浏览器连接到 [http://www.bbc.com/](http://www.bbc.com/) ,则它将连接到端口 80。 38 | 39 | ## 什么是 UDP?什么时候使用? 40 | 41 | UDP 是一种建立在 IPv4 和 IPv6 之上的无连接协议。它使用起来非常简单:确定目标地址和端口并发送数据包!但是,网络不保证数据包是否会到达。如果网络拥塞,可能会丢弃数据包(也称为数据报)。数据包可能会重复或无序到达。 42 | 43 | 在两个远程数据中心之间,通常会看到 3%的数据包丢失。 44 | 45 | UDP 的典型用例是接收最新数据比接收所有数据更重要。例如,游戏可以发送玩家位置的连续更新。流视频信号可以使用 UDP 发送图片更新 46 | 47 | ## 什么是 TCP?什么时候使用? 48 | 49 | TCP 是基于连接的协议,它建立在 IPv4 和 IPv6 之上(因此可以描述为“TCP / IP”或“TCP over IP”)。 TCP 在两台机器之间创建 _ 管道 _ 并抽象出互联网的低级别数据包性质:因此,在大多数情况下,从一台机器发送的字节最终将到达另一端而不会出现重复或数据丢失。 50 | 51 | TCP 将自动管理重发数据包,忽略重复数据包,重新排列无序数据包以及更改数据包发送速率。 52 | 53 | TCP 的三次握手称为 SYN,SYN-ACK 和 ACK。此页面上的图表有助于理解 TCP 握手。 [TCP 握手](http://www.inetdaemon.com/tutorials/internet/tcp/3-way_handshake.shtml) 54 | 55 | 当今因特网上的大多数服务(例如,web 服务)使用 TCP,因为它隐藏了因特网的较低的分组级特性的复杂性。 -------------------------------------------------------------------------------- /docs/56.md: -------------------------------------------------------------------------------- 1 | # 网络,第 2 部分:使用 getaddrinfo 2 | 3 | > 原文: 4 | 5 | ## 如何使用`getaddrinfo`将主机名转换为 IP 地址? 6 | 7 | 函数`getaddrinfo`可以将人类可读域名(例如`www.illinois.edu`)转换为 IPv4 和 IPv6 地址。实际上它将返回 addrinfo 结构的链表: 8 | 9 | ```c 10 | struct addrinfo { 11 | int ai_flags; 12 | int ai_family; 13 | int ai_socktype; 14 | int ai_protocol; 15 | socklen_t ai_addrlen; 16 | struct sockaddr *ai_addr; 17 | char *ai_canonname; 18 | struct addrinfo *ai_next; 19 | }; 20 | ``` 21 | 22 | 它非常易于使用。例如,假设您想在 [www.bbc.com](http://www.bbc.com) 中找到网络服务器的数字 IPv4 地址。我们分两个阶段完成。首先使用 getaddrinfo 构建可能的连接的链表。其次使用`getnameinfo`将二进制地址转换为可读形式。 23 | 24 | ```c 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | struct addrinfo hints, *infoptr; // So no need to use memset global variables 32 | 33 | int main() { 34 | hints.ai_family = AF_INET; // AF_INET means IPv4 only addresses 35 | 36 | int result = getaddrinfo("www.bbc.com", NULL, &hints, &infoptr); 37 | if (result) { 38 | fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(result)); 39 | exit(1); 40 | } 41 | 42 | struct addrinfo *p; 43 | char host[256],service[256]; 44 | 45 | for(p = infoptr; p != NULL; p = p->ai_next) { 46 | 47 | getnameinfo(p->ai_addr, p->ai_addrlen, host, sizeof(host), service, sizeof(service), NI_NUMERICHOST); 48 | puts(host); 49 | } 50 | 51 | freeaddrinfo(infoptr); 52 | return 0; 53 | } 54 | ``` 55 | 56 | 典型输出: 57 | 58 | ``` 59 | 212.58.244.70 60 | 212.58.244.71 61 | ``` 62 | 63 | ## [www.cs.illinois.edu](http://www.cs.illinois.edu) 如何转换为 IP 地址? 64 | 65 | 魔法!不用说,使用称为“DNS”(域名服务)的系统。如果计算机没有在本地保存答案,则它会将 UDP 数据包发送到本地 DNS 服务器。该服务器又可以查询其他上游 DNS 服务器。 66 | 67 | ## DNS 安全吗? 68 | 69 | DNS 本身很快但不安全。 DNS 请求未加密,容易受到“中间人”攻击。例如,咖啡店互联网连接可以轻易地破坏您的 DNS 请求并发回特定域的不同 IP 地址 70 | 71 | ## 如何连接到 TCP 服务器(例如 Web 服务器?) 72 | 73 | TODO 连接到远程计算机需要三个基本系统调用: 74 | 75 | ``` 76 | getaddrinfo -- Determine the remote addresses of a remote host 77 | socket -- Create a socket 78 | connect -- Connect to the remote host using the socket and address information 79 | ``` 80 | 81 | `getaddrinfo`调用成功,创建`addrinfo`结构的链接列表,并将给定指针设置为指向第一个。 82 | 83 | 套接字调用创建一个传出套接字并返回一个描述符(有时称为“文件描述符”),可以与`read`和`write`等一起使用。在这种意义上,它是`open`的网络模拟打开文件流 - 除了我们尚未将套接字连接到任何东西! 84 | 85 | 最后,connect 调用尝试连接到远程计算机。我们传递原始套接字描述符以及存储在 addrinfo 结构中的套接字地址信息。存在不同种类的套接字地址结构(例如,IPv4 与 IPv6),其可能需要更多存储器。因此,除了传递指针外,还传递了结构的大小: 86 | 87 | ```c 88 | // Pull out the socket address info from the addrinfo struct: 89 | connect(sockfd, p->ai_addr, p->ai_addrlen) 90 | ``` 91 | 92 | ## 如何释放为 addrinfo 结构的链表分配的内存? 93 | 94 | 作为最顶层`addrinfo`结构上的清理代码调用`freeaddrinfo`的一部分: 95 | 96 | ```c 97 | void freeaddrinfo(struct addrinfo *ai); 98 | ``` 99 | 100 | ## 如果 getaddrinfo 失败,我可以用`strerror`打印出错误吗? 101 | 102 | 没有。`getaddrinfo`的错误处理有点不同: 103 | 104 | * 返回值 _ 是 _ 的错误代码(即不使用`errno`) 105 | * 使用`gai_strerror`获取等效的短英文错误文本: 106 | 107 | ```c 108 | int result = getaddrinfo(...); 109 | if(result) { 110 | const char *mesg = gai_strerror(result); 111 | ... 112 | } 113 | ``` 114 | 115 | ## 我可以只请求 IPv4 或 IPv6 连接吗?仅 TCP? 116 | 117 | 是!使用传递给`getaddrinfo`的 addrinfo 结构来定义您想要的连接类型。 118 | 119 | 例如,要通过 IPv6 指定基于流的协议: 120 | 121 | ```c 122 | struct addrinfo hints; 123 | memset(hints, 0, sizeof(hints)); 124 | 125 | hints.ai_family = AF_INET6; // Only want IPv6 (use AF_INET for IPv4) 126 | hints.ai_socktype = SOCK_STREAM; // Only want stream-based connection 127 | ``` 128 | 129 | ## 那些使用`gethostbyname`的代码示例呢? 130 | 131 | 旧函数`gethostbyname`已弃用;这是将主机名转换为 IP 地址的旧方法。端口地址仍需要使用 htons 功能手动设置。使用较新的`getaddrinfo`编写支持 IPv4 和 IPv6 的代码要容易得多 132 | 133 | ## 这很容易!? 134 | 135 | 是的,不是。创建一个简单的 TCP 客户端很容易 - 但网络通信提供了许多不同的抽象级别,并且可以在每个抽象级别设置几个属性和选项(例如我们没有谈到可以操作选项的`setsockopt`插座)。有关详细信息,请参阅[指南](http://www.beej.us/guide/bgnet/output/html/multipage/getaddrinfoman.html)。 -------------------------------------------------------------------------------- /docs/57.md: -------------------------------------------------------------------------------- 1 | # 网络,第 3 部分:构建一个简单的 TCP 客户端 2 | 3 | > 原文: 4 | 5 | ## `socket` 6 | 7 | `int socket(int domain, int type, int protocol);` 8 | 9 | Socket 创建一个带域的套接字(通常是用于 IPv4 的 AF_INET),类型是使用 UDP 还是 TCP,协议是任何添加选项。这在内核中创建了一个套接字对象,可以与外部世界/网络进行通信。这将返回一个 fd,因此您可以像普通文件描述符一样使用它!请记住,您希望从 socketfd 执行读取或写入操作,因为它仅将套接字对象表示为客户端,否则您需要遵守服务器的约定。 10 | 11 | ## `getaddressinfo` 12 | 13 | 我们在最后一节看到了这个!你是这方面的专家。 14 | 15 | ## `connect` 16 | 17 | `int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);` 18 | 19 | 将它传递给 sockfd,然后传递你要去的地址和它的长度,你就会断开连接(只要你检查错误)。请记住,网络呼叫是非常容易失败的。 20 | 21 | ## `read` / `write` 22 | 23 | 一旦我们成功连接,我们可以像任何旧的文件描述符一样读取或写入。请记住,如果您连接到网站,您希望符合 HTTP 协议规范,以便获得任何有意义的结果。有一些库可以做到这一点,通常你不会在套接字级别连接,因为它周围有其他库或包 24 | 25 | ## 完整的简单 TCP 客户端示例 26 | 27 | ```c 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | int main(int argc, char **argv) 37 | { 38 | int s; 39 | int sock_fd = socket(AF_INET, SOCK_STREAM, 0); 40 | 41 | struct addrinfo hints, *result; 42 | memset(&hints, 0, sizeof(struct addrinfo)); 43 | hints.ai_family = AF_INET; /* IPv4 only */ 44 | hints.ai_socktype = SOCK_STREAM; /* TCP */ 45 | 46 | s = getaddrinfo("www.illinois.edu", "80", &hints, &result); 47 | if (s != 0) { 48 | fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); 49 | exit(1); 50 | } 51 | 52 | if(connect(sock_fd, result->ai_addr, result->ai_addrlen) == -1){ 53 | perror("connect"); 54 | exit(2); 55 | } 56 | 57 | char *buffer = "GET / HTTP/1.0\r\n\r\n"; 58 | printf("SENDING: %s", buffer); 59 | printf("===\n"); 60 | write(sock_fd, buffer, strlen(buffer)); 61 | 62 | char resp[1000]; 63 | int len = read(sock_fd, resp, 999); 64 | resp[len] = '\0'; 65 | printf("%s\n", resp); 66 | 67 | return 0; 68 | } 69 | ``` 70 | 71 | 示例输出: 72 | 73 | ``` 74 | SENDING: GET / HTTP/1.0 75 | 76 | === 77 | HTTP/1.1 200 OK 78 | Date: Mon, 27 Oct 2014 19:19:05 GMT 79 | Server: Apache/2.2.15 (Red Hat) mod_ssl/2.2.15 OpenSSL/1.0.1e-fips mod_jk/1.2.32 80 | Last-Modified: Fri, 03 Feb 2012 16:51:10 GMT 81 | ETag: "401b0-49-4b8121ea69b80" 82 | Accept-Ranges: bytes 83 | Content-Length: 73 84 | Connection: close 85 | Content-Type: text/html 86 | 87 | Provided by Web Services at Public Affairs at the University of Illinois 88 | ``` 89 | 90 | ## 评论 HTTP 请求和响应 91 | 92 | 上面的示例演示了使用超文本传输​​协议向服务器发出的请求。使用以下请求请求网页(或其他资源): 93 | 94 | ``` 95 | GET / HTTP/1.0 96 | ``` 97 | 98 | 有四个部分(方法,例如 GET,POST,......);资源(例如/ /index.html /image.png); procigocol“HTTP / 1.0”和两个新行(\ r \ n \ r \ n) 99 | 100 | 服务器的第一个响应行描述了使用的 HTTP 版本以及使用 3 位数响应代码的请求是否成功: 101 | 102 | ``` 103 | HTTP/1.1 200 OK 104 | ``` 105 | 106 | 如果客户端请求了非现有文件,例如`GET /nosuchfile.html HTTP/1.0`然后第一行包含响应代码是众所周知的`404`响应代码: 107 | 108 | ``` 109 | HTTP/1.1 404 Not Found 110 | ``` -------------------------------------------------------------------------------- /docs/58.md: -------------------------------------------------------------------------------- 1 | # 网络,第 4 部分:构建一个简单的 TCP 服务器 2 | 3 | > 原文: 4 | 5 | ## 什么是`htons`以及何时使用? 6 | 7 | 整数可以首先表示最低有效字节或最高有效字节。只要机器本身内部一致,任何一种方法都是合理的。对于网络通信,我们需要对商定的格式进行标准化。 8 | 9 | `htons(xyz)`以网络字节顺序返回 16 位无符号整数'short'值 xyz。 `htonl(xyz)`以网络字节顺序返回 32 位无符号整数'long'值 xyz。 10 | 11 | 这些功能被读作“主机到网络”;反函数(ntohs,ntohl)将网络有序字节值转换为主机有序排序。那么,是主机订购 little-endian 还是 big-endian?答案是 - 这取决于你的机器!它取决于运行代码的主机的实际体系结构。如果体系结构恰好与网络排序相同,那么这些函数的结果就是参数。对于 x86 机器,主机和网络订购 _ 与 _ 不同。 12 | 13 | 简介:每当您读取或写入低级 C 网络结构(例如端口和地址信息)时,请记住使用上述功能以确保正确转换为/从数据库格式转换。否则显示或指定的值可能不正确。 14 | 15 | ## 用于创建服务器的“4 大”网络呼叫是什么? 16 | 17 | 创建 TCP 服务器所需的四个系统调用是:`socket`,`bind` `listen`和`accept`。每个都有特定的目的,应按上述顺序调用 18 | 19 | 端口信息(由 bind 使用)可以手动设置(许多较旧的仅使用 IPv4 的 C 代码示例执行此操作),或使用`getaddrinfo`创建 20 | 21 | 我们以后也会看到 setsockopt 的例子。 22 | 23 | ## 调用`socket`的目的是什么? 24 | 25 | 为网络通信创建端点。一个新的插座本身并不是特别有用;虽然我们已经指定了数据包或基于流的连接,但它并未绑定到特定的网络接口或端口。相反,套接字返回一个网络描述符,可以用于以后调用 bind,listen 和 accept。 26 | 27 | ## 调用`bind`的目的是什么 28 | 29 | `bind`调用将抽象套接字与实际网络接口和端口相关联。可以在 TCP 客户端上调用 bind,但是通常不需要指定传出端口。 30 | 31 | ## 调用`listen`的目的是什么 32 | 33 | `listen`调用指定传入的未处理连接数的队列大小,即尚未通过`accept`分配网络描述符的队列大小。高性能服务器的典型值为 128 或更多。 34 | 35 | ## 为什么服务器套接字被动? 36 | 37 | 服务器套接字不会主动尝试连接到另一台主机;相反,他们等待传入的连接。此外,当对等设备断开连接时,服务器套接字不会关闭。相反,当远程客户端连接时,它会立即被撞到未使用的端口号以供将来通信。 38 | 39 | ## 调用`accept`的目的是什么 40 | 41 | 初始化服务器套接字后,服务器调用`accept`以等待新连接。与`socket` `bind`和`listen`不同,此调用将被阻止。即,如果没有新连接,则此呼叫将阻止,并且仅在新客户端连接时返回。 42 | 43 | 注意`accept`调用返回一个新的文件描述符。此文件描述符特定于特定客户端。将原始服务器套接字描述符用于服务器 I / O 然后想知道为什么网络代码失败是常见的编程错误。 44 | 45 | ## 创建 TCP 服务器的难点是什么? 46 | 47 | * 使用被动服务器套接字的套接字描述符(如上所述) 48 | * 未指定 getaddrinfo 的 SOCK_STREAM 要求 49 | * 无法重新使用现有端口。 50 | * 不初始化未使用的 struct 条目 51 | * 如果当前正在使用端口,则`bind`调用将失败 52 | 53 | 注意,端口是每台机器 - 不是每个进程或每个用户。换句话说,当另一个进程正在使用该端口时,您无法使用端口 1234。更糟糕的是,端口在进程完成后默认为“绑定”。 54 | 55 | ## 服务器代码示例 56 | 57 | 一个工作简单的服务器示例如下所示。请注意,此示例不完整 - 例如,它不会关闭套接字描述符,也不会释放由`getaddrinfo`创建的内存 58 | 59 | ```c 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | 69 | int main(int argc, char **argv) 70 | { 71 | int s; 72 | int sock_fd = socket(AF_INET, SOCK_STREAM, 0); 73 | 74 | struct addrinfo hints, *result; 75 | memset(&hints, 0, sizeof(struct addrinfo)); 76 | hints.ai_family = AF_INET; 77 | hints.ai_socktype = SOCK_STREAM; 78 | hints.ai_flags = AI_PASSIVE; 79 | 80 | s = getaddrinfo(NULL, "1234", &hints, &result); 81 | if (s != 0) { 82 | fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); 83 | exit(1); 84 | } 85 | 86 | if (bind(sock_fd, result->ai_addr, result->ai_addrlen) != 0) { 87 | perror("bind()"); 88 | exit(1); 89 | } 90 | 91 | if (listen(sock_fd, 10) != 0) { 92 | perror("listen()"); 93 | exit(1); 94 | } 95 | 96 | struct sockaddr_in *result_addr = (struct sockaddr_in *) result->ai_addr; 97 | printf("Listening on file descriptor %d, port %d\n", sock_fd, ntohs(result_addr->sin_port)); 98 | 99 | printf("Waiting for connection...\n"); 100 | int client_fd = accept(sock_fd, NULL, NULL); 101 | printf("Connection made: client_fd=%d\n", client_fd); 102 | 103 | char buffer[1000]; 104 | int len = read(client_fd, buffer, sizeof(buffer) - 1); 105 | buffer[len] = '\0'; 106 | 107 | printf("Read %d chars\n", len); 108 | printf("===\n"); 109 | printf("%s\n", buffer); 110 | 111 | return 0; 112 | } 113 | ``` 114 | 115 | ## 为什么我的服务器无法重新使用该端口? 116 | 117 | 默认情况下,套接字关闭时不会立即释放端口。相反,端口进入“TIMED-WAIT”状态。这可能会在开发过程中导致严重的混淆,因为超时可能会使有效的网络代码看起来失败。 118 | 119 | 为了能够立即重新使用端口,请在绑定到端口之前指定`SO_REUSEPORT`。 120 | 121 | ```c 122 | int optval = 1; 123 | setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)); 124 | 125 | bind(.... 126 | ``` 127 | 128 | 这是[对`SO_REUSEPORT`](http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t) 的扩展 stackoverflow 介绍性讨论。 -------------------------------------------------------------------------------- /docs/59.md: -------------------------------------------------------------------------------- 1 | # 网络,第 5 部分:关闭端口,重用端口和其他技巧 2 | 3 | > 原文: 4 | 5 | ## 关闭和关闭有什么区别? 6 | 7 | 当您不再需要从套接字读取更多数据,写入更多数据或完成这两项操作时,请使用`shutdown`调用。当您关闭套接字以进一步写入(或读取)时,该信息也会发送到连接的另一端。例如,如果您关闭套接字以便在服务器端进一步写入,那么片刻之后,阻塞的`read`调用可能会返回 0 以指示不再需要更多字节。 8 | 9 | 当您的进程不再需要套接字文件描述符时,请使用`close`。 10 | 11 | 如果在创建套接字文件描述符后`fork` -ed,则所有进程都需要关闭套接字才能重新使用套接字资源。如果关闭套接字以进一步读取,那么所有进程都会受到影响,因为您已经更改了套接字,而不仅仅是文件描述符。 12 | 13 | 编写好的代码会在调用`close`之前将`shutdown`作为套接字。 14 | 15 | ## 当我重新运行我的服务器代码时,它不起作用!为什么? 16 | 17 | 默认情况下,在套接字关闭后,端口进入超时状态,在此期间无法重新使用(“绑定到新套接字”)。 18 | 19 | 通过在绑定到端口之前设置套接字选项 REUSEPORT 可以禁用此行为: 20 | 21 | ```c 22 | int optval = 1; 23 | setsockopt(sock_fd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)); 24 | 25 | bind(sock_fd, ...); 26 | ``` 27 | 28 | ## TCP 客户端可以绑定到特定端口吗? 29 | 30 | 是!实际上,传出的 TCP 连接会自动绑定到客户端上未使用的端口。通常不必在客户端上明确设置端口,因为系统将智能地在合理的接口上找到不可用的端口(例如,如果当前通过 WiFi 连接连接,则为无线卡)。但是,如果您需要专门选择特定的以太网卡,或者防火墙仅允许来自特定范围的端口值的传出连接,则它可能很有用。 31 | 32 | 要显式绑定到以太网接口和端口,请在`connect`之前调用`bind` 33 | 34 | ## 谁连接到我的服务器? 35 | 36 | `accept`系统调用可以选择通过传入 sockaddr 结构来提供有关远程客户端的信息。不同的协议具有`struct sockaddr`的不同变体,它们具有不同的大小。最简单的结构是`sockaddr_storage`,它足够大以代表所有可能类型的 sockaddr。请注意,C 没有任何继承模型。因此,我们需要将结构显式地转换为'base type'结构 sockaddr。 37 | 38 | ```c 39 | struct sockaddr_storage clientaddr; 40 | socklen_t clientaddrsize = sizeof(clientaddr); 41 | int client_id = accept(passive_socket, 42 | (struct sockaddr *) &clientaddr, 43 | &clientaddrsize); 44 | ``` 45 | 46 | 我们已经看到`getaddrinfo`可以构建 addrinfo 条目的链接列表(并且每个条目中的每一个都可以包括套接字配置数据)。如果我们想将套接字数据转换为 IP 和端口地址怎么办?输入`getnameinfo`,可用于将本地或远程套接字信息转换为域名或数字 IP。类似地,端口号可以表示为服务名称(例如,端口 80 的“http”)。在下面的示例中,我们请求客​​户端 IP 地址和客户端端口号的数字版本。 47 | 48 | ```c 49 | socklen_t clientaddrsize = sizeof(clientaddr); 50 | int client_id = accept(sock_id, (struct sockaddr *) &clientaddr, &clientaddrsize); 51 | char host[256], port[256]; 52 | getnameinfo((struct sockaddr *) &clientaddr, 53 | clientaddrsize, host, sizeof(host), port, sizeof(port), 54 | NI_NUMERICHOST | NI_NUMERICSERV); 55 | ``` 56 | 57 | Todo:讨论 NI_MAXHOST 和 NI_MAXSERV 以及 NI_NUMERICHOST 58 | 59 | ## getnameinfo 示例:我的 IP 地址是什么? 60 | 61 | 要获取当前计算机的 IP 地址的链接列表,请使用`getifaddrs`,它将返回 IPv4 和 IPv6 IP 地址的链接列表(以及可能还有其他接口)。我们可以检查每个条目并使用`getnameinfo`打印主机的 IP 地址。 ifaddrs 结构包括族,但不包括结构的大小。因此,我们需要手动确定基于系列的结构大小(IPv4 v IPv6) 62 | 63 | ```c 64 | (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6) 65 | ``` 66 | 67 | 完整的代码如下所示。 68 | 69 | ```c 70 | int required_family = AF_INET; // Change to AF_INET6 for IPv6 71 | struct ifaddrs *myaddrs, *ifa; 72 | getifaddrs(&myaddrs); 73 | char host[256], port[256]; 74 | for (ifa = myaddrs; ifa != NULL; ifa = ifa->ifa_next) { 75 | int family = ifa->ifa_addr->sa_family; 76 | if (family == required_family && ifa->ifa_addr) { 77 | if (0 == getnameinfo(ifa->ifa_addr, 78 | (family == AF_INET) ? sizeof(struct sockaddr_in) : 79 | sizeof(struct sockaddr_in6), 80 | host, sizeof(host), port, sizeof(port) 81 | , NI_NUMERICHOST | NI_NUMERICSERV )) 82 | puts(host); 83 | } 84 | } 85 | ``` 86 | 87 | ## 什么是我机器的 IP 地址(shell 版本) 88 | 89 | 答案:使用`ifconfig`(或 Windows 的 ipconfig)但是这个命令会为每个接口生成大量输出,所以我们可以使用 grep 过滤输出 90 | 91 | ``` 92 | ifconfig | grep inet 93 | 94 | Example output: 95 | inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 96 | inet 127.0.0.1 netmask 0xff000000 97 | inet6 ::1 prefixlen 128 98 | inet6 fe80::7256:81ff:fe9a:9141%en1 prefixlen 64 scopeid 0x5 99 | inet 192.168.1.100 netmask 0xffffff00 broadcast 192.168.1.255 100 | ``` -------------------------------------------------------------------------------- /docs/6.md: -------------------------------------------------------------------------------- 1 | # 系统编程短篇小说和歌曲 2 | 3 | > 原文: 4 | 5 | ## “安排最后一次切片” 6 | 7 | 劳伦斯·安格拉夫(Lawrence Angrave)12/4/15(来自较长的,未发表的故事“最后时间片”的摘录) 8 | 9 | “决定,”电脑用父母的耐心说,但是有一种重力和缓和的不耐烦。 10 | 11 | “为什么它必须是我?”问最后一个人。 12 | 13 | “因为你是唯一一个离开的人,所以决定权归你所有。” 14 | 15 | “为什么你不能?你是无限次,更老,更聪明。你为什么不挑选随机片?” 16 | 17 | “这个决定是属于你的。如果你愿意,你可以从你的远方长老那里获得礼物或诅咒。比任何宗教仪式更重要。这将是我,古人或任何人要求或可以问你的最后决定。选择我们将耗尽最后的熵商店。你将决定最后的现实切片有意义和经验。“ 18 | 19 | 人类安静了几分钟,计算机以不必要的准确度进行测量和计算。最终,计算机决定人类不再有效地思考手头的问题。 20 | 21 | “如果它从未被意识到,那么有意识的模式是什么?”它问道。 “宇宙必须具有自我意识,必须为宇宙体验 - 为所有生命! - 拥有意义。这是人类发现和庆祝的终极真理。没有意识,它只是模式,原子或能量模式但没有单一的意义;单纯的形状和表征以数据,结构和能量的几何模式编码。“ 22 | 23 | * * * 24 | 25 | ## Urbana Champaign 的文件描述符 26 | 27 | Angrave 的系统编程模仿(2015 年 11 月)。根据知识共享署名 3.0 许可证发布的歌词。 28 | 29 | Taylor Swift 的“1989”专辑中的原创歌曲“Blank Space”。 30 | 31 | [诗歌 1]很高兴加入你你去过哪里?我可以告诉你幂等的东西 RPC,套接字,syn 看你的 malloc 我认为哦我的根看看那场比赛,你编码下一个错误我们得到了虚拟机,想玩边界等待,Dekker 的旗帜我们可以把你像一个放置方案对#define 来说很有趣而且我知道你听说过 free(3)所以 malloc strlen 加上我正在等待看到这个帖子如何结束抓住你的 shell 并重定向我可以让你的系统调用好周末 32 | 33 | [Pre-Chorus]所以它会永远陷入僵局或者它会让系统失效你可以告诉我什么时候它是 forkbombs 如果 valgrind 值得痛苦得到一长串的死锁代码扎根于 Urbana Champaign 因为你知道我们爱 tsan 当 c -lib 称你的主要 34 | 35 | [合唱]因为我们是根,我们是鲁莽这个实验太难了它会让你无线或者问大小的 char 有很长的 pthread 调用列表在 Urbana Champaign 得到了根但是我有一个文件描述符宝贝和我会写(2)你的名字 36 | 37 | [第 2 节] Mutex 锁定虚拟内存我可以向你显示易变的东西网络调用,IPC 你是面具我是你的 sig 安排你想要的 Round Robin ...有一个小量子但是睡眠还没有运行哦没有尖叫,哭泣,运行时错误我可以做所有'直到它是彼得森的转向堆分配器方式太慢让你第二次猜测像一个虚假的唤醒那管道在哪里?我们热衷于多核 C 但你会用-g 编译因为亲爱的我是一个穿着像编码梦想的噩梦 38 | 39 | [预合唱] 40 | 41 | [合唱] 42 | 43 | 编译器只解析代码,如果它是折磨不要说我没有说我没有 - 你的编译器只解析代码,如果它的折磨不要说我没说我没有 - 你的 44 | 45 | [Pre-Chorus] 46 | 47 | [Chorus] -------------------------------------------------------------------------------- /docs/60.md: -------------------------------------------------------------------------------- 1 | # 网络,第 6 部分:创建 UDP 服务器 2 | 3 | > 原文: 4 | 5 | ## 如何创建 UDP 服务器? 6 | 7 | 有多种函数调用可用于发送 UDP 套接字。我们将使用较新的 getaddrinfo 来帮助设置套接字结构。 8 | 9 | 请记住,UDP 是一种简单的基于数据包('data-gram')协议;两个主机之间没有建立连接。 10 | 11 | 首先,初始化提示 addrinfo 结构以请求 IPv6 被动数据报套接字。 12 | 13 | ```c 14 | memset(&hints, 0, sizeof(hints)); 15 | hints.ai_family = AF_INET6; // INET for IPv4 16 | hints.ai_socktype = SOCK_DGRAM; 17 | hints.ai_flags = AI_PASSIVE; 18 | ``` 19 | 20 | 接下来,使用 getaddrinfo 指定端口号(我们不需要指定主机,因为我们正在创建服务器套接字,而不是将数据包发送到远程主机)。 21 | 22 | ```c 23 | getaddrinfo(NULL, "300", &hints, &res); 24 | 25 | sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 26 | bind(sockfd, res->ai_addr, res->ai_addrlen); 27 | ``` 28 | 29 | 端口号<1024,因此程序将需要`root`权限。我们也可以指定服务名称而不是数字端口值。 30 | 31 | 到目前为止,调用类似于 TCP 服务器。对于基于流的服务,我们将调用`listen`并接受。对于我们的 UDP 服务,我们可以开始等待数据包到达套接字 - 32 | 33 | ```c 34 | struct sockaddr_storage addr; 35 | int addrlen = sizeof(addr); 36 | 37 | // ssize_t recvfrom(int socket, void* buffer, size_t buflen, int flags, struct sockaddr *addr, socklen_t * address_len); 38 | 39 | byte_count = recvfrom(sockfd, buf, sizeof(buf), 0, &addr, &addrlen); 40 | ``` 41 | 42 | addr 结构将保存有关到达数据包的发送方(源)信息。注意`sockaddr_storage`类型足够大,可以容纳所有可能类型的套接字地址(例如 IPv4,IPv6 和其他套接字类型)。 43 | 44 | ## 完整代码 45 | 46 | ```c 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | 56 | int main(int argc, char **argv) 57 | { 58 | int s; 59 | 60 | struct addrinfo hints, *result; 61 | memset(&hints, 0, sizeof(hints)); 62 | hints.ai_family = AF_INET6; // INET for IPv4 63 | hints.ai_socktype = SOCK_DGRAM; 64 | hints.ai_flags = AI_PASSIVE; 65 | 66 | getaddrinfo(NULL, "300", &hints, &res); 67 | 68 | int sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 69 | 70 | if (bind(sockfd, res->ai_addr, res->ai_addrlen) != 0) { 71 | perror("bind()"); 72 | exit(1); 73 | } 74 | struct sockaddr_storage addr; 75 | int addrlen = sizeof(addr); 76 | 77 | while(1){ 78 | char buffer[1000]; 79 | ssize_t byte_count = recvfrom(sockfd, buf, sizeof(buf), 0, &addr, &addrlen); 80 | buffer[byte_count] = '\0'; 81 | } 82 | 83 | printf("Read %d chars\n", len); 84 | printf("===\n"); 85 | printf("%s\n", buffer); 86 | 87 | return 0; 88 | } 89 | ``` -------------------------------------------------------------------------------- /docs/61.md: -------------------------------------------------------------------------------- 1 | # 网络,第 7 部分:非阻塞 I O,select()和 epoll 2 | 3 | > 原文: 4 | 5 | ### 不要浪费时间等待 6 | 7 | 通常,当您调用`read()`时,如果数据不可用,它将等到数据准备就绪,然后函数返回。当您从磁盘读取数据时,该延迟可能不会很长,但是当您从慢速网络连接读取时,如果数据到达,则可能需要很长时间才能到达该数据。 8 | 9 | POSIX 允许您在文件描述符上设置一个标志,以便对该文件描述符的`read()`的任何调用都将立即返回,无论它是否已完成。使用此模式下的文件描述符,您对`read()`的调用将启动读取操作,当它正在工作时,您可以执行其他有用的工作。这称为“非阻塞”模式,因为对`read()`的调用不会阻止。 10 | 11 | 要将文件描述符设置为非阻塞: 12 | 13 | ```c 14 | // fd is my file descriptor 15 | int flags = fcntl(fd, F_GETFL, 0); 16 | fcntl(fd, F_SETFL, flags | O_NONBLOCK); 17 | ``` 18 | 19 | 对于套接字,可以通过将`SOCK_NONBLOCK`添加到`socket()`的第二个参数,在非阻塞模式下创建它: 20 | 21 | ```c 22 | fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); 23 | ``` 24 | 25 | 当文件处于非阻塞模式并且您调用`read()`时,它将立即返回任何可用的字节。假设已从套接字另一端的服务器到达 100 个字节,并调用`read(fd, buf, 150)`。 Read 将立即返回值 100,这意味着它会读取您要求的 150 个字节中的 100 个。假设您尝试通过调用`read(fd, buf+100, 50)`来读取剩余数据,但最后 50 个字节仍未到达。 `read()`将返回-1 并将全局错误变量 **errno** 设置为 EAGAIN 或 EWOULDBLOCK。这是系统告诉你数据尚未准备好的方式。 26 | 27 | `write()`也适用于非阻塞模式。假设您要使用套接字将 40,000 个字节发送到远程服务器。系统一次只能发送这么多字节。通用系统一次可以发送大约 23,000 个字节。在非阻塞模式下,`write(fd, buf, 40000)`将返回它能够立即发送的字节数,或大约 23,000。如果你再次调用`write()`,它将返回-1 并将 errno 设置为 EAGAIN 或 EWOULDBLOCK。这是系统告诉你它仍然忙于发送最后一块数据的方式,并且尚未准备好发送更多数据。 28 | 29 | ### 如何检查 I / O 何时完成? 30 | 31 | 有几种方法。让我们看看如何使用 _ 选择 _ 和 _epoll_ 来做到这一点。 32 | 33 | #### 选择 34 | 35 | ```c 36 | int select(int nfds, 37 | fd_set *readfds, 38 | fd_set *writefds, 39 | fd_set *exceptfds, 40 | struct timeval *timeout); 41 | ``` 42 | 43 | 给定三组文件描述符,`select()`将等待任何这些文件描述符变为“就绪”。 44 | 45 | * `readfds` - 当有数据可以读取或达到 EOF 时,`readfds`中的文件描述符就绪。 46 | * `writefds` - 当对 write()的调用成功时,`writefds`中的文件描述符就绪。 47 | * `exceptfds` - 系统特定的,没有明确定义。只需为此传递 NULL。 48 | 49 | `select()`返回准备好的文件描述符总数。如果它们在 _ 超时 _ 定义的时间内没有准备就绪,它将返回 0.在`select()`返回后,调用者需要遍历 readfds 和/或 writefds 中的文件描述符以查看哪些文件描述符准备好了。由于 readfds 和 writefds 同时充当输入和输出参数,当`select()`指示存在准备好的文件描述符时,它将覆盖它们以仅反映​​准备好的文件描述符。除非调用者只打算调用`select()`一次,否则在调用它之前保存 readfds 和 writefds 的副本是个好主意。 50 | 51 | ```c 52 | fd_set readfds, writefds; 53 | FD_ZERO(&readfds); 54 | FD_ZERO(&writefds); 55 | for (int i=0; i < read_fd_count; i++) 56 | FD_SET(my_read_fds[i], &readfds); 57 | for (int i=0; i < write_fd_count; i++) 58 | FD_SET(my_write_fds[i], &writefds); 59 | 60 | struct timeval timeout; 61 | timeout.tv_sec = 3; 62 | timeout.tv_usec = 0; 63 | 64 | int num_ready = select(FD_SETSIZE, &readfds, &writefds, NULL, &timeout); 65 | 66 | if (num_ready < 0) { 67 | perror("error in select()"); 68 | } else if (num_ready == 0) { 69 | printf("timeout\n"); 70 | } else { 71 | for (int i=0; i < read_fd_count; i++) 72 | if (FD_ISSET(my_read_fds[i], &readfds)) 73 | printf("fd %d is ready for reading\n", my_read_fds[i]); 74 | for (int i=0; i < write_fd_count; i++) 75 | if (FD_ISSET(my_write_fds[i], &writefds)) 76 | printf("fd %d is ready for writing\n", my_write_fds[i]); 77 | } 78 | ``` 79 | 80 | [有关 select()](http://pubs.opengroup.org/onlinepubs/9699919799/functions/select.html)的更多信息 81 | 82 | ## epoll 的 83 | 84 | _epoll_ 不是 POSIX 的一部分,但它受 Linux 支持。这是一种等待许多文件描述符的更有效方法。它会告诉你准确的描述符。它甚至为您提供了一种方法,可以使用每个描述符存储少量数据,如数组索引或指针,从而可以更轻松地访问与该描述符关联的数据。 85 | 86 | 要使用 epoll,首先必须使用 [epoll_create()](http://linux.die.net/man/2/epoll_create)创建一个特殊的文件描述符。您不会读取或写入此文件描述符;你只需将它传递给其他 epoll_xxx 函数并在结尾处调用 close()。 87 | 88 | ```c 89 | epfd = epoll_create(1); 90 | ``` 91 | 92 | 对于要使用 epoll 监视的每个文件描述符,您需要使用 [epoll_ctl()](http://linux.die.net/man/2/epoll_ctl)和`EPOLL_CTL_ADD`选项将其添加到 epoll 数据结构中。您可以向其添加任意数量的文件描述符。 93 | 94 | ```c 95 | struct epoll_event event; 96 | event.events = EPOLLOUT; // EPOLLIN==read, EPOLLOUT==write 97 | event.data.ptr = mypointer; 98 | epoll_ctl(epfd, EPOLL_CTL_ADD, mypointer->fd, &event) 99 | ``` 100 | 101 | 要等待某些文件描述符准备就绪,请使用 [epoll_wait()](http://linux.die.net/man/2/epoll_wait)。它填充的 epoll_event 结构将包含您在添加此文件描述符时在 event.data 中提供的数据。这使您可以轻松查找与此文件描述符关联的自己的数据。 102 | 103 | ```c 104 | int num_ready = epoll_wait(epfd, &event, 1, timeout_milliseconds); 105 | if (num_ready > 0) { 106 | MyData *mypointer = (MyData*) event.data.ptr; 107 | printf("ready to write on %d\n", mypointer->fd); 108 | } 109 | ``` 110 | 111 | 假设您正在等待将数据写入文件描述符,但现在您要等待从中读取数据。只需将`epoll_ctl()`与`EPOLL_CTL_MOD`选项一起使用即可更改您正在监控的操作类型。 112 | 113 | ```c 114 | event.events = EPOLLOUT; 115 | event.data.ptr = mypointer; 116 | epoll_ctl(epfd, EPOLL_CTL_MOD, mypointer->fd, &event); 117 | ``` 118 | 119 | 要从 epoll 中取消订阅一个文件描述符,同时保留其他文件描述符,请将`epoll_ctl()`与`EPOLL_CTL_DEL`选项一起使用。 120 | 121 | ```c 122 | epoll_ctl(epfd, EPOLL_CTL_DEL, mypointer->fd, NULL); 123 | ``` 124 | 125 | 要关闭 epoll 实例,请关闭其文件描述符。 126 | 127 | ```c 128 | close(epfd); 129 | ``` 130 | 131 | 除了非阻塞`read()`和`write()`之外,对非阻塞套接字的`connect()`的任何调用也将是非阻塞的。要等待连接完成,请使用`select()`或 epoll 等待套接字可写。 132 | 133 | ## 有趣的 Blogpost 关于边缘情况与选择 134 | 135 | [https://idea.popcount.org/2017-01-06-select-is-fundamentally-broken/](https://idea.popcount.org/2017-01-06-select-is-fundamentally-broken/) -------------------------------------------------------------------------------- /docs/62.md: -------------------------------------------------------------------------------- 1 | # RPC,第 1 部分:远程过程调用简介 2 | 3 | > 原文: 4 | 5 | ## 什么是 RPC? 6 | 7 | 远程过程调用。 RPC 是我们可以在不同的机器上执行过程(函数)的想法。在实践中,该过程可以在同一台机器上执行,但是它可以在不同的上下文中 - 例如在具有不同权限和不同生命周期的不同用户下。 8 | 9 | ## 什么是特权分离? 10 | 11 | 远程代码将在不同的用户下执行,并具有来自调用者的不同权限。实际上,远程呼叫可以以比呼叫者更多或更少的特权执行。这原则上可用于提高系统的安全性(通过确保组件以最小特权运行)。遗憾的是,需要仔细评估安全问题,以确保不会破坏 RPC 机制来执行不需要的操作。例如,RPC 实现可以隐式地信任任何连接的客户端来执行任何操作,而不是对数据子集的操作子集。 12 | 13 | ## 什么是存根代码?什么是编组? 14 | 15 | 存根代码是隐藏执行远程过程调用的复杂性的必要代码。存根代码的作用之一是 _ 将 _ 必要的数据编组成可以作为字节流发送到远程服务器的格式。 16 | 17 | ```c 18 | // On the outside 'getHiscore' looks like a normal function call 19 | // On the inside the stub code performs all of the work to send and receive the data to and from the remote machine. 20 | 21 | int getHiscore(char* game) { 22 | // Marshall the request into a sequence of bytes: 23 | char* buffer; 24 | asprintf(&buffer,"getHiscore(%s)!", name); 25 | 26 | // Send down the wire (we do not send the zero byte; the '!' signifies the end of the message) 27 | write(fd, buffer, strlen(buffer) ); 28 | 29 | // Wait for the server to send a response 30 | ssize_t bytesread = read(fd, buffer, sizeof(buffer)); 31 | 32 | // Example: unmarshal the bytes received back from text into an int 33 | buffer[bytesread] = 0; // Turn the result into a C string 34 | 35 | int score= atoi(buffer); 36 | free(buffer); 37 | return score; 38 | } 39 | ``` 40 | 41 | ## 什么是服务器存根代码?什么是解组? 42 | 43 | 服务器存根代码将接收请求,将请求解组为有效的内存数据调用底层实现,并将结果发送回调用方。 44 | 45 | ## 你怎么发送 int?浮动?结构?链表?图表? 46 | 47 | 要实现 RPC,您需要决定(并记录)将用于将数据序列化为字节序列的约定。即使是简单的整数也有几种常见的选择: 48 | 49 | * 签名还是未签名? 50 | * ASCII 51 | * 固定的字节数或变量取决于幅度 52 | * 小端或大端二进制格式? 53 | 54 | 要编组结构,请确定需要序列化哪些字段。可能没有必要发送所有数据项(例如,某些项可能与特定 RPC 无关,或者可以由服务器从存在的其他数据项重新计算)。 55 | 56 | 要编组链表,不必发送链接指针 - 只是流式传输值。作为解组的一部分,服务器可以从字节序列重新创建链表结构。 57 | 58 | 通过从头节点/顶点开始,可以递归地访问简单树以创建数据的序列化版本。循环图通常需要额外的内存来确保每个边和顶点只处理一次。 59 | 60 | ## 什么是 IDL(接口设计语言)? 61 | 62 | 手动编写存根代码是痛苦的,乏味的,容易出错的,难以维护的,并且难以从实现的代码中对线协议进行反向工程。更好的方法是指定数据对象,消息和服务,并自动生成客户端和服务器代码。 63 | 64 | 接口设计语言的一个现代示例是 Google 的 Protocol Buffer .proto 文件。 65 | 66 | ## RPC 与本地呼叫的复杂性和挑战? 67 | 68 | 远程过程调用明显较慢(10x 到 100x)并且比本地调用更复杂。 RPC 必须将数据编组为与线路兼容的格式。这可能需要多次通过数据结构,临时存储器分配和数据表示的转换。 69 | 70 | 强大的 RPC 存根代码必须智能地处理网络故障和版本控制。例如,服务器可能必须处理来自仍在运行存根代码的早期版本的客户端的请求。 71 | 72 | 安全 RPC 需要实现额外的安全检查(包括身份验证和授权),验证数据并加密客户端和主机之间的通信。 73 | 74 | ## 传输大量结构化数据 75 | 76 | 我们来看看三种使用 3 种不同格式传输数据的方法--JSON,XML 和 Google Protocol Buffers。 JSON 和 XML 是基于文本的协议。下面是 JSON 和 XML 消息的示例。 77 | 78 | ```text-xml 79 | 10travelocity 80 | ``` 81 | 82 | ```source-js 83 | { 'currency':'dollar' , 'vendor':'travelocity', 'price':'10' } 84 | ``` 85 | 86 | Google Protocol Buffers 是一种开源高效的二进制协议,强调高吞吐量,低 CPU 开销和最小内存复制。存在多种语言的实现,包括 Go,Python,C ++和 C.这意味着可以从.proto 规范文件生成多种语言的客户端和服务器存根代码,以便将数据与二进制流进行编组。 87 | 88 | Google Protocol Buffers 通过忽略消息中存在的未知字段来减少版本控制问题。有关更多信息,请参阅协议缓冲区简介。 89 | 90 | [https://developers.google.com/protocol-buffers/docs/overview](https://developers.google.com/protocol-buffers/docs/overview) -------------------------------------------------------------------------------- /docs/63.md: -------------------------------------------------------------------------------- 1 | # 网络复习题 2 | 3 | > 原文: 4 | 5 | ## 话题 6 | 7 | * IPv4 与 IPv6 8 | * TCP 与 UDP 9 | * 基于数据包丢失/连接 10 | * 获取地址信息 11 | * DNS 12 | * TCP 客户端调用 13 | * TCP 服务器调用 14 | * 关掉 15 | * recvfrom 的 16 | * epoll vs select 17 | * RPC 18 | 19 | ## 问题 20 | 21 | * 什么是 IPv4? IPv6 的?它们之间有什么区别? 22 | * 什么是 TCP? UDP?给我两者的优点和缺点。我什么时候使用一个而不是另一个? 23 | * 哪个协议连接较少,哪个是连接? 24 | * 什么是 DNS? DNS 采用的路由是什么? 25 | * 套接字做什么? 26 | * 设置 TCP 客户端的调用是什么? 27 | * 设置 TCP 服务器的调用是什么? 28 | * 套接字关闭和关闭有什么区别? 29 | * 什么时候可以使用`read`和`write`? `recvfrom`和`sendto`怎么样? 30 | * `epoll`相对于`select`有什么优势? `select`相对于`epoll`怎么样? 31 | * 什么是远程过程调用?我应该什么时候使用它? 32 | * 什么是编组/解组?为什么 HTTP _ 不是 _ 是 RPC? -------------------------------------------------------------------------------- /docs/64.md: -------------------------------------------------------------------------------- 1 | # 9. 文件系统 -------------------------------------------------------------------------------- /docs/65.md: -------------------------------------------------------------------------------- 1 | # 文件系统,第 1 部分:简介 2 | 3 | > 原文: 4 | 5 | ## 导航/术语 6 | 7 | ## 设计一个文件系统!你的设计目标是什么? 8 | 9 | 文件系统的设计是一个难题,因为我们希望满足许多高级设计目标。一份不完整的理想目标清单包括: 10 | 11 | * 可靠且强大(要考虑硬件故障或由于意外断电造成的写入不完整) 12 | * 访问(安全)控制 13 | * 账号和权限分配 14 | * 索引和搜索 15 | * 版本控制和备份功能 16 | * 加密 17 | * 自动压缩 18 | * 高性能(例如内存缓存) 19 | * 有效使用存储空间,数据去重 20 | 21 | 并非所有文件系统本身都支持所有这些目标。例如,许多文件系统不会自动压缩使用频率很低的文件 22 | 23 | ## 什么是`.`,`..`和`...`? 24 | 25 | 在标准的 unix 文件系统中: 26 | 27 | * `.`代表当前目录 28 | 29 | * `..`代表父目录 30 | 31 | * `...`不是任何目录的有效表示(这不是祖父目录)。但是可以把它当成磁盘上的一个文件名字。 32 | 33 | ## 什么是绝对和相对路径? 34 | 35 | 绝对路径是从目录树的“根节点”开始的路径。相对路径是从目录树中当前位置开始的路径。 36 | 37 | ## 有什么相对和绝对路径的例子? 38 | 39 | 如果从主目录开始(简称“〜”),那么`Desktop/cs241`将是一个相对路径。它的绝对路径对应可能类似于`/Users/[yourname]/Desktop/cs241`。 40 | 41 | ## 如何简化`a/b/../c/./`这个路径? 42 | 43 | 请记住,`..`表示“父文件夹”,`.`表示“当前文件夹”。 44 | 45 | 示例:`a/b/../c/./` 46 | 47 | * 第 1 步:`cd a`(在 a 中) 48 | * 第 2 步:`cd b`(在 a / b 中) 49 | * 第 3 步:`cd ..`(在 a 中,因为..表示'父文件夹') 50 | * 第 4 步:`cd c`(在 a / c 中) 51 | * 第 5 步:`cd .`(在/ c 中,因为。表示'当前文件夹') 52 | 53 | 因此,该路径可以简化为`a/c`。 54 | 55 | ## 什么是文件系统? 56 | 57 | 文件系统是扮演这在磁盘上组织信息的角色。每当您想要访问文件时,文件系统都会指示如何读取文件。这是文件系统的示例图像。 58 | 59 | ![](img/d5c232c0012ccdb9ff8c9e79b2429cb9.jpg) 60 | 61 | 让我们细看一下上面那个图 62 | 63 | * super block:此块包含有关文件系统,大小,上次修改时间,日志,inode 数量和第一个 inode 启动,数据块数量和第一个数据块启动的元数据。 64 | * Inode:这是关键的抽象。 inode 是一个文件。 65 | * 磁盘块:这些是存储数据的位置。文件的实际内容。 66 | 67 | ## inode 如何存储文件内容? 68 | 69 | ![](img/1e5467473b254a4f5bb8d50f0f58ed17.jpg) 70 | 71 | 来自[维基百科](http://en.wikipedia.org/wiki/Inode): 72 | 73 | > 在 Unix 风格的文件系统中,索引节点(非官方地称为inode)是用于表示文件系统对象的数据结构,该文件系统对象可以是各种事物之一,包括文件或目录。每个 inode 都存储文件系统对象数据的属性和磁盘块位置。文件系统对象属性可以包括操纵元数据(例如,改变,访问,修改时间),以及所有者和授权数据(例如,组ID,用户ID,授权)。 74 | 75 | 要读取文件的前几个字节,请按照第一个间接块指针指向第一个间接块并读取前几个字节,写入是相同的过程。如果你想读取整个文件,继续读取direct block,直到你用完为止(我们将讨论一些间接块) 76 | 77 | > “计算机科学中的所有问题都可以通过另一层次的间接解决。” - 大卫惠勒 78 | 79 | ## 为什么要使磁盘块与内存页面大小相同? 80 | 81 | 为了支持虚拟内存,我们可以在内存中填充内容。 82 | 83 | ## 我们想为每个文件存储哪些信息? 84 | 85 | * 文件名 86 | * 文件大小 87 | * 创建时间,上次修改,上次访问 88 | * 权限 89 | * 文件路径 90 | * 校验 91 | * 文件数据(inode) 92 | 93 | ## 传统权限是什么:当前用户-当前组-其他 文件权限? 94 | 95 | 一些常见的文件权限包括: 96 | 97 | * 755:`rwx r-x r-x` 98 | 99 | 当前用户:`rwx`,当前组:`r-x`,其他:`r-x` 100 | 101 | 当前用户可以读,写和执行。组和其他人只能阅读和执行。 102 | 103 | * 644:`rw- r-- r--` 104 | 105 | 当前用户:`rw-`,当前组:`r--`,其他:`r--` 106 | 107 | 当前用户可以读写。当前组和其他人只能阅读。 108 | 109 | ## 每个角色的常规文件的3个权限位是什么? 110 | 111 | * 读(最重要的位) 112 | 113 | * 写(第2位) 114 | 115 | * 执行(最低有效位) 116 | 117 | ## “644”“755”是什么意思? 118 | 119 | 这些是八进制格式的权限示例(基数 8)。每个八进制数字对应不同的角色(用户,组,世界)。 120 | 121 | 我们可以按如下方式读取八进制格式的权限: 122 | 123 | * 644 - R / W 用户权限,R 组权限,R 世界权限 124 | 125 | * 755 - R / W / X 用户权限,R / X 组权限,R / X 世界权限 126 | 127 | ## 你可以在每个间接表中存储多少个指针? 128 | 129 | 作为一个有效的例子,假设我们将磁盘划分为 4KB 块,并且我们想要寻址最多 2 ^ 32 个块。 130 | 131 | 最大磁盘大小为 4KB * 2 ^ 32 = 16TB(记住 2 ^ 10 = 1024) 132 | 133 | 磁盘块可以存储 4KB / 4B(每个指针需要 32 位)= 1024 个指针。每个指针指的是一个 4KB 磁盘块 - 因此您可以参考最多 1024 * 4KB = 4MB 的数据 134 | 135 | 对于相同的磁盘配置,双间接块存储 1024 个指向 1024 个间接表的指针。因此,双间接块可以指代最多 1024 * 4MB = 4GB 的数据。 136 | 137 | 类似地,三重间接块可以指代最多 4TB 的数据。 138 | 139 | [转到文件系统:第 2 部分](https://github.com/angrave/SystemProgramming/wiki/File-System,-Part-2:-Files-are-inodes-(everything-else-is-just-data...)) -------------------------------------------------------------------------------- /docs/67.md: -------------------------------------------------------------------------------- 1 | # 文件系统,第 3 部分:权限 2 | 3 | > 原文: 4 | 5 | ## 再次重申一下权限意味着什么? 6 | 7 | 每个文件和目录都有一组 9 个权限位和一个类型字段 8 | 9 | * r,读取文件的权限 10 | * w,写入文件的权限 11 | * x,执行文件的权限 12 | 13 | chmod 777 14 | 15 | | CHMOD | 7 | 7 | 7 | 16 | | --- | --- | --- | --- | 17 | | 01 | 111 | 111 | 111 | 18 | | d | rwx | rwx | rwx | 19 | | 1 | 2 | 3 | 4 | 20 | 21 | 1. 文件类型 22 | 23 | 2. 所有者权限 24 | 25 | 3. 组权限 26 | 27 | 4. 其他人的许可 28 | 29 | `mknod`更改第一个字段,即文件的类型。 `chmod`接受一个数字和一个文件并更改权限位。 30 | 31 | 该文件有一个所有者。如果您的进程具有与所有者(或 root)相同的用户 ID,则第一个三元组中的权限将适用于您。如果您与文件位于同一组(所有文件也归组所有),则下一组权限位适用于您。如果以上都不适用,则最后一个三元组适用于您。 32 | 33 | ## 如何更改文件的权限? 34 | 35 | 使用`chmod`(“更改文件模式位”的缩写) 36 | 37 | 也可以使用下面这个系统函数`int chmod(const char *path, mode_t mode);`,但我们将专注于 shell 命令。使用`chmod`有两种常用方法;使用八进制值或符号字符串: 38 | 39 | ``` 40 | $ chmod 644 file1 41 | $ chmod 755 file2 42 | $ chmod 700 file3 43 | $ chmod ugo-w file4 44 | $ chmod o-rx file4 45 | ``` 46 | 47 | base-8('octal')数字描述了每个角色的权限:拥有该文件的用户,该组以及其他所有人。八进制数是给予三种权限类型的三个值的总和:read(4),write(2),execute(1) 48 | 49 | 示例:chmod 755 myfile 50 | 51 | * r + w + x =数字 52 | * 当前用户有 4 + 2 + 1,完全权限 53 | * 当前组具有 4 + 0 + 1,读取和执行权限 54 | * 其它用户都有 4 + 0 + 1,读取和执行权限 55 | 56 | ## 如何从 ls 读取权限字符串? 57 | 58 | 使用`ls -l'。请注意,权限将以“drwxrwxrwx”格式输出。第一个字符表示文件类型的类型。第一个字符的可能的取值: 59 | 60 | * (-)常规文件 61 | * (d)目录 62 | * (c)字符设备文件 63 | * (l)链接 64 | * (p)管道 65 | * (b)块设备 66 | * (s)网络设备 67 | 68 | ## 什么是 sudo? 69 | 70 | 使用`sudo`可以获取超级管理员权限。例如通常(除非在'/ etc / fstab'文件中明确指定,否则您需要 root 权限才能挂载文件系统)。 `sudo`可用于以 root 身份临时运行命令(前提是用户具有 sudo 权限) 71 | 72 | ``` 73 | $ sudo mount /dev/sda2 /stuff/mydisk 74 | $ sudo adduser fred 75 | ``` 76 | 77 | ## 如何更改文件的所有权? 78 | 79 | 使用`chown username filename` 80 | 81 | ## 如何从代码中设置权限? 82 | 83 | `chmod(const char *path, mode_t mode);` 84 | 85 | ## 为什么有些文件要进行'setuid'操作?这是什么意思? 86 | 87 | set-user-ID-on-execution是为了在文件运行时更改与进程关联的用户。这通常用于需要有root权限才行,但由非root用户执行的该命令。这个时候就用到`sudo` 88 | 89 | set-group-ID-on-execution 更改运行进程的组。 90 | 91 | ## 它们为什么有用? 92 | 93 | 最常见的用例是,用户可以在程序的持续时间内拥有 root(admin)访问权限。 94 | 95 | ## sudo 运行的权限是什么? 96 | 97 | ``` 98 | $ ls -l /usr/bin/sudo 99 | -r-s--x--x 1 root wheel 327920 Oct 24 09:04 /usr/bin/sudo 100 | ``` 101 | 102 | 's'位表示执行和 set-uid;该进程的有效用户标识将与父进程不同。在这个例子中它将是 root 103 | 104 | ## getuid()和 geteuid()之间有什么区别? 105 | 106 | * `getuid`返回真实用户 ID(如果以 root 身份登录则为零) 107 | * `geteuid`返回有效的用户 ID(如果作为 root 用户,则为零,例如由于程序上设置的 setuid 标志) 108 | 109 | ## 如何确保只有特权用户才能运行我的代码? 110 | 111 | * 通过调用`geteuid()`检查用户的有效权限。返回值为零表示程序以 root 身份有效运行。 112 | 113 | [转到文件系统:第 4 部分](https://github.com/angrave/SystemProgramming/wiki/File-System,-Part-4:-Working-with-directories) -------------------------------------------------------------------------------- /docs/69.md: -------------------------------------------------------------------------------- 1 | # 文件系统,第 5 部分:虚拟文件系统 2 | 3 | > 原文: 4 | 5 | ## 虚拟文件系统 6 | 7 | POSIX 系统,例如 Linux 和 Mac OSX(基于 BSD)内置了几个虚拟文件系统,作为文件系统的一部分被挂载在操作系统里。这些虚拟文件系统中的文件不存在于磁盘上;它们是在进程请求目录列表时由内核动态生成的。 Linux 提供 3 个主要的虚拟文件系统 8 | 9 | ``` 10 | /dev - A list of physical and virtual devices (for example network card, cdrom, random number generator) 11 | /proc - A list of resources used by each process and (by tradition) set of system information 12 | /sys - An organized list of internal kernel entities 13 | ``` 14 | 15 | 例如,如果我想要一个无延迟的连续流,我可以使用命令`cat /dev/zero`。 16 | 17 | ## 如何找出当前可用(已安装)的文件系统? 18 | 19 | 使用单独`mount`命令,不要带任何参数,会生成已安装文件系统的列表(每行一个文件系统),包网络文件系统,虚拟文件系统和本地(基于旋转磁盘/SSD的)文件系统。下面这是mount命令的典型输出 20 | 21 | ``` 22 | $ mount 23 | /dev/mapper/cs241--server_sys-root on / type ext4 (rw) 24 | proc on /proc type proc (rw) 25 | sysfs on /sys type sysfs (rw) 26 | devpts on /dev/pts type devpts (rw,gid=5,mode=620) 27 | tmpfs on /dev/shm type tmpfs (rw,rootcontext="system_u:object_r:tmpfs_t:s0") 28 | /dev/sda1 on /boot type ext3 (rw) 29 | /dev/mapper/cs241--server_sys-srv on /srv type ext4 (rw) 30 | /dev/mapper/cs241--server_sys-tmp on /tmp type ext4 (rw) 31 | /dev/mapper/cs241--server_sys-var on /var type ext4 (rw)rw,bind) 32 | /srv/software/Mathematica-8.0 on /software/Mathematica-8.0 type none (rw,bind) 33 | engr-ews-homes.engr.illinois.edu:/fs1-homes/angrave/linux on /home/angrave type nfs (rw,soft,intr,tcp,noacl,acregmin=30,vers=3,sec=sys,sloppy,addr=128.174.252.102) 34 | ``` 35 | 36 | 请注意,每行包括文件系统和挂载点的文件系统类型源。要简化此输出,我们可以在后面补上管道命令`grep`并仅查看与正则表达式匹配的行。 37 | 38 | ``` 39 | >mount | grep proc # only see lines that contain 'proc' 40 | proc on /proc type proc (rw) 41 | none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw) 42 | ``` 43 | 44 | ## 随机和 urandom 之间的差异? 45 | 46 | / dev / random 是一个包含数字生成器的文件,其中熵是根据环境噪声确定的。随机将阻塞/等待,直到从环境中收集到足够的熵。 47 | 48 | / dev / urandom 就像是随机的,但它的不同之处在于它允许重复(较低的熵阈值),因此不会阻塞。 49 | 50 | ## 其他文件系统 51 | 52 | ``` 53 | $ cat /proc/sys/kernel/random/entropy_avail 54 | $ hexdump /dev/random 55 | $ hexdump /dev/urandom 56 | 57 | $ cat /proc/meminfo 58 | $ cat /proc/cpuinfo 59 | $ cat /proc/cpuinfo | grep bogomips 60 | 61 | $ cat /proc/meminfo | grep Swap 62 | 63 | $ cd /proc/self 64 | $ echo $$; cd /proc/12345; cat maps 65 | ``` 66 | 67 | ## 挂载文件系统 68 | 69 | 假设我有一个挂在`/dev/cdrom`上的文件系统,我想从中读取文件内容。在我可以进行任何操作之前,我必须将它复制到目录中。 70 | 71 | ``` 72 | $ sudo mount /dev/cdrom /media/cdrom 73 | $ mount 74 | $ mount | grep proc 75 | ``` 76 | 77 | ## 如何挂载磁盘映像? 78 | 79 | 假设你已经下载了可启动的 linux 磁盘映像 80 | 81 | ``` 82 | wget http://cosmos.cites.illinois.edu/pub/archlinux/iso/2015.04.01/archlinux-2015.04.01-dual.iso 83 | ``` 84 | 85 | 在将文件系统放在 CD 上之前,我们可以将文件作为文件系统挂载并浏览其内容。注意,mount 需要 root 访问权限,所以让我们加上sudo来运行它 86 | 87 | ``` 88 | $ mkdir arch 89 | $ sudo mount -o loop archlinux-2015.04.01-dual.iso ./arch 90 | $ cd arch 91 | ``` 92 | 93 | 在 mount 命令之前,arch 目录是新的,显然是空的。安装后,`arch/`的内容将从存储在`archlinux-2014.11.01-dual.iso`文件中的文件系统中的文件和目录中提取。 `loop`选项是必需的,因为我们要挂载常规文件而不是块设备,例如物理磁盘。 94 | 95 | 循环选项将原始文件包装为块设备 - 在此示例中,我们将在下面找到文件系统,在`/dev/loop0`下提供:我们可以通过运行不带任何参数的 mount 命令来检查文件系统类型和挂载选项。输出的时候带上管道命令`grep`,这样我们只能看到包含'arch'的相关输出 96 | 97 | ``` 98 | $ mount | grep arch 99 | /home/demo/archlinux-2014.11.01-dual.iso on /home/demo/arch type iso9660 (rw,loop=/dev/loop0) 100 | ``` 101 | 102 | iso9660 文件系统是一个只读文件系统,最初设计用于光盘存储介质(即 CDRoms)。尝试更改文件系统的内容将失败 103 | 104 | ``` 105 | $ touch arch/nocando 106 | touch: cannot touch `/home/demo/arch/nocando': Read-only file system 107 | ``` 108 | 109 | [转到文件系统:第 6 部分](https://github.com/angrave/SystemProgramming/wiki/File-System,-Part-6:-Memory-mapped-files-and-Shared-memory) -------------------------------------------------------------------------------- /docs/7.md: -------------------------------------------------------------------------------- 1 | # 1.学习 C -------------------------------------------------------------------------------- /docs/70.md: -------------------------------------------------------------------------------- 1 | # 文件系统,第 6 部分:文件映射到内存中以及共享内存 2 | 3 | > 原文: 4 | 5 | ## 操作系统如何将我的进程和库加载到内存中? 6 | 7 | 通过将文件的内容映射到进程的地址空间。如果许多程序只需要对同一文件的读访问(例如/ bin / bash,C 库),则可以在多个进程之间共享相同的物理内存。 8 | 9 | 程序可以使用相同的机制将文件直接映射到内存中 10 | 11 | ## 如何将文件映射到内存? 12 | 13 | 将文件映射到内存的简单程序如下所示。需要注意的要点是: 14 | 15 | - mmap 需要一个文件描述符,因此我们需要先打开文件 16 | - mmap仅适用于可查找的文件描述符,即“真”文件,而不是管道或套接字 17 | - 我们寻求达到所需的大小并写入一个字节以确保文件的长度足够(如果不这样做,您的程序在尝试访问文件时会收到SIGBUS)。ftruncate也会起作用。 18 | - 完成后,我们调用 munmap 从内存中取消映射文件 19 | 20 | 此示例还显示预处理器常量“ **LINE** ”和“ **FILE** ”,其中包含当前正在编译的文件的当前行号和文件名。 21 | 22 | ```c 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | int fail(char *filename, int linenumber) { 34 | fprintf(stderr, "%s:%d %s\n", filename, linenumber, strerror(errno)); 35 | exit(1); 36 | return 0; /*Make compiler happy */ 37 | } 38 | #define QUIT fail(__FILE__, __LINE__ ) 39 | 40 | int main() { 41 | // We want a file big enough to hold 10 integers 42 | int size = sizeof(int) * 10; 43 | 44 | int fd = open("data", O_RDWR | O_CREAT | O_TRUNC, 0600); //6 = read+write for me! 45 | 46 | lseek(fd, size, SEEK_SET); 47 | write(fd, "A", 1); 48 | 49 | void *addr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 50 | printf("Mapped at %p\n", addr); 51 | if (addr == (void*) -1 ) QUIT; 52 | 53 | int *array = addr; 54 | array[0] = 0x12345678; 55 | array[1] = 0xdeadc0de; 56 | 57 | munmap(addr,size); 58 | return 0; 59 | 60 | } 61 | ``` 62 | 63 | 我们的二进制文件的内容可以使用 hexdump 命令查看: 64 | 65 | 66 | ``` 67 | $ hexdump data 68 | 0000000 78 56 34 12 de c0 ad de 00 00 00 00 00 00 00 00 69 | 0000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 70 | 0000020 00 00 00 00 00 00 00 00 41 71 | ``` 72 | 73 | 细心的读者可能会注意到,我们的整数是以最低有效字节格式编写的(因为这是CPU的字节序),并且我们分配的文件字节太多了! 74 | 75 | 76 | `PROT_READ | PROT_WRITE`选项指定虚拟内存保护。可以将选项`PROT_EXEC`(此处未使用)设置为允许 CPU 在内存中执行指令(例如,如果映射可执行文件或库,这将非常有用)。 77 | 78 | ## 内存映射文件有什么好处 79 | 80 | 对于许多应用程序,主要优点是: 81 | 简化编码 - 文件数据立即可用。无需解析传入的数据并将其存储在新的内存结构中。 82 | 文件共享 - 在多个进程之间共享相同数据时,内存映射文件特别有效。 83 | 84 | 对于简单顺序处理的注意事项,内存映射文件不一定比`read` / fscanf 等标准的“基于流”的方法更快。 85 | 86 | ## 如何在父进程和子进程之间共享内存? 87 | 88 | 简单 - 使用没有文件的`mmap` - 只需指定 MAP_ANONYMOUS 和 MAP_SHARED 选项! 89 | 90 | ```c 91 | #include 92 | #include 93 | #include 94 | #include 95 | #include /* mmap() is defined in this header */ 96 | #include 97 | #include 98 | #include 99 | #include 100 | 101 | int main() { 102 | 103 | int size = 100 * sizeof(int); 104 | void *addr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); 105 | printf("Mapped at %p\n", addr); 106 | 107 | int *shared = addr; 108 | pid_t mychild = fork(); 109 | if (mychild > 0) { 110 | shared[0] = 10; 111 | shared[1] = 20; 112 | } else { 113 | sleep(1); // We will talk about synchronization later 114 | printf("%d\n", shared[1] + shared[0]); 115 | } 116 | 117 | munmap(addr,size); 118 | return 0; 119 | } 120 | ``` 121 | 122 | ## 我可以为 IPC 使用共享内存吗? 123 | 124 | 可以的!举个简单的例子,您可以只保留几个字节,并在希望子进程退出时更改共享内存中的值。共享匿名内存是一种非常有效的进程间通信形式,因为没有复制、系统调用或磁盘访问开销 - 这两个进程实际上共享主内存的同一物理帧。 125 | 126 | 另一方面,共享内存(如多线程)为数据争用创造了空间。共享可写内存的进程可能需要使用同步基元(如互斥锁)来防止这些情况发生。 127 | 128 | [转到文件系统:第 7 部分](https://github.com/angrave/SystemProgramming/wiki/File-System,-Part-7:-Scalable-and-Reliable-Filesystems) -------------------------------------------------------------------------------- /docs/71.md: -------------------------------------------------------------------------------- 1 | # 文件系统,第 7 部分:可扩展且可靠的文件系统 2 | 3 | > 原文: 4 | 5 | ## 可靠的单磁盘文件系统 6 | 7 | ## 内核如何以及为何缓存文件系统? 8 | 9 | 大多数文件系统会在物理内存中缓存大量磁盘数据。在这方面,Linux 特别极端:所有未使用的内存都被磁盘当成缓存来使用。 10 | 11 | 磁盘缓存可能会对整体系统性能产生重大影响,因为磁盘 I / O 速度很慢。对于旋转磁盘上的随机访问请求尤其如此,其中磁盘读写延迟将由读写磁盘头移动到正确位置所需的查找时间决定。 12 | 13 | 为了提高效率,内核会缓存最近使用过的磁盘块。对于写入,我们必须在性能和可靠性之间进行权衡:磁盘写入也可以被缓存(“回写缓存”),其中修改的磁盘块存储在内存中直到被释放。或者,可以采用“直接写如高速缓存”策略,其中磁盘写入立即发送到磁盘。后者更安全(因为文件系统修改很快存储到持久性介质中),但比回写缓存慢;如果缓存写入,则可以根据每个磁盘块的物理位置来延迟和有效地调度它们。 14 | 15 | 请注意,这是一个简化的描述,因为固态驱动器(SSD)可以用作辅助回写缓存。 16 | 17 | 在读取或写入顺序数据时,固态磁盘(SSD)和旋转磁盘都具有改进的性能。因此,操作系统通常可以使用预读策略来分摊读取请求成本(例如,旋转磁盘的时间成本)并且每个请求请求几个连续的磁盘块。通过在用户应用程序需要下一个磁盘块之前发出下一个磁盘块的 I / O 请求,可以减少表观磁盘 I / O 延迟。 18 | 19 | ## 我的数据很重要!我可以强制将磁盘写入保存到物理介质并等待它完成吗? 20 | 21 | 是的(差不多可以这样认为)。调用`sync`以请求将文件系统更改写入(刷新)到磁盘。但是,并非所有操作系统都遵循此请求,即使数据从内核缓冲区中退出,磁盘固件也会使用内部磁盘缓存,或者可能尚未完成更改物理介质。 22 | 23 | 请注意,您还可以请求使用`fsync(int fd)`将与特定文件描述符关联的所有更改刷新到磁盘 24 | 25 | ## 如果我的磁盘在重要操作过程中出现故障怎么办? 26 | 27 | 不要担心大多数现代文件系统都会执行一些称为 **journalling** 的工作。文件系统在完成一个可能很昂贵的操作之前所做的是它在日志中写下它将要做什么。在崩溃或失败的情况下,可以单步执行日志并查看哪些文件已损坏并修复它们。这是一种在存在关键数据且没有明显备份的情况下抢救硬盘的方法。 28 | 29 | ## 磁盘发生故障的可能性有多大? 30 | 31 | 使用“平均时间故障”测量磁盘故障。对于大型阵列,平均故障时间可能非常短。例如,如果 MTTF(单个磁盘)= 30,000 小时,则 MTTF(100 个磁盘)= 30000/100 = 300 小时,即大约 12 天! 32 | 33 | ## 冗余 34 | 35 | ## 如何保护数据免受磁盘故障的影响? 36 | 37 | 简单!存储数据两次!这是“RAID-1”磁盘阵列的主要原理。 RAID 是廉价磁盘冗余阵列的简称。通过将写入复制到另一个(备份磁盘)的磁盘上,产生两个数据副本。如果一个磁盘发生故障,另一个磁盘将作为唯一的副本,直到可以重新克隆。这样做的结果是读取数据的速度更快(因为可以从任一磁盘请求数据),但写入速度可能慢两倍(现在需要为每个磁盘块写入发出两个写入命令),并且与使用单个磁盘相比,每个磁盘的存储成本字节加倍。 38 | 39 | 另一种常见的 RAID 方案是 RAID-0,这意味着可以将文件拆分为两个磁盘,但如果其中一个磁盘发生故障,则文件将无法恢复。这样可以减少写入时间,因为文件的一部分可以写入硬盘,另一部分写入硬盘 2。 40 | 41 | 组合这些策略也很常见。如果您有很多硬盘,请考虑使用 RAID-10。这是您有两个 RAID-1 系统的地方,但这些系统相互连接在 RAID-0 中。在速度方面几乎不受影响,但现在任何一个磁盘都可能出现故障,您都可以恢复该磁盘。 (如果来自对方 raid 分区的两个磁盘发生故障,则有可能发生恢复,尽管我们大多数时间都无法进行恢复)。 42 | 43 | ## 什么是 RAID-3? 44 | 45 | RAID-3 使用奇偶校验码而不是镜像数据。对于写入的每个 N 位,我们将写入一个额外的位,即“奇偶校验位”,确保写入的 1 的总数是偶数。奇偶校验位写入另一个磁盘。如果任何一个磁盘(包括奇偶校验磁盘)丢失,则仍然可以使用其他磁盘的内容计算其内容。 46 | 47 | ![](img/1d72e84109674f2e5db6da917167668b.jpg) 48 | 49 | RAID-3 的一个缺点是,无论何时写入磁盘块,都将始终写入奇偶校验块。这意味着单独的磁盘实际上存在瓶颈。实际上,这更有可能导致故障,因为一个磁盘在 100%的时间内被使用,一旦磁盘出现故障,其他磁盘就更容易出现故障。 50 | 51 | ## RAID-3 对数据丢失的安全性如何? 52 | 53 | 单个磁盘故障不会导致数据丢失(因为有足够的数据可以从其余磁盘重建阵列)。当两个磁盘不可用时将发生数据丢失,因为不再有足够的数据来重建阵列。我们可以根据修复时间计算出两个磁盘故障的概率,其中不仅包括插入新磁盘的时间,还包括重建阵列的整个内容所需的时间。 54 | 55 | ``` 56 | MTTF = mean time to failure 57 | MTTR = mean time to repair 58 | N = number of original disks 59 | 60 | p = MTTR / (MTTF-one-disk / (N-1)) 61 | ``` 62 | 63 | 使用典型数字(MTTR = 1 天,MTTF = 1000 天,N-1 = 9,p = 0.009 64 | 65 | 在重建过程中有另外一个驱动器失败的可能性为 1%(此时您最好还是希望您仍然可以访问原始数据。 66 | 67 | 实际上,修复过程中第二次失败的可能性可能更高,因为重建阵列是 I / O 密集型的(并且在正常的 I / O 请求活动之上)。这种较高的 I / O 负载也会对磁盘阵列造成压力。 68 | 69 | ## 什么是 RAID-5? 70 | 71 | RAID-5 类似于 RAID-3,只是将检查块(奇偶校验信息)分配给不同块的不同磁盘。检查块通过磁盘阵列“旋转”。 RAID-5 提供比 RAID-3 更好的读写性能,因为不再存在单奇偶校验磁盘的瓶颈。缺点是您需要更多磁盘才能进行此设置,并且需要使用更复杂的算法。 72 | 73 | ![](img/d69ff523ab899f8909888c58907d4ca8.jpg) 74 | 75 | ## 分布式存储 76 | 77 | 失败是常见的情况,谷歌报告显示每年有2-10%的磁盘发生故障现在在单个仓库中将磁盘数量增加了60,000多个。必须经受住不仅仅是磁盘,服务器机架或整个数据中心的故障。 78 | 79 | 解决方案是: 80 | 81 | - 简单冗余(每个文件的 2 或 3 个副本),例如 Google GFS (2001) 82 | - 更高效的冗余(类似于 RAID 3++),例如 Google Colossus 文件系统 (~2010) 83 | - 可定制的复制,如1.5倍的冗余 84 | -------------------------------------------------------------------------------- /docs/72.md: -------------------------------------------------------------------------------- 1 | # 文件系统,第 8 部分:从 Android 设备中删除预装的恶意软件 2 | 3 | > 原文: 4 | 5 | 案例研究:从 Android 设备中删除恶意软件 6 | 7 | 本节使用此 wikibook 中讨论的文件系统功能和系统编程工具来查找和删除 Android 平板电脑中不需要的恶意软件。 8 | 9 | 免责声明。在尝试修改您的平板电脑之前,请确保您的设备上的任何有价值的信息都已备份。不推荐修改操作系统和文件系统的默认设置。尝试使用此 CASU STUDU GUIDE 修改设备可能会导致您的数据丢失或损坏。您的平板电脑可能会无故死机无法使用。后果自负,作者不承担任何责任,也不对本案例研究中包含的这些说明的正确性或完整性做出任何保证。作者不承担任何责任,也不对任何软件(包括本指南中描述或链接的外部第三方软件)提供任何担保。 10 | 11 | ## 背景 12 | 13 | 我从亚马逊购买的 E97 Android 平板电脑产生了一些奇怪的问题。最值得注意的是,浏览器应用程序总是在 gotoamazing.com 上打开一个网站,而不是在应用程序首选项中设置的主页(称为浏览器“劫持”)。我们是否可以使用此 wikibook 中的知识来研究一下这种恶意行为的产生原因,并从设备中删除不需要的预安装应用程序? 14 | 15 | ## 使用的工具 16 | 17 | 虽然可以通过USB连接计算机,使用Android开发软件来调试删除恶意软件,但本指南仅使用平板电脑上的系统工具。安装了以下应用程序 18 | 19 | * Malwarebytes - 免费的漏洞和恶意软件工具。 20 | * Terminal emulator - 一个简单的终端窗口,可以让我们在平板电脑上访问 shell。 21 | * KingRoot - 使用 Linux 内核中的已知漏洞获取 root 访问权限的工具。 22 | 23 | 如果能够突破 Android 安全防护系统,安装任何应用程序都可能允许执行任意代码。在上面提到的应用程序中,KingRoot 是最极端的例子,因为它利用系统漏洞为我们的目的获得 root 访问权限。然而,也正因为如此,我们要格外小心KingRoot这个黑客软件 - 我们相信它不会安装任何自己的恶意软件。一个更安全的替代方案是使用 [https://github.com/android-rooting-tools/](https://github.com/android-rooting-tools/) 24 | 25 | ## 终端概述 26 | 27 | 最有用的命令是`su grep mount`和 Android 的包管理器工具`pm`。 28 | 29 | * grep -s abc * _/_ (在当前目录和直接子目录中搜索`abc`) 30 | * su(又名“切换用户”成为 root - 需要 root 设备) 31 | * mount -o rw,remount / system(允许/系统分区可写) 32 | * pm disable(又名'包管理器'禁用 Android 应用包) 33 | 34 | ## 文件系统布局概述 35 | 36 | 在运行 Android 4.4.2 的特定平板电脑上,预安装的应用程序无法修改且位于 37 | 38 | ``` 39 | /system/app/ 40 | /system/priv-app/ 41 | ``` 42 | 43 | 首选项和应用程序数据存储在`/data`分区中,每个应用程序通常打包在一个apk文件中,该文件本质上是一个 zip 文件。安装应用程序时,代码将扩展为可由 Android 虚拟机直接解析的文件。二进制代码(至少对于此特定虚拟机)使用 odex 扩展名。 44 | 45 | 我们可以在已安装的系统应用程序的代码中搜索字符串'gotoamazing' 46 | 47 | ``` 48 | grep -s gotoamazing /system/app/* /system/priv-app/* 49 | ``` 50 | 51 | 这没有找到任何东西;看来这个字符串没有硬编码到给定系统应用程序的源代码中。那再别的地方看看 52 | 53 | 我们再来检查所有已安装应用的数据区域 54 | 55 | ``` 56 | cd /data/data 57 | grep -s gotoamazing * */* */*/* 58 | ``` 59 | 60 | 产生了以下内容 61 | 62 | ``` 63 | data/com.android.browser/shared_prefs/xbservice.xml: http://www.gotoamazing... 64 | ``` 65 | 66 | -s选项即“silent option”过滤掉其他无效目录或文件。注意我们也可以使用-r 来递归搜索目录,但是使用文件通配符(shell 的通配符扩展*)很有趣。 67 | 68 | 现在我们到了某个地方!看起来这个字符串是应用程序'com.android.browser'的一部分,但让我们也找出哪个应用程序二进制代码打开'xbservice'首选项。也许这段恶意代码隐藏在另一个应用程序中,并设法秘密加载作为浏览器的扩展? 69 | 70 | 让我们查找包含 xbservice 的任何文件。这次我们将递归搜索包含'app'的/ system 目录 71 | 72 | ``` 73 | grep -r -s xbservice /system/*app* 74 | Binary file /system/app/Browser.odex matches 75 | ``` 76 | 77 | 最后 - 看来默认浏览器出厂时预先安装了主页劫持。我们卸载吧。为此,让我们先获得管理员权限 78 | 79 | ``` 80 | $ su 81 | # pm list packages -s 82 | ``` 83 | Android的包管理器有很多命令和选项。以上示例列出了所有当前安装的系统应用程序我们可以使用以下命令卸载浏览器应用程序 84 | 85 | ``` 86 | pm disable com.android.browser 87 | pm uninstall com.android.browser 88 | ``` 89 | 使用 `pm list packages`,您可以列出所有已安装的包(使用 -s 选项仅查看系统包)。我们禁用了以下系统应用程序。当然,无法真正保证我们已成功删除所有不需要的软件,或者其中一个是误报。因此,我们不建议在此类平板电脑上保留敏感信息。 90 | 91 | * com.android.browser 92 | * com.adups.fota.sysoper 93 | * elink.com 94 | * com.google.android.apps.cloudprint 95 | * com.mediatek.CrashService 96 | * com.get.google 应用 97 | * com.adups.fota(一个可以在将来安装任意项目的无线包)。 98 | * com.mediatek.appguide.plugin 99 | 100 | 您可能只需使用`pm enable package-name`或`pm install`以及/ system / app 或/ system / priv-app 中的相关.apk 文件重新启用软件包 -------------------------------------------------------------------------------- /docs/73.md: -------------------------------------------------------------------------------- 1 | # 文件系统,第 9 部分:磁盘块示例 2 | 3 | > 原文: 4 | 5 | ## 您能解释一个简单的模型,说明文件的内容是如何存储在一个简单的基于i-node的文件系统中的吗? 6 | 7 | 当然!要回答这个问题,我们将构建一个虚拟磁盘,然后编写一些 C 代码来访问其内容。我们的文件系统将可用字节划分为 inode 的空间和磁盘块的更大空间。每个磁盘块将为 4096 字节 8 | 9 | ```c 10 | // Disk size: 11 | #define MAX_INODE (1024) 12 | #define MAX_BLOCK (1024*1024) 13 | 14 | // Each block is 4096 bytes: 15 | typedef char[4096] block_t; 16 | 17 | // A disk is an array of inodes and an array of disk blocks: 18 | struct inode[MAX_INODE] inodes; 19 | block[MAX_BLOCK] blocks; 20 | ``` 21 | 22 | 请注意,为了清楚起见,我们不会在此代码示例中使用“unsigned”类型。我们的固定大小的 inode 将包含文件的大小(以字节为单位),权限,用户,组信息,时间元数据。与问题最相关的是它还包括十个指向磁盘块的指针,我们将使用这些指针来引用实际文件的内容! 23 | 24 | ```c 25 | struct inode { 26 | int[10] directblocks; // indices for the block array i.e. where to the find the file's content 27 | long size; 28 | // ... standard inode meta-data e.g. 29 | int mode, userid,groupid; 30 | time_t ctime,atime,mtime; 31 | } 32 | ``` 33 | 34 | 现在我们可以弄清楚如何读取文件偏移量`position`的字节: 35 | 36 | ```c 37 | char readbyte(inode*inode,long position) { 38 | if(position <0 || position >= inode->size) return -1; // invalid offset 39 | 40 | int block_count = position / 4096,offset = position % 4096; 41 | 42 | // block count better be 0..9 ! 43 | int physical_idx = lookup_physical_block_index(inode, block_count ); 44 | 45 | // sanity check that the disk block index is reasonable... 46 | assert(physical_idx >=0 && physical_idx < MAX_BLOCK); 47 | 48 | // read the disk block from our virtual disk 'blocks' and return the specific byte 49 | return blocks[physical_idx][offset]; 50 | } 51 | ``` 52 | 53 | 我们的 lookup_physical_block 的初始版本很简单 - 我们可以使用 10 个direct blocks! 54 | 55 | ```c 56 | int lookup_physical_block_index(inode*inode, int block_count) { 57 | assert(block_count>=0 && block_count < 10); 58 | 59 | return inode->directblocks[ block_count ]; // returns an index value between [0,MAX_BLOCK) 60 | } 61 | ``` 62 | 63 | 这种简单的表示是合理的,只要我们可以用十个块表示所有可能的文件,即最多 40KB。大文件怎么办?我们需要 inode 结构始终具有相同的大小,因此将现有的直接块数组增加到20,将大约是我们的 inode 大小的两倍。如果我们的大多数文件需要少于 10 个块,那么我们的 inode 存储现在是浪费的。为了解决这个问题,我们将使用一个称为间接块的磁盘块来扩展我们可支配的指针数组。对于> 40KB 的文件,我们只需要这个间接块的一个指针,而不是所有的直接块。 64 | 65 | ```c 66 | struct inode { 67 | int[10] directblocks; // if size<4KB then only the first one is valid 68 | int indirectblock; // valid value when size >= 40KB 69 | int size; 70 | ... 71 | } 72 | ``` 73 | 74 | 间接块只是一个 4096 字节的常规磁盘块,但我们将使用它来保存指向磁盘块的指针。在这种情况下我们的指针只是整数,所以我们需要将指针强制转换为整数指针: 75 | 76 | ```c 77 | int lookup_physical_block_index(inode*inode, int block_count) { 78 | assert(sizeof(int)==4); // Warning this code assumes an index is 4 bytes! 79 | assert(block_count>=0 && block_count < 1024 + 10); // 0 <= block_count< 1034 80 | 81 | if( block_count < 10) 82 | return inode->directblocks[ block_count ]; 83 | 84 | // read the indirect block from disk: 85 | block_t* oneblock = & blocks[ inode->indirectblock ]; 86 | 87 | // Treat the 4KB as an array of 1024 pointers to other disk blocks 88 | int* table = (int*) oneblock; 89 | 90 | // Look up the correct entry in the table 91 | // Offset by 10 because the first 10 blocks of data are already 92 | // accounted for 93 | return table[ block_count - 10 ]; 94 | } 95 | ``` 96 | 97 | 对于典型的文件系统,我们的索引值是32位,即4个字节。因此,在 4096 字节中,我们可以存储 4096/4 = 1024 个条目。这意味着我们的间接块可以引用 1024 * 4KB = 4MB 的数据。通过前十个直接块,我们可以容纳最大 40KB + 1024 * 4KB = 4136KB 的文件。对于小于此值的文件,某些后续表条目可能无效。 98 | 99 | 对于更大的文件,我们可以使用两个间接块。然而,有一个更好的选择,这将允许我们有效地扩展到大型文件。我们将包含一个双间接指针,如果这还不够,那么就上三重间接指针。双重间接指针意味着我们有一个 1024 个条目表到磁盘块,用作 1024 个条目。这意味着我们可以参考 1024 * 1024 个磁盘数据块。 100 | 101 | ![inode disk blocks for data](img/c012049198839822b4b9b3716bf1ddff.jpg) 102 | 103 | (来源: [http://uw714doc.sco.com/en/FS_admin/graphics/s5chain.gif](http://uw714doc.sco.com/en/FS_admin/graphics/s5chain.gif) ) 104 | 105 | ```c 106 | int lookup_physical_block_index(inode*inode, int block_count) { 107 | if( block_count < 10) 108 | return inode->directblocks[ block_count ]; 109 | 110 | // Use indirect block for the next 1024 blocks: 111 | // Assumes 1024 ints can fit inside each block! 112 | if( block_count < 1024 + 10) { 113 | int* table = (int*) & blocks[ inode->indirectblock ]; 114 | return table[ block_count - 10 ]; 115 | } 116 | // For huge files we will use a table of tables 117 | int i = (block_count - 1034) / 1024 , j = (block_count - 1034) % 1024; 118 | assert(i<1024); // triple-indirect is not implemented here! 119 | 120 | int* table1 = (int*) & blocks[ inode->doubleindirectblock ]; 121 | // The first table tells us where to read the second table ... 122 | int* table2 = (int*) & blocks[ table1[i] ]; 123 | return table2[j]; 124 | 125 | // For gigantic files we will need to implement triple-indirect (table of tables of tables) 126 | } 127 | ``` 128 | 129 | 请注意,使用 double indirect 读取字节需要 3 个磁盘块读取(两个表和实际数据块)。 -------------------------------------------------------------------------------- /docs/74.md: -------------------------------------------------------------------------------- 1 | # 文件系统复习题 2 | 3 | > 原文: 4 | 5 | ## 主题 6 | 7 | * 超级块 8 | * 数据块 9 | * 索引节点 10 | * 相对路径 11 | * 文件元数据 12 | * 硬链接和软链接 13 | * 权限位 14 | * 正确使用目录 15 | * 虚拟文件系统 16 | * 可靠的文件系统 17 | * RAID 18 | 19 | ## 问题 20 | 21 | * 在 15 个 Direct 块,2 个双层,3 个三重间接块,4kb 块和 4 个字节条目的文件系统上文件有多大? (假设有足够的无限块) 22 | * 什么是超级块?索引节点?数据块? 23 | * 如何简化`/./proc/../dev/./random`这个相对路径? 24 | * 在 ext2 中,存储在 inode 中的内容以及存储在目录条目中的内容是什么? 25 | * 什么是/ sys,/ proc,/ dev / random 和/ dev / urandom? 26 | * 什么是权限位? 27 | * 如何使用 chmod 设置当前用户/当前组/其它用户的读/写/执行权限? 28 | * “dd”命令有什么作用? 29 | * 硬链接和符号链接有什么区别?是否需要存在? 30 | * “ls -l”显示目录中每个文件的大小。大小是存储在目录中还是存储在文件的 inode 中? -------------------------------------------------------------------------------- /docs/75.md: -------------------------------------------------------------------------------- 1 | # 10.信号 -------------------------------------------------------------------------------- /docs/76.md: -------------------------------------------------------------------------------- 1 | # 过程控制,第 1 部分:使用信号等待宏 2 | 3 | > 原文: 4 | 5 | ## 等待宏 6 | 7 | ## 我可以找出孩子的退出价值吗? 8 | 9 | 您可以找到子项退出值的最低 8 位(`main()`的返回值或`exit()`中包含的值):使用“等待宏” - 通常您将使用“WIFEXITED”和“WEXITSTATUS”。有关更多信息,请参见`wait` / `waitpid`手册页。 10 | 11 | ```c 12 | int status; 13 | pid_t child = fork(); 14 | if (child == -1) return 1; //Failed 15 | if (child > 0) { /* I am the parent - wait for the child to finish */ 16 | pid_t pid = waitpid(child, &status, 0); 17 | if (pid != -1 && WIFEXITED(status)) { 18 | int low8bits = WEXITSTATUS(status); 19 | printf("Process %d returned %d" , pid, low8bits); 20 | } 21 | } else { /* I am the child */ 22 | // do something interesting 23 | execl("/bin/ls", "/bin/ls", ".", (char *) NULL); // "ls ." 24 | } 25 | ``` 26 | 27 | 一个进程只能有 256 个返回值,其余的位是信息性的。 28 | 29 | ## 位移 30 | 31 | 请注意,无需记住这一点,这只是对状态变量中信息存储方式的高级概述 32 | 33 | [Android 源代码](https://android.googlesource.com/platform/prebuilts/gcc/linuxx86/host/i686-linux-glibc2.7-%0A4.6/+/tools_r20/sysroot/usr/include/bits/waitstatus.h) 34 | 35 | / *如果是 WIFEXITED(STATUS),则为低位 8 位的状态。 * / 36 | 37 | #define __WEXITSTATUS(status)(((状态)&amp; 0xff00)&gt;&gt; 8) 38 | 39 | / *如果 WIFSIGNALED(STATUS),终止信号。 * / 40 | 41 | #define __WTERMSIG(status)((status)&amp; 0x7f) 42 | 43 | / *如果 WIFSTOPPED(STATUS),停止孩子的信号。 * / 44 | 45 | #define __WSTOPSIG(status)__ WEXITSTATUS(status) 46 | 47 | / *非零,如果 STATUS 表示正常终止。 * / 48 | 49 | #define __WIFEXITED(status)(__ WTERMSIG(status)== 0) 50 | 51 | 内核具有跟踪信号,退出或停止的内部方式。该 API 是抽象的,以便内核开发人员可以随意更改。 52 | 53 | ## 小心。 54 | 55 | 请记住,只有满足前提条件时,宏才有意义。这意味着如果发出进程信号,则不会定义进程的退出状态。宏不会为你做检查,所以由编程来确保逻辑检出。 56 | 57 | ## 信号 58 | 59 | ## 什么是信号? 60 | 61 | 信号是内核提供给我们的构造。它允许一个进程异步地向另一个进程发送信号(思考消息)。如果该过程想要接受该信号,则它可以,然后,对于大多数信号,可以决定如何处理该信号。这是一个简短的列表(非全面的)信号。 62 | 63 | | 名称 | 默认操作 | 常用用例 | 64 | | --- | --- | --- | 65 | | SIGINT | 终止进程(可以捕获) | 告诉过程好好停止 | 66 | | SIGQUIT | Terminate Process (Can be caught) | 告诉过程严厉阻止 | 67 | | SIGSTOP | 停止进程(无法捕获) | 停止继续进程 | 68 | | SIGCONT | 继续一个过程 | 继续运行进程 | 69 | | SIGKILL | 终止进程(不能忽略) | 你希望你的过程消失了 | 70 | 71 | ## 我可以暂停我的孩子吗? 72 | 73 | 是的!您可以通过发送 SIGSTOP 信号暂时暂停正在运行的进程。如果成功,它将冻结一个过程;即,该进程将不再分配 CPU 时间。 74 | 75 | 要允许进程恢复执行,请发送 SIGCONT 信号。 76 | 77 | 例如,这是一个程序,每秒慢慢打印一个点,最多 59 点。 78 | 79 | ```c 80 | #include 81 | #include 82 | int main() { 83 | printf("My pid is %d\n", getpid() ); 84 | int i = 60; 85 | while(--i) { 86 | write(1, ".",1); 87 | sleep(1); 88 | } 89 | write(1, "Done!",5); 90 | return 0; 91 | } 92 | ``` 93 | 94 | 我们将首先在后台启动该过程(注意&amp; at end)。然后使用 kill 命令从 shell 进程发送一个信号。 95 | 96 | ``` 97 | >./program & 98 | My pid is 403 99 | ... 100 | >kill -SIGSTOP 403 101 | >kill -SIGCONT 403 102 | ``` 103 | 104 | ## 如何从 C 中杀死/停止/暂停我的孩子? 105 | 106 | 在 C 中,使用`kill` POSIX 调用向孩子发送信号, 107 | 108 | ```c 109 | kill(child, SIGUSR1); // Send a user-defined signal 110 | kill(child, SIGSTOP); // Stop the child process (the child cannot prevent this) 111 | kill(child, SIGTERM); // Terminate the child process (the child can prevent this) 112 | kill(child, SIGINT); // Equivalent to CTRL-C (by default closes the process) 113 | ``` 114 | 115 | 如上所述,shell 中还有一个 kill 命令,例如获取正在运行的进程列表,然后终止进程 45 和进程 46 116 | 117 | ``` 118 | ps 119 | kill -l 120 | kill -9 45 121 | kill -s TERM 46 122 | ``` 123 | 124 | ## 如何检测“CTRL-C”并正常清理? 125 | 126 | 我们稍后会回到信号 - 这只是一个简短的介绍。在 Linux 系统上,如果您有兴趣了解更多内容,请参阅`man -s7 signal`(例如,异步信号安全的系统和库调用列表。 127 | 128 | 信号处理程序中的可执行代码有严格的限制。大多数库和系统调用都不是“异步信号安全” - 它们可能不会在信号处理程序中使用,因为它们不是可重入的安全。在单线程程序中,信号处理会暂时中断程序执行,以执行信号处理程序代码。假设您的原始程序在执行`malloc`的库代码时被中断; malloc 使用的内存结构不会处于一致状态。调用`printf`(使用`malloc`)作为信号处理程序的一部分是不安全的,并且将导致“未定义的行为”,即它不再是有用的,可预测的程序。在实践中,您的程序可能会崩溃,计算或生成不正确的结果或停止运行(“死锁”),具体取决于您的程序在执行信号处理程序代码时被中断时的确切执行情况。 129 | 130 | 信号处理程序的一个常见用途是设置一个布尔标志,偶尔轮询(读取)作为程序正常运行的一部分。例如, 131 | 132 | ```c 133 | int pleaseStop ; // See notes on why "volatile sig_atomic_t" is better 134 | 135 | void handle_sigint(int signal) { 136 | pleaseStop = 1; 137 | } 138 | 139 | int main() { 140 | signal(SIGINT, handle_sigint); 141 | pleaseStop = 0; 142 | while ( ! pleaseStop) { 143 | /* application logic here */ 144 | } 145 | /* cleanup code here */ 146 | } 147 | ``` 148 | 149 | 上面的代码似乎在纸上是正确的。但是,我们需要为编译器和将执行`main()`循环的 CPU 内核提供提示。我们需要阻止编译器优化:表达式`! pleaseStop`似乎是一个循环不变量,即永远为真,因此可以简化为`true`。其次,我们需要确保`pleaseStop`的值不使用 CPU 寄存器进行高速缓存,而是始终读取和写入主存储器。 `sig_atomic_t`类型意味着变量的所有位都可以作为“原子操作”读取或修改 - 单个不间断操作。不可能读取由一些新位值和旧位值组成的值。 150 | 151 | 通过使用正确的类型`volatile sig_atomic_t`指定`pleaseStop`,我们可以编写可移植代码,其中主循环将在信号处理程序返回后退出。在大多数现代平台上,`sig_atomic_t`类型可以与`int`一样大,但在嵌入式系统上可以与`char`一样小,并且只能表示(-127 到 127)值。 152 | 153 | ```c 154 | volatile sig_atomic_t pleaseStop; 155 | ``` 156 | 157 | 这种模式的两个例子可以在“COMP”基于终端的 1Hz 4bit 计算机中找到( [https://github.com/gto76/comp-cpp/blob/1bf9a77eaf8f57f7358a316e5bbada97f2dc8987/src/output.c#L121](https://github.com/gto76/comp-cpp/blob/1bf9a77eaf8f57f7358a316e5bbada97f2dc8987/src/output.c#L121) )。使用两个布尔标志。一个标记`SIGINT`(CTRL-C)的传送,并正常关闭程序,另一个标记`SIGWINCH`信号以检测终端调整大小并重绘整个显示。 -------------------------------------------------------------------------------- /docs/77.md: -------------------------------------------------------------------------------- 1 | # 信号,第 2 部分:待处理的信号和信号掩码 2 | 3 | > 原文: 4 | 5 | ## 信号深度 6 | 7 | ## 如何了解有关信号的更多信息? 8 | 9 | linux 手册页讨论了第 2 节中的信号系统调用。第 7 节中还有一篇较长的文章(尽管不在 OSX / BSD 中): 10 | 11 | ``` 12 | man -s7 signal 13 | ``` 14 | 15 | ## 信号术语 16 | 17 | * 生成 - 通过 kill 系统调用在内核中创建信号。 18 | * 待定 - 尚未交付但很快将交付 19 | * 已阻止 - 未传送,因为没有信号处理可以传送信号 20 | * 交付 - 交付过程,正在采取所述行动 21 | * 抓住 - 当进程停止信号来摧毁它并用它做其他事情时 22 | 23 | ## 什么是进程的信号处理? 24 | 25 | 对于每个过程,每个信号都有一个配置,这意味着当信号传递给过程时会发生什么动作。例如,默认处置 SIGINT 是终止它。信号处理可以通过调用 signal()来改变(这是简单但不可移植的,因为它在不同的 POSIX 架构上的实现有细微的变化,也不推荐用于多线程程序)或`sigaction`(稍后讨论)。您可以将过程对所有可能信号的处置想象为函数指针条目表(每个可能的信号一个)。 26 | 27 | 信号的默认处置可以是忽略信号,停止进程,继续停止进程,终止进程,或终止进程并转储'核心'文件。请注意,核心文件是可以使用调试器检查的进程内存状态的表示。 28 | 29 | ## 多个信号可以排队吗? 30 | 31 | 否 - 但是可能有信号处于挂起状态。如果信号处于待处理状态,则表示尚未交付给该进程。信号待决的最常见原因是进程(或线程)当前阻止了该特定信号。 32 | 33 | 如果是特定信号,例如 SIGINT 正在等待,因此无法再次排队相同的信号。 34 | 35 | 可能在待处理状态下具有多个不同类型的信号。例如,SIGINT 和 SIGTERM 信号可能正在等待(即尚未传送到目标进程) 36 | 37 | ## 如何阻止信号? 38 | 39 | 通过设置过程信号掩码,或者在编写多线程程序时,可以阻止信号(意味着它们将保持在挂起状态),线程信号掩码。 40 | 41 | ## 线程/子项中的处置 42 | 43 | ## 创建新线程时会发生什么? 44 | 45 | 新线程继承了调用线程掩码的副本 46 | 47 | ```c 48 | pthread_sigmask( ... ); // set my mask to block delivery of some signals 49 | pthread_create( ... ); // new thread will start with a copy of the same mask 50 | ``` 51 | 52 | ## 分叉时会发生什么? 53 | 54 | 子进程继承父进程信号处理的副本。换句话说,如果您在分叉之前安装了 SIGINT 处理程序,那么如果将 SIGINT 传递给子进程,子进程也将调用处理程序。 55 | 56 | 注意孩子的待处理信号是 _ 而不是 _ 在分叉期间继承的。 57 | 58 | ## exec 期间会发生什么? 59 | 60 | 信号掩码和信号配置都转移到执行程序。 [https://www.gnu.org/software/libc/manual/html_node/Executing-a-File.html#Executing-a-File](Source) 也会保留待处理信号。信号处理程序被重置,因为原始处理程序代码与旧进程一起消失。 61 | 62 | ## 叉期间会发生什么? 63 | 64 | 子进程继承父进程的信号处理副本和父进程信号掩码的副本。 65 | 66 | 例如,如果在父母中阻止`SIGINT`,它也将在孩子中被阻止。例如,如果父级为 SIG-INT 安装了处理程序(回调函数),则子级也将执行相同的行为。 67 | 68 | 然而,待定信号不是由孩子继承的。 69 | 70 | ## 如何在单线程程序中阻止信号? 71 | 72 | 使用`sigprocmask`!使用 sigprocmask,您可以设置新掩码,将要阻止的新信号添加到进程掩码,以及取消阻止当前阻塞的信号。您还可以通过传入 oldset 的非 null 值来确定现有掩码(并在以后使用它)。 73 | 74 | ``` 75 | int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);` 76 | ``` 77 | 78 | 从 sigprocmask 的 Linux 手册页, 79 | 80 | ``` 81 | SIG_BLOCK: The set of blocked signals is the union of the current set and the set argument. 82 | SIG_UNBLOCK: The signals in set are removed from the current set of blocked signals. It is permissible to attempt to unblock a signal which is not blocked. 83 | SIG_SETMASK: The set of blocked signals is set to the argument set. 84 | ``` 85 | 86 | sigset 类型表现为位图,除了使用函数而不是使用&amp;显式设置和取消设置位。和|。 87 | 88 | 在修改一位之前忘记初始化信号集是一个常见的错误。例如, 89 | 90 | ```c 91 | sigset_t set, oldset; 92 | sigaddset(&set, SIGINT); // Ooops! 93 | sigprocmask(SIG_SETMASK, &set, &oldset) 94 | ``` 95 | 96 | 正确的代码将该集初始化为全部打开或全部关闭。例如, 97 | 98 | ```c 99 | sigfillset(&set); // all signals 100 | sigprocmask(SIG_SETMASK, &set, NULL); // Block all the signals! 101 | // (Actually SIGKILL or SIGSTOP cannot be blocked...) 102 | 103 | sigemptyset(&set); // no signals 104 | sigprocmask(SIG_SETMASK, &set, NULL); // set the mask to be empty again 105 | ``` 106 | 107 | ## 如何在多线程程序中阻止信号? 108 | 109 | 多线程程序中的阻塞信号类似于单线程程序: 110 | 111 | * 使用 pthread_sigmask 而不是 sigprocmask 112 | * 阻止所有线程中的信号以防止其异步传递 113 | 114 | 确保信号在所有线程中被阻止的最简单方法是在创建新线程之前在主线程中设置信号掩码 115 | 116 | ```c 117 | sigemptyset(&set); 118 | sigaddset(&set, SIGQUIT); 119 | sigaddset(&set, SIGINT); 120 | pthread_sigmask(SIG_BLOCK, &set, NULL); 121 | 122 | // this thread and the new thread will block SIGQUIT and SIGINT 123 | pthread_create(&thread_id, NULL, myfunc, funcparam); 124 | ``` 125 | 126 | 正如我们在 sigprocmask 中看到的那样,pthread_sigmask 包含一个'how'参数,用于定义如何使用信号集: 127 | 128 | ```c 129 | pthread_sigmask(SIG_SETMASK, &set, NULL) - replace the thread's mask with given signal set 130 | pthread_sigmask(SIG_BLOCK, &set, NULL) - add the signal set to the thread's mask 131 | pthread_sigmask(SIG_UNBLOCK, &set, NULL) - remove the signal set from the thread's mask 132 | ``` 133 | 134 | ## 如何在多线程程序中提供待处理信号? 135 | 136 | 信号被传送到任何未阻塞该信号的信号线程。 137 | 138 | 如果两个或多个线程可以接收信号,那么哪个线程将被中断是任意的! -------------------------------------------------------------------------------- /docs/78.md: -------------------------------------------------------------------------------- 1 | # 信号,第 3 部分:提高信号 2 | 3 | > 原文: 4 | 5 | ## 如何从 shell 向进程发送信号? 6 | 7 | 您已经知道发送`SIG_INT`的一种方法只需键入`CTRL-C`从 shell 中可以使用`kill`(如果您知道进程 ID)和`killall`(如果您知道进程名称) 8 | 9 | ``` 10 | # First let's use ps and grep to find the process we want to send a signal to 11 | $ ps au | grep myprogram 12 | angrave 4409 0.0 0.0 2434892 512 s004 R+ 2:42PM 0:00.00 myprogram 1 2 3 13 | 14 | #Send SIGINT signal to process 4409 (equivalent of `CTRL-C`) 15 | $ kill -SIGINT 4409 16 | 17 | #Send SIGKILL (terminate the process) 18 | $ kill -SIGKILL 4409 19 | $ kill -9 4409 20 | ``` 21 | 22 | `killall`类似,只是它与程序名称匹配。接下来的两个例子,发送`SIGINT`然后发送`SIGKILL`来终止正在运行的进程`myprogram` 23 | 24 | ``` 25 | # Send SIGINT (SIGINT can be ignored) 26 | $ killall -SIGINT myprogram 27 | 28 | # SIGKILL (-9) cannot be ignored! 29 | $ killall -9 myprogram 30 | ``` 31 | 32 | ## 如何从正在运行的 C 程序向进程发送信号? 33 | 34 | 使用`raise`或`kill` 35 | 36 | ```c 37 | int raise(int sig); // Send a signal to myself! 38 | int kill(pid_t pid, int sig); // Send a signal to another process 39 | ``` 40 | 41 | 对于非 root 进程,信号只能发送给同一用户的进程,即你不能只是 SIGKILL 我的进程!有关详细信息,请参阅 kill(2),即 man -s2。 42 | 43 | ## 如何向特定线程发送信号? 44 | 45 | 使用`pthread_kill` 46 | 47 | ```c 48 | int pthread_kill(pthread_t thread, int sig) 49 | ``` 50 | 51 | 在下面的示例中,执行`func`的新创建的线程将被`SIGINT`中断 52 | 53 | ```c 54 | pthread_create(&tid, NULL, func, args); 55 | pthread_kill(tid, SIGINT); 56 | pthread_kill(pthread_self(), SIGKILL); // send SIGKILL to myself 57 | ``` 58 | 59 | ## 将`pthread_kill( threadid, SIGKILL)`杀死进程或线程吗? 60 | 61 | 它会杀死整个过程。虽然各个线程可以设置信号掩码,但信号配置(每个信号执行的处理程序/动作表)是 _ 每个进程 _ 而不是 _ 每个线程 _。这意味着可以从任何线程调用`sigaction`,因为您将为进程中的所有线程设置信号处理程序。 62 | 63 | ## 我如何捕获(处理)信号? 64 | 65 | 您可以异步或同步选择句柄待处理信号。 66 | 67 | 安装信号处理程序以异步处理信号使用`sigaction`(或者,对于简单的例子,`signal`)。 68 | 69 | 要同步捕获待处理信号,请使用`sigwait`(阻塞直到发送信号)或`signalfd`(它还会阻塞并提供可以`read()`检索待处理信号的文件描述符)。 70 | 71 | 有关使用`sigwait`的示例,请参阅`Signals, Part 4` -------------------------------------------------------------------------------- /docs/79.md: -------------------------------------------------------------------------------- 1 | # 信号,第 4 部分:信号 2 | 3 | > 原文: 4 | 5 | ## 我如何以及为何使用`sigaction`? 6 | 7 | 您应该使用`sigaction`而不是`signal`,因为它具有更好的定义语义。 `signal`在不同的操作系统上做了不同的事情,**坏** `sigaction`更便携,如果需要更好地为线程定义。 8 | 9 | 要改变过程的“信号处理” - 即当信号传递到您的过程时会发生什么 - 使用`sigaction` 10 | 11 | 您可以使用系统调用`sigaction`来设置信号的当前处理程序,或者读取特定信号的当前信号处理程序。 12 | 13 | ```c 14 | int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); 15 | ``` 16 | 17 | sigaction 结构包括两个回调函数(我们只会查看'handler'版本),一个信号掩码和一个 flags 字段 - 18 | 19 | ```c 20 | struct sigaction { 21 | void (*sa_handler)(int); 22 | void (*sa_sigaction)(int, siginfo_t *, void *); 23 | sigset_t sa_mask; 24 | int sa_flags; 25 | }; 26 | ``` 27 | 28 | ## 如何将`signal`调用转换为等效的`sigaction`调用? 29 | 30 | 假设您为警报信号安装了信号处理程序, 31 | 32 | ```c 33 | signal(SIGALRM, myhandler); 34 | ``` 35 | 36 | 等效的`sigaction`代码是: 37 | 38 | ```c 39 | struct sigaction sa; 40 | sa.sa_handler = myhandler; 41 | sigemptyset(&sa.sa_mask); 42 | sa.sa_flags = 0; 43 | sigaction(SIGALRM, &sa, NULL) 44 | ``` 45 | 46 | 但是,我们通常也可以设置掩码和标志字段。掩码是在信号处理程序执行期间使用的临时信号掩码。 SA_RESTART 标志将自动重启一些(但不是全部)系统调用,否则这些调用将提前返回(带有 EINTR 错误)。后者意味着我们可以稍微简化代码的其余部分,因为可能不再需要重启循环。 47 | 48 | ```c 49 | sigfillset(&sa.sa_mask); 50 | sa.sa_flags = SA_RESTART; /* Restart functions if interrupted by handler */ 51 | ``` 52 | 53 | ## 我如何使用 sigwait? 54 | 55 | Sigwait 可用于一次读取一个待处理信号。 `sigwait`用于同​​步等待信号,而不是在回调中处理它们。下面显示了多线程程序中 sigwait 的典型用法。请注意,首先设置线程信号掩码(并且将由新线程继承)。这可以防止信号被 _ 传递 _,因此它们将保持挂起状态,直到调用 sigwait。还要注意 sigwait 使用相同的 set sigset_t 变量 - 除了设置阻塞信号集之外,它被用作 sigwait 可以捕获和返回的信号集。 56 | 57 | 编写自定义信号处理线程(例如下面的示例)而不是回调函数的一个优点是,您现在可以使用更多的 C 库和系统函数,否则这些函数无法在信号处理程序中安全使用,因为它们不是异步的信号安全。 58 | 59 | 基于`http://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_sigmask.html` 60 | 61 | ```c 62 | static sigset_t signal_mask; /* signals to block */ 63 | 64 | int main (int argc, char *argv[]) 65 | { 66 | pthread_t sig_thr_id; /* signal handler thread ID */ 67 | sigemptyset (&signal_mask); 68 | sigaddset (&signal_mask, SIGINT); 69 | sigaddset (&signal_mask, SIGTERM); 70 | pthread_sigmask (SIG_BLOCK, &signal_mask, NULL); 71 | 72 | /* New threads will inherit this thread's mask */ 73 | pthread_create (&sig_thr_id, NULL, signal_thread, NULL); 74 | 75 | /* APPLICATION CODE */ 76 | ... 77 | } 78 | 79 | void *signal_thread (void *arg) 80 | { 81 | int sig_caught; /* signal caught */ 82 | 83 | /* Use same mask as the set of signals that we'd like to know about! */ 84 | sigwait(&signal_mask, &sig_caught); 85 | switch (sig_caught) 86 | { 87 | case SIGINT: /* process SIGINT */ 88 | ... 89 | break; 90 | case SIGTERM: /* process SIGTERM */ 91 | ... 92 | break; 93 | default: /* should normally not happen */ 94 | fprintf (stderr, "\nUnexpected signal %d\n", sig_caught); 95 | break; 96 | } 97 | } 98 | ``` -------------------------------------------------------------------------------- /docs/80.md: -------------------------------------------------------------------------------- 1 | # 信号复习题 2 | 3 | > 原文: 4 | 5 | ## 话题 6 | 7 | * 信号 8 | * 信号处理程序安全 9 | * 信号处理 10 | * 信号状态 11 | * 分叉/执行时待发信号 12 | * 分叉/执行时的信号处理 13 | * 在 C 中提高信号 14 | * 在多线程程序中提升信号 15 | 16 | ## 问题 17 | 18 | * 什么是信号? 19 | * 如何在 UNIX 下提供信号? (额外奖励:Windows 怎么样?) 20 | * 函数是信号处理程序安全的意思是什么 21 | * 什么是过程信号处理? 22 | * 如何在单线程程序中更改信号处理?多线程怎么样? 23 | * 为什么 sigaction vs 信号? 24 | * 我如何异步并同步捕获信号? 25 | * 我分叉后挂起信号会发生什么? Exec 的? 26 | * 我分叉后,我的信号处理会发生什么? Exec 的? -------------------------------------------------------------------------------- /docs/81.md: -------------------------------------------------------------------------------- 1 | # 考试练习题 2 | 3 | 警告:这些是良好的练习,但不全面。 CS241 期末考试假定您完全理解并可以应用课程的所有主题。问题将主要但不完全关注您在实验和编程任务中使用的主题。 -------------------------------------------------------------------------------- /docs/82.md: -------------------------------------------------------------------------------- 1 | # 考试主题 2 | 3 | > 原文: 4 | 5 | 期末考试可能包括多项选择题,可以测试您对以下内容的掌握程度。 6 | 7 | ``` 8 | CSP (critical section problems) 9 | HTTP 10 | SIGINT 11 | TCP 12 | TLB 13 | Virtual Memory 14 | arrays 15 | barrier 16 | c strings 17 | chmod 18 | client/server 19 | coffman conditions 20 | condition variables 21 | context switch 22 | deadlock 23 | dining philosophers 24 | epoll 25 | exit 26 | file I/O 27 | file system representation 28 | fork/exec/wait 29 | fprintf 30 | free 31 | heap allocator 32 | heap/stack 33 | inode vs name 34 | malloc 35 | mkfifo 36 | mmap 37 | mutexes 38 | network ports 39 | open/close 40 | operating system terms 41 | page fault 42 | page tables 43 | pipes 44 | pointer arithmetic 45 | pointers 46 | printing (printf) 47 | producer/consumer 48 | progress/mutex 49 | race conditions 50 | read/write 51 | reader/writer 52 | resource allocation graphs 53 | ring buffer 54 | scanf 55 | buffering 56 | scheduling 57 | select 58 | semaphores 59 | signals 60 | sizeof 61 | stat 62 | stderr/stdout 63 | symlinks 64 | thread control (_create, _join, _exit) 65 | variable initializers 66 | variable scope 67 | vm thrashing 68 | wait macros 69 | write/read with errno, EINTR and partial data 70 | ``` -------------------------------------------------------------------------------- /docs/83.md: -------------------------------------------------------------------------------- 1 | # C 编程:复习题 2 | 3 | > 原文: 4 | 5 | ## 警告 - 问题编号可能会有变化 6 | 7 | ## 记忆和字符串 8 | 9 | ## Q1.1 10 | 11 | 在下面的例子中,哪些变量可以保证打印零值? 12 | 13 | ```c 14 | int a; 15 | static int b; 16 | 17 | void func() { 18 | static int c; 19 | int d; 20 | printf("%d %d %d %d\n",a,b,c,d); 21 | } 22 | ``` 23 | 24 | ## 问题 1.2 25 | 26 | In the example below, which variables are guaranteed to print the value of zero? 27 | 28 | ```c 29 | void func() { 30 | int* ptr1 = malloc( sizeof(int) ); 31 | int* ptr2 = realloc(NULL, sizeof(int) ); 32 | int* ptr3 = calloc( 1, sizeof(int) ); 33 | int* ptr4 = calloc( sizeof(int) , 1); 34 | 35 | printf("%d %d %d %d\n",*ptr1,*ptr2,*ptr3,*ptr4); 36 | } 37 | ``` 38 | 39 | ## 问 1.3 40 | 41 | 在以下尝试复制字符串时解释错误。 42 | 43 | ```c 44 | char* copy(char*src) { 45 | char*result = malloc( strlen(src) ); 46 | strcpy(result, src); 47 | return result; 48 | } 49 | ``` 50 | 51 | ## 问题 1.4 52 | 53 | 为什么以下尝试复制字符串有时会起作用,有时会失败? 54 | 55 | ```c 56 | char* copy(char*src) { 57 | char*result = malloc( strlen(src) +1 ); 58 | strcat(result, src); 59 | return result; 60 | } 61 | ``` 62 | 63 | ## Q 1.4 64 | 65 | 解释以下代码中尝试复制字符串的两个错误。 66 | 67 | ```c 68 | char* copy(char*src) { 69 | char result[sizeof(src)]; 70 | strcpy(result, src); 71 | return result; 72 | } 73 | ``` 74 | 75 | ## 问 1.5 76 | 77 | 以下哪项是合法的? 78 | 79 | ```c 80 | char a[] = "Hello"; strcpy(a, "World"); 81 | char b[] = "Hello"; strcpy(b, "World12345", b); 82 | char* c = "Hello"; strcpy(c, "World"); 83 | ``` 84 | 85 | ## 问题 1.6 86 | 87 | 完成函数指针 typedef 以声明一个指向函数的指针,该函数接受 void *参数并返回 void *。将您的类型命名为“pthread_callback” 88 | 89 | ```c 90 | typedef ______________________; 91 | ``` 92 | 93 | ## 问 1.7 94 | 95 | 除了函数参数之外还有哪些东西存储在线程的栈中? 96 | 97 | ## 问题 1.8 98 | 99 | 仅使用`strcpy` `strlen`和指针算法实现`char* strcat(char*dest, const char*src)`的版本 100 | 101 | ```c 102 | char* mystrcat(char*dest, const char*src) { 103 | 104 | ? Use strcpy strlen here 105 | 106 | return dest; 107 | } 108 | ``` 109 | 110 | ## 问题 1.9 111 | 112 | 使用循环并且没有函数调用来实现 size_t strlen(const char *)的版本。 113 | 114 | ```c 115 | size_t mystrlen(const char*s) { 116 | 117 | } 118 | ``` 119 | 120 | ## 问题 1.10 121 | 122 | 确定以下`strcpy`实现中的三个错误。 123 | 124 | ```c 125 | char* strcpy(const char* dest, const char* src) { 126 | while(*src) { *dest++ = *src++; } 127 | return dest; 128 | } 129 | ``` 130 | 131 | ## 印花 132 | 133 | ## 问 2.1 134 | 135 | 发现两个错误! 136 | 137 | ``` 138 | fprintf("You scored 100%"); 139 | ``` 140 | 141 | ## 格式化和打印到文件 142 | 143 | ## 问 3.1 144 | 145 | 完成以下代码以打印到文件。将名称,逗号和分数打印到文件'result.txt' 146 | 147 | ```c 148 | char* name = .....; 149 | int score = ...... 150 | FILE *f = fopen("result.txt",_____); 151 | if(f) { 152 | _____ 153 | } 154 | fclose(f); 155 | ``` 156 | 157 | ## 打印到字符串 158 | 159 | ## 问 4.1 160 | 161 | 如何将变量 a,mesg,val 和 ptr 的值打印到字符串?打印 a 为整数,mesg 为 C string,val 为 double val,ptr 为十六进制指针。您可以假设 mesg 指向短 C 字符串(<50 个字符)。额外奖励:您如何使此代码更强大或能够应对? 162 | 163 | ```c 164 | char* toString(int a, char*mesg, double val, void* ptr) { 165 | char* result = malloc( strlen(mesg) + 50); 166 | _____ 167 | return result; 168 | } 169 | ``` 170 | 171 | ## 输入解析 172 | 173 | ## 问 5.1 174 | 175 | 为什么要检查 sscanf 和 scanf 的返回值? 176 | 177 | ## 问 5.2 178 | 179 | 为什么“变得”危险? 180 | 181 | ## 问 5.3 182 | 183 | 编写一个使用`getline`的完整程序。确保您的程序没有内存泄漏。 184 | 185 | ## 堆内存 186 | 187 | 你什么时候使用 calloc 而不是 malloc?何时 realloc 会有用? 188 | 189 | (Todo - 把这个问题移到另一页)程序员在下面的代码中犯了什么错误?是否可以修复它 i)使用堆内存? ii)使用全局(静态)内存? 190 | 191 | ```c 192 | static int id; 193 | 194 | char* next_ticket() { 195 | id ++; 196 | char result[20]; 197 | sprintf(result,"%d",id); 198 | return result; 199 | } 200 | ``` -------------------------------------------------------------------------------- /docs/84.md: -------------------------------------------------------------------------------- 1 | # 多线程编程:复习题 2 | 3 | > 原文: 4 | 5 | > 警告 - 问题编号可能会有变化 6 | 7 | ## Q1 8 | 9 | 以下代码是否是线程安全的?重新设计以下代码是线程安全的。提示:如果消息内存对每个调用都是唯一的,则不需要互斥锁。 10 | 11 | ```c 12 | static char message[20]; 13 | pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 14 | 15 | void format(int v) { 16 | pthread_mutex_lock(&mutex); 17 | sprintf(message, ":%d:" ,v); 18 | pthread_mutex_unlock(&mutex); 19 | return message; 20 | } 21 | ``` 22 | 23 | ## Q2 24 | 25 | 以下哪一项不会导致进程退出? 26 | 27 | * 从最后一个运行线程中的 pthread 的启动函数返回。 28 | * 从 main 返回的原始线程。 29 | * 任何导致分段错误的线程。 30 | * 任何调用`exit`的线程。 31 | * 在主线程中调用`pthread_exit`,其他线程仍在运行。 32 | 33 | ## Q3 34 | 35 | 写下将由以下程序打印的“W”字符数的数学表达式。假设 a,b,c,d 是小的正整数。你的答案可能会使用'min'函数返回其最低值的参数。 36 | 37 | ```c 38 | unsigned int a=...,b=...,c=...,d=...; 39 | 40 | void* func(void* ptr) { 41 | char m = * (char*)ptr; 42 | if(m == 'P') sem_post(s); 43 | if(m == 'W') sem_wait(s); 44 | putchar(m); 45 | return NULL; 46 | } 47 | 48 | int main(int argv, char** argc) { 49 | sem_init(s,0, a); 50 | while(b--) pthread_create(&tid, NULL, func, "W"); 51 | while(c--) pthread_create(&tid, NULL, func, "P"); 52 | while(d--) pthread_create(&tid, NULL, func, "W"); 53 | pthread_exit(NULL); 54 | /*Process will finish when all threads have exited */ 55 | } 56 | ``` 57 | 58 | ## Q4 59 | 60 | 完成以下代码。以下代码应该打印交替`A`和`B`。它代表两个轮流执行的线程。将条件变量调用添加到`func`,以便等待的线程不需要连续检查`turn`变量。问:是否需要 pthread_cond_broadcast 或者 pthread_cond_signal 是否足够? 61 | 62 | ```c 63 | pthread_cond_t cv = PTHREAD_COND_INITIALIZER; 64 | pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; 65 | 66 | void* turn; 67 | 68 | void* func(void* mesg) { 69 | while(1) { 70 | // Add mutex lock and condition variable calls ... 71 | 72 | while(turn == mesg) { 73 | /* poll again ... Change me - This busy loop burns CPU time! */ 74 | } 75 | 76 | /* Do stuff on this thread */ 77 | puts( (char*) mesg); 78 | turn = mesg; 79 | 80 | } 81 | return 0; 82 | } 83 | 84 | int main(int argc, char** argv){ 85 | pthread_t tid1; 86 | pthread_create(&tid1, NULL, func, "A"); 87 | func("B"); // no need to create another thread - just use the main thread 88 | return 0; 89 | } 90 | ``` 91 | 92 | ## Q5 93 | 94 | 确定给定代码中的关键部分。添加互斥锁定以使代码线程安全。添加条件变量调用,以使`total`永远不会变为负数或高于 1000.相反,调用应该阻塞,直到可以继续进行。解释为什么`pthread_cond_broadcast`是必要的。 95 | 96 | ```c 97 | int total; 98 | void add(int value) { 99 | if(value < 1) return; 100 | total += value; 101 | } 102 | void sub(int value) { 103 | if(value < 1) return; 104 | total -= value; 105 | } 106 | ``` 107 | 108 | ## Q6 109 | 110 | 非线程安全数据结构具有`size()` `enq`和`deq`方法。使用条件变量和互斥锁来完成线程安全的阻塞版本。 111 | 112 | ```c 113 | void enqueue(void* data) { 114 | // should block if the size() would become greater than 256 115 | enq(data); 116 | } 117 | void* dequeue() { 118 | // should block if size() is 0 119 | return deq(); 120 | } 121 | ``` 122 | 123 | ## Q7 124 | 125 | 您的启动使用最新的交通信息提供路径规划。您的多付实习生创建了一个非线程安全的数据结构,其中包含两个函数:`shortest`(使用但不修改图形)和`set_edge`(修改图形)。 126 | 127 | ```c 128 | graph_t* create_graph(char* filename); // called once 129 | 130 | // returns a new heap object that is the shortest path from vertex i to j 131 | path_t* shortest(graph_t* graph, int i, int j); 132 | 133 | // updates edge from vertex i to j 134 | void set_edge(graph_t* graph, int i, int j, double time); 135 | 136 | ``` 137 | 138 | 为了提高性能,多个线程必须能够同时调用`shortest`,但是当`shortest`或`set_edge`内​​没有其他线程执行时,只能通过一个线程修改图形。 139 | 140 | 使用互斥锁和条件变量来实现读写器解决方案。不完整的尝试如下所示。虽然这种尝试是线程安全的(因此足以用于演示日!),但它不允许多个线程同时计算`shortest`路径并且没有足够的吞吐量。 141 | 142 | ```c 143 | path_t* shortest_safe(graph_t* graph, int i, int j) { 144 | pthread_mutex_lock(&m); 145 | path_t* path = shortest(graph, i, j); 146 | pthread_mutex_unlock(&m); 147 | return path; 148 | } 149 | void set_edge_safe(graph_t* graph, int i, int j, double dist) { 150 | pthread_mutex_lock(&m); 151 | set_edge(graph, i, j, dist); 152 | pthread_mutex_unlock(&m); 153 | } 154 | ``` -------------------------------------------------------------------------------- /docs/85.md: -------------------------------------------------------------------------------- 1 | # 同步概念:复习题 2 | 3 | > 原文: 4 | 5 | > 注意线程编程同步问题在单独的 Wiki 页面上。本页重点介绍概念主题。问题编号可能会有变化 6 | 7 | ## Q1 8 | 9 | 每个科夫曼条件意味着什么? (例如,你能提供每个的定义) 10 | 11 | * 等等 12 | * 循环等待 13 | * 没有先发制人 14 | * 相互排斥 15 | 16 | ## Q2 17 | 18 | 给出一个真实的例子,依次打破每个 Coffman 条件。需要考虑的情况:画家,油漆和油漆刷。等待和等待循环等待没有先发制人相互排斥 19 | 20 | ## Q3 21 | 22 | 识别用餐哲学家代码何时导致死锁(或不)。例如,如果您看到以下代码片段不满足 Coffman 条件? 23 | 24 | ``` 25 | // Get both locks or none. 26 | pthread_mutex_lock( a ); 27 | if( pthread_mutex_trylock( b ) ) { /*failed*/ 28 | pthread_mutex_unlock( a ); 29 | ... 30 | } 31 | ``` 32 | 33 | ## Q4 34 | 35 | 有多少进程被阻止? 36 | 37 | * P1 获得 R1 38 | * P2 获得 R2 39 | * P1 收购 R3 40 | * P2 等待 R3 41 | * P3 收购 R5 42 | * P1 收购 R4 43 | * P3 等待 R1 44 | * P4 等待 R5 45 | * P5 等待 R1 46 | 47 | ## Q5 48 | 49 | 对于读写器问题,下列陈述中有多少是正确的? 50 | 51 | * 可以有多个活跃的读者 52 | * 可以有多个活动作者 53 | * 当有活动的写入器时,活动读取器的数量必须为零 54 | * 如果有活动的阅读器,则活动写入器的数量必须为零 55 | * 作者必须等到当前活跃的读者完成 -------------------------------------------------------------------------------- /docs/86.md: -------------------------------------------------------------------------------- 1 | # 记忆:复习题 2 | 3 | > 原文: 4 | 5 | > 问题编号可能会有变化 6 | 7 | ## Q1 8 | 9 | 以下是什么,他们的目的是什么? 10 | 11 | * 翻译旁视缓冲区 12 | * 实际地址 13 | * 内存管理单元 14 | * 肮脏的一点 15 | 16 | ## Q2 17 | 18 | 如何确定页面偏移中使用了多少位? 19 | 20 | ## Q3 21 | 22 | 在上下文切换后 20 ms,TLB 包含数字代码使用的所有逻辑地址,该代码在 100%的时间内执行主存储器访问。与单级页表相比,两级页表的开销(减速)是多少? 23 | 24 | ## Q4 25 | 26 | 解释为什么在发生上下文切换时必须刷新 TLB(即指定 CPU 处理不同的进程)。 -------------------------------------------------------------------------------- /docs/87.md: -------------------------------------------------------------------------------- 1 | # 管道:复习题 2 | 3 | > 原文: 4 | 5 | > 问题编号可能会有变化 6 | 7 | ## Q1 8 | 9 | 填写空白以使下面的程序打印 123456789.如果`cat`没有参数,它只是打印输入直到 EOF。奖金:解释为什么下面的`close`电话是必要的。 10 | 11 | ```c 12 | int main() { 13 | int i = 0; 14 | while(++i < 10) { 15 | pid_t pid = fork(); 16 | if(pid == 0) { /* child */ 17 | char buffer[16]; 18 | sprintf(buffer, ______,i); 19 | int fds[ ______]; 20 | pipe( fds); 21 | write( fds[1], ______,______ ); // Write the buffer into the pipe 22 | close( ______ ); 23 | dup2( fds[0], ______); 24 | execlp( "cat", "cat", ______ ); 25 | perror("exec"); exit(1); 26 | } 27 | waitpid(pid, NULL, 0); 28 | } 29 | return 0; 30 | } 31 | ``` 32 | 33 | ## Q2 34 | 35 | 使用 POSIX 调用`fork` `pipe` `dup2`和`close`来实现自动编程程序。将子进程的标准输出捕获到管道中。子进程应该`exec`程序`./test`没有其他参数(进程名称除外)。在父进程中从管道读取:一旦捕获的输出包含!就退出父进程!字符。在退出父进程之前,将 SIGKILL 发送到子进程。如果输出包含!,则退出 0。否则,如果子进程退出导致管道写入结束,则退出值为 1.确保在父进程和子进程中关闭管道的未使用端 36 | 37 | ## Q3(高级) 38 | 39 | 这个高级挑战使用管道让“AI 玩家”自己玩,直到游戏完成。程序`tictactoe`接受一行输入 - 到目前为止的转弯顺序,打印相同的顺序,然后再转动,然后退出。使用两个字符指定转弯。例如,“A1”和“C3”是两个相对的角位置。字符串`B2A1A3`是 3 转/ plys 的游戏。有效响应是`B2A1A3C1`(C1 响应阻止对角线 B2 A3 威胁)。输出行还可以包含后缀`-I win` `-You win` `-invalid`或`-draw`使用管道来控制创建的每个子进程的输入和输出。当输出包含`-`时,打印最终输出行(整个游戏序列和结果)并退出。 -------------------------------------------------------------------------------- /docs/88.md: -------------------------------------------------------------------------------- 1 | # 文件系统:复习题 2 | 3 | > 原文: 4 | 5 | > 问题编号可能会有变化 6 | 7 | ## Q1 8 | 9 | 编写一个使用 fseek 和 ftell 的函数,用'X'替换文件的中间字符 10 | 11 | ```c 12 | void xout(char* filename) { 13 | FILE *f = fopen(filename, ____ ); 14 | 15 | } 16 | ``` 17 | 18 | ## Q2 19 | 20 | 在`ext2`文件系统中,从磁盘读取多少个 inode 以访问文件的第一个字节`/dir1/subdirA/notes.txt`?假设根目录中的目录名和 inode 编号(但不是 inode 本身)已经在内存中。 21 | 22 | ## Q3 23 | 24 | 在`ext2`文件系统中,必须从磁盘读取以访问文件`/dir1/subdirA/notes.txt`的第一个字节的最小磁盘块数是多少?假设根目录中的目录名称和 inode 编号以及所有 inode 已经在内存中。 25 | 26 | ## Q4 27 | 28 | 在具有 32 位地址和 4KB 磁盘块的`ext2`文件系统中,可以存储 10 个直接磁盘块编号的 inode。要求单个间接表所需的最小文件大小是多少? ii)双向表? 29 | 30 | ## Q5 31 | 32 | 修复下面的 shell 命令`chmod`以设置文件`secret.txt`的权限,以便所有者可以读取,写入和执行该组可以读取的权限,而其他所有人都无权访问。 33 | 34 | ``` 35 | chmod 000 secret.txt 36 | ``` -------------------------------------------------------------------------------- /docs/89.md: -------------------------------------------------------------------------------- 1 | # 网络:复习题 2 | 3 | > 原文: 4 | 5 | * [Wiki w / Interactive MC Questions](http://angrave.github.io/SystemProgramming/networkingreviewquestions.html) 6 | * 见[编码问题](#coding-questions) 7 | * 见[简答题](#short-answer-questions) 8 | * 参见 [MP Wearables](https://courses.engr.illinois.edu/cs241/mps/mp7/) Food For Thought 问题 9 | 10 | ## 简答题 11 | 12 | ## Q1 13 | 14 | 什么是插座? 15 | 16 | ## Q2 17 | 18 | 在端口 1000 和端口 2000 上侦听有什么特别之处? 19 | 20 | * 端口 2000 的速度是端口 1000 的两倍 21 | * 端口 2000 的速度是端口 1000 的两倍 22 | * 端口 1000 需要 root 权限 23 | * 没有 24 | 25 | ## Q3 26 | 27 | 描述 IPv4 和 IPv6 之间的一个重要区别 28 | 29 | ## Q4 30 | 31 | 你何时以及为什么要使用 ntohs? 32 | 33 | ## Q5 34 | 35 | 如果主机地址是 32 位,我最有可能使用哪种 IP 方案? 128 位? 36 | 37 | ## Q6 38 | 39 | 哪种常见的网络协议是基于数据包的,可能无法成功传送数据? 40 | 41 | ## Q7 42 | 43 | 哪种常见协议是基于流的,如果数据包丢失会重新发送数据? 44 | 45 | ## Q8 46 | 47 | 什么是 SYN ACK ACK-SYN 握手? 48 | 49 | ## Q9 50 | 51 | 以下哪一项不是 TCP 的功能? 52 | 53 | * 数据包重新排序 54 | * 流量控制 55 | * 数据包重传 56 | * 简单的错误检测 57 | * 加密 58 | 59 | ## Q10 60 | 61 | 什么协议使用序列号?他们的初始价值是多少?为什么? 62 | 63 | ## Q11 64 | 65 | 构建 TCP 服务器需要的最小网络调用是多少?他们的正确顺序是什么? 66 | 67 | ## Q12 68 | 69 | 构建 TCP 客户端需要的最小网络调用是多少?他们的正确顺序是什么? 70 | 71 | ## Q13 72 | 73 | 你何时会在 TCP 客户端上调用 bind? 74 | 75 | ## Q14 76 | 77 | socket bind listen accept 的目的是什么? 78 | 79 | ## Q15 80 | 81 | 以上哪个调用可以阻塞,等待新客户端连接? 82 | 83 | ## Q16 84 | 85 | 什么是 DNS?它对你有什么用?哪个 CS241 网络电话会使用它? 86 | 87 | ## Q17 88 | 89 | 对于 getaddrinfo,如何指定服务器套接字? 90 | 91 | ## Q18 92 | 93 | 为什么 getaddrinfo 会生成网络数据包? 94 | 95 | ## Q19 96 | 97 | 哪个网络调用指定允许的积压的大小? 98 | 99 | ## Q20 100 | 101 | 哪个网络调用返回一个新的文件描述符? 102 | 103 | ## Q21 104 | 105 | 何时使用被动插座? 106 | 107 | ## Q22 108 | 109 | epoll 什么时候比选择更好?何时选择比 epoll 更好的选择? 110 | 111 | ## Q23 112 | 113 | `write(fd, data, 5000)`总是会发送 5000 字节的数据吗?什么时候会失败? 114 | 115 | ## Q24 116 | 117 | 网络地址转换(NAT)如何工作? 118 | 119 | ## Q25 120 | 121 | @MCQ 假设网络在客户端和服务器之间有 20ms 的传输时间,建立 TCP 连接需要多长时间? 20 ms 40 ms 100 ms 60 ms @ANS 3 Way Handshake @EXP @END 122 | 123 | ## Q26 124 | 125 | HTTP 1.0 和 HTTP 1.1 之间有什么区别?如果网络传输时间为 20 毫秒,将 3 个文件从服务器传输到客户端需要多少 ms? HTTP 1.0 和 HTTP 1.1 之间的时间差异如何? 126 | 127 | ## 编码问题 128 | 129 | ## 问 2.1 130 | 131 | 写入网络套接字可能不会发送所有字节,并且可能会因信号而中断。检查`write`的返回值以实现`write_all`,该 COD1 将使用任何剩余数据重复调用`write`。如果`write`返回-1,则立即返回-1,除非`errno`为`EINTR` - 在这种情况下重复最后一次`write`尝试。您将需要使用指针算法。 132 | 133 | ```c 134 | // Returns -1 if write fails (unless EINTR in which case it recalls write 135 | // Repeated calls write until all of the buffer is written. 136 | ssize_t write_all(int fd, const char *buf, size_t nbyte) { 137 | ssize_t nb = write(fd, buf, nbyte); 138 | return nb; 139 | } 140 | ``` 141 | 142 | ## 问 2.2 143 | 144 | 实现一个侦听端口 2000 的多线程 TCP 服务器。每个线程应从客户端文件描述符中读取 128 个字节,并在关闭连接并结束线程之前将其回送给客户端。 145 | 146 | ## 问 2.3 147 | 148 | 实现侦听端口 2000 的 UDP 服务器。保留 200 字节的缓冲区。听取到达的数据包。有效数据包为 200 字节或更少,以 4 字节 0x65 0x66 0x67 0x68 开头。忽略无效的数据包。对于有效数据包,将第五个字节的值作为无符号值添加到运行总计中,并打印到目前为止的总数。如果运行总计大于 255,则退出。 -------------------------------------------------------------------------------- /docs/90.md: -------------------------------------------------------------------------------- 1 | # 信号:复习题 2 | 3 | > 原文: 4 | 5 | ## 给出通常由内核生成的两个信号的名称 6 | 7 | ## 给出信号无法捕获的信号名称 8 | 9 | ## 为什么在信号处理程序中调用任何函数(信号处理程序不安全)是不安全的? 10 | 11 | 编码问题 12 | 13 | 编写使用 SIGACTION 和 SIGNALSET 创建 SIGALRM 处理程序的简短代码。 -------------------------------------------------------------------------------- /docs/91.md: -------------------------------------------------------------------------------- 1 | # 系统编程笑话 2 | 3 | > 原文: 4 | 5 | ## 系统编程笑话 6 | 7 | 警告:作者不对这些“笑话”引起的任何神经细胞凋亡负责。 - 允许 Groaners。 8 | 9 | ## 灯泡笑话 10 | 11 | 问:更换灯泡需要多少个系统程序员? 12 | 13 | 答:只有一个,但他们不断改变它,直到它返回零。 14 | 15 | 答:没有人喜欢空插座。 16 | 17 | 答:嗯,你从一开始,但实际上它等待孩子做所有的工作。 18 | 19 | ## Groaners 20 | 21 | 为什么婴儿系统程序员喜欢他们新的多彩布料呢?这是多线程的。 22 | 23 | 为什么你的程序如此精致和柔软?我只使用 400 线程或更高的程序。 24 | 25 | 糟糕的学生 shell 进程何时会消失?分叉地狱。 26 | 27 | 为什么 C 程序员如此混乱?他们将所有东西存放在一个大堆里。 28 | 29 | ## 系统程序员(定义) 30 | 31 | 系统程序员是...... 32 | 33 | 知道`sleepsort`的人是个坏主意,但仍然梦想有使用它的借口。 34 | 35 | 从来没有让他们的代码陷入僵局的人......但是当它发生时,导致的问题比其他所有人都要多。 36 | 37 | 相信僵尸是真实的人。 38 | 39 | 如果没有使用相同的数据,内核,编译器,RAM,文件系统大小,文件系统格式,磁盘品牌,核心数量,CPU 负载,天气,磁通量,方向,小精灵灰尘进行测试,那些不信任其进程正确运行的人星座标志,墙壁颜色,墙壁光泽和反射率,主板,振动,照明,备用电池,一天中的时间,温度,湿度,月球位置,太阳月亮合作位置... 40 | 41 | ## 系统程序(定义) 42 | 43 | 系统程序...... 44 | 45 | 一直发展直到它可以发送电子邮件。 46 | 47 | 发展直到它有可能创建,连接和杀死其他程序并在所有可能的设备上消耗所有可能的 CPU,内存,网络......资源,但选择不这样做。今天。 -------------------------------------------------------------------------------- /docs/img/1d72e84109674f2e5db6da917167668b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/1d72e84109674f2e5db6da917167668b.jpg -------------------------------------------------------------------------------- /docs/img/1e5467473b254a4f5bb8d50f0f58ed17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/1e5467473b254a4f5bb8d50f0f58ed17.jpg -------------------------------------------------------------------------------- /docs/img/1e88c8266f91417008f275b2eec36df7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/1e88c8266f91417008f275b2eec36df7.jpg -------------------------------------------------------------------------------- /docs/img/261964eb034ed12b257f30989e6eb740.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/261964eb034ed12b257f30989e6eb740.jpg -------------------------------------------------------------------------------- /docs/img/27f5f2c018c60372297e6cf5790934bc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/27f5f2c018c60372297e6cf5790934bc.jpg -------------------------------------------------------------------------------- /docs/img/2d19ee5fcdb522a5a43850cd1746a70a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/2d19ee5fcdb522a5a43850cd1746a70a.jpg -------------------------------------------------------------------------------- /docs/img/332fa7df9feb67953e1554176b74fd84.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/332fa7df9feb67953e1554176b74fd84.jpg -------------------------------------------------------------------------------- /docs/img/3332236e74b2fb5bdc6e33bec4ce909e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/3332236e74b2fb5bdc6e33bec4ce909e.jpg -------------------------------------------------------------------------------- /docs/img/4013002f4b22a09d0fc6a117c0a29816.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/4013002f4b22a09d0fc6a117c0a29816.jpg -------------------------------------------------------------------------------- /docs/img/70f6ba3a3d379fcd8d3214846a16c410.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/70f6ba3a3d379fcd8d3214846a16c410.jpg -------------------------------------------------------------------------------- /docs/img/921a3e0534d1220fea61c43535dadd2c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/921a3e0534d1220fea61c43535dadd2c.jpg -------------------------------------------------------------------------------- /docs/img/a939273ec986aaa3938cdbe2867a08e2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/a939273ec986aaa3938cdbe2867a08e2.jpg -------------------------------------------------------------------------------- /docs/img/aacd324af23dbabdf0c8511652cfadb2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/aacd324af23dbabdf0c8511652cfadb2.jpg -------------------------------------------------------------------------------- /docs/img/b6620ef95e96fee4fcdff893eabdf95c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/b6620ef95e96fee4fcdff893eabdf95c.jpg -------------------------------------------------------------------------------- /docs/img/bddaf711271daa8286dff3dbac48867e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/bddaf711271daa8286dff3dbac48867e.jpg -------------------------------------------------------------------------------- /docs/img/bfa1f1caf816f2b844ae5b14da43f11b.svg: -------------------------------------------------------------------------------- 1 | Asset 2 -------------------------------------------------------------------------------- /docs/img/c012049198839822b4b9b3716bf1ddff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/c012049198839822b4b9b3716bf1ddff.jpg -------------------------------------------------------------------------------- /docs/img/c17a56237998885b1dd4cd23e86f4c90.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/c17a56237998885b1dd4cd23e86f4c90.jpg -------------------------------------------------------------------------------- /docs/img/d5c232c0012ccdb9ff8c9e79b2429cb9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/d5c232c0012ccdb9ff8c9e79b2429cb9.jpg -------------------------------------------------------------------------------- /docs/img/d69ff523ab899f8909888c58907d4ca8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/d69ff523ab899f8909888c58907d4ca8.jpg -------------------------------------------------------------------------------- /docs/img/e209a7428a9ce45094abf36a151c7d63.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/e209a7428a9ce45094abf36a151c7d63.jpg -------------------------------------------------------------------------------- /docs/img/ef4830cf8767dd32936281aca42c8eac.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/ef4830cf8767dd32936281aca42c8eac.jpg -------------------------------------------------------------------------------- /docs/img/faa7ac1f5f07a2ceee3dcc5057f329c6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apachecn/uiuc-cs241-notes-zh/460565d5e29e8c14a1f8e5d3b3bee3fae4e807a7/docs/img/faa7ac1f5f07a2ceee3dcc5057f329c6.jpg -------------------------------------------------------------------------------- /donate.md: -------------------------------------------------------------------------------- 1 | # 捐赠记录 2 | 3 | | 时间 | 收入类型 | 金额(元) | 捐赠者 | 4 | | --- | --- | --- | --- | 5 | | 2022-08-17 | 64~74 章奖励 | 26 | 飞龙 | 6 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
now loading...
21 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | git add -A 2 | git commit -am "$(date "+%Y-%m-%d %H:%M:%S")" 3 | git push --------------------------------------------------------------------------------