《现代操作系统》读书笔记——进程

一个进程就是一个正在执行的程序实例,它包括程序计数器、寄存器以及变量的当前值。一个程序运行,它的逻辑计数器装入 CPU 的程序计数器中;一个程序暂停,CPU 的程序计数器被保存在内存的逻辑程序计数器中,在下次运行前不占用 CPU。 要特别注意的是,进程并不是在 CPU 中一直运行的,CPU 会在各进程之间来回切换,所以每个进程执行的速度是不确定的。所以,大多数进程并不受 CPU 多道程序设计或其它进程相对速度的影响。 进程的创建和终止 有四种情况会导致进程的创建,它们分别是系统初始化、正在运行的程序执行了创建进程的系统调用、用户请求创建一个新进程、一个批处理作业的初始化。拿 Linux 为例,Linux 启动时的第一个进程是 0 号进程,它是所有进程的祖先。其次是 1 号进程,它是所有用户进程的祖先。 我们都知道 Nginx。当我们启动 Nginx 后,它一直在默默地监听端口执行 WEB 服务,而我们没有感知。这一类进程,便是守护进程。 任何进程,都可以创建一个新的进程,这个进程便是子进程,执行的是 fork 系统调用。进程可以 fork 子进程,子进程共享父进程的数据,但是,子进程对数据做的任何修改,对于父进程,都是不可见的。子进程的内存地址空间,是父进程的副本,是不同的地址空间。 进程只能有一个父进程,但是一个进程可以有多个子进程。在 UNIX 中,进程不能剥夺其子进程的继承权。 可写的空间是不共享的,共享的空间是不可写的。子进程可以共享父进程的内存空间,但是要通过写时复制共享。即在修改部分内存,先要明确地复制,以确保发生在私有的内存区域。 进程终止通常由下面的情况引起,正常退出、出错退出、严重错误、被其他进程杀死。其中后面两种情况是非自愿的。Linux 中自愿终止进程可以通过调用 exit 系统调用完成。 进程的状态 进程由三种状态,运行、阻塞和就绪。 处在运行态的进程,在这个时刻实际占用 CPU。 处在就绪态的进程具备可以运行条件,但是因为其它进程正在运行,而被操作系统暂停运行。 处在阻塞态的进程,因该进程调用了本地阻塞的系统调用,导致暂停运行。处在阻塞态的进程,不具备可以运行的条件。除非外部某实践发生,例如本地阻塞的调用完成,方能够转换为就绪态,等待操作系统调度。 进程的实现 操作系统维护这一个进程表,每一个进程的详细信息都保存在这张进程表中。包括程序计数器、堆栈指针、内存分配情况、文件打开情况、中断向量等。 如前面所说,每个进程都不是一直在 CPU 中运行地。其中就绪态和阻塞态是非运行状态。当操作系统将正在运行的进程切换为就绪态,或者进程因为调用了本地阻塞的系统调用而进入阻塞态时,其运行信息被保存在进程表中。当操作系统重新切换进程为运行状态时,将进程表中的信息恢复,就像进程没有中断一样。 以 IO 为例。当一个进程 A 调用了网络 IO 的系统调用时,由于该系统调用时阻塞的,于是操作系统将其切换为阻塞态,并且将它的现场都保存在进程表中,并将此地址与中断向量映射保存。然后,切换 B 进程。一段时间过去,此时可能是 C 进程在 CPU 中运行,A 进程调用的 IO 完成了,则该硬件发生了一个中断。此时,操作系统将中断正在运行的 C 进程,同时通过中断向量找到进程表中的 A 进程的现场并恢复到 A 进程没有中断时的状态,继续运行。
Read more →

《现代操作系统》读书笔记——线程

Read more →

源码安装 NSQ

因为业务需要,要用到 NSQ。所以学习了下 NSQ。首先是安装,我在自己电脑上,倾向于源码安装。一是源码安装可以安装最新的代码,二是整个安装过程可以自己掌控。 但是,安装过程中遇到了一些坑。主要还是我对 Go 以及一些衍生工具用的不是特别熟悉,并且在网上搜索到的文章,都是抄来抄去的很多并不能解决我的问题。所以我把整个安装过程记录下来,给自己一个备忘,给别人一个方便。 安装 Go NSQ 是用 Go 写的,所以安装 NSQ 之前,要先安装 Go。 我这里给出安装具体过程的命令。具体可以参考我写的另外一篇文章 从零开始学习 Go ——安装。 echo "export GOROOT=$HOME/.golang/go" >> ~/.bash_profile echo "export GOPATH=$HOME/.golang/path" >> ~/.bash_profile echo "export PATH=$PATH:$HOME/.golang/go/bin" >> ~/.bash_profile echo "export GOROOT_BOOTSTRAP=$HOME/.golang/go1.4" >> ~/.bash_profile source ~/.bash_profile cd ~ mkdir .golang git clone https://github.com/golang/go.git go cp -r go go1.4 cd go1.4 git checkout -b release-branch.go1.4 origin/release-branch.go1.4 cd src ./make.bash cd ../../go git checkout -b release-branch.go1.8 origin/release-branch.
Read more →

从零开始写 PHP 扩展(一)

PHP 是用 C 语言写的。对于每个 PHPer 来说,都有着内心的一种希望写扩展的冲动了吧。然而,缺乏一个很好的切入点。Google 上搜 PHP 扩展开发,大部分都是复制品文章,甚至有些人连操作都没有操作过就搬运在了自己的博客。不过也有几篇好教程,但是都是 PHP 5 时代的产物,隐藏着非常多的坑。我会将我自己慢慢踩坑的过程记录下来,也许这就成了其它人的“教程”了吧。 生成一个扩展 想必很多人已经看到很多网上的教程了。大多都是教我们执行这个命令:$ ./ext_skel –extname=extname。但是,当你 clone 了 PHP 源码后会发现,master 分支下并没有 ext/ext_skel 这个文件。所以,我总结了一下: 如果你是直接下载 PHP 的源码,或者在已经 release 的版本分之下,你可以执行这个命令 $ cd ext $ ./ext_skel –extname=extname 如果你是直接在 master 分支下,只有 ext_skel.php 文件,这个时候你就直接可以执行这个 PHP 文件 $ cd ext $ php ext_skel.php –ext extname 由于我是直接在 master 分支下开发的,所以后面的都是默认在 master 分之下的操作。 生成了扩展之后,我们会看到四个文件和一个文件夹。现在这个阶段,我们只需要用到两个文件,.c 文件和 .h 文件。 一个小坑 在我们生成好扩展之后,我们可以试着编译一下 $ phpize $ ./configure $ make && make test 我们会惊讶地发现,编译的时候会有一个 warning。
Read more →

浅入理解单例模式

问题 恼人的全局变量 在 PHP 中,甚至不只 PHP 中,我们都会用到全局变量,以保存全局状态。可是,往往全局变量是全局共享的,任何地方任何代码都有可能将其覆盖。例如,我们定义一个全局变量叫做 PHONE。我们在某一行代码中,将其定义成了 iPhone,但是我们不小心在另一行代码中将其覆写成了 Nokia。这就非常的尴尬了,因为本来我们并不想它被覆写。 繁琐的参数传递 在一个系统中,我们会定义许多的方法,生成很多的对象。有时候,我们会使用很多的方法,对同一个对象做操作。在不使用全局变量的情况下,我们需要将对象作为参数传入方法中。但是这样传递同一个对象,可能会造成混乱,还可能造成不必要的依赖。 其实我们只需要一个全局可访问的对象就可以解决这个,但是全局变量又会出现我们上面的说的问题。 解决 目标 我们要解决这些问题,我们对这样的对象有下面的几个目标。 这个对象,无论在哪里都能访问,就想全局变量一样。 这个对象,和全局变量不同,不能被覆写。 这个对象,整个系统中只存在一个,对它的修改在整个系统中都能被感知到。 以上的几个目标,就是我们所需要的,也就是单例模式的特征。 UML 实现 class Preference { private static $instance; private $props = []; private __construct() {} public static function getInstance() { if (empty(self::$instance)) { self::$instance = new Preference(); } return self::$instance; } public function setProperty($key, $value) { $this->props[$key] = $value; } public function getProperty($key) { return $this->props[$key]; } } 我们在这里引入了一个私有的构造函数,这样,外部就无法实例化这个对象了。同时,我们使用 getInstance 方法来获取具体的实例,而无法去覆写它,这就达成了第二个目标。
Read more →

浮点数那些事

本文为作者自己的总结的,由于作者的水平限制,难免会有错误,欢迎大家指正,感激不尽。 说起浮点数,大家都是又恨又爱的。爱呢,是因为,只有它可以方便地使用小数;恨呢,是因为它并不能精确地表示小数。 以 PHP 为例:floor((0.1 + 0.7) * 10) 这样一个函数调用,根据数学老师死得晚原理,大家都能得出 8 这个结果。可是事实上呢?它会返回 7。数学老师的棺材板。。。(╯‵□′)╯︵┻━┻ 可是为什么会出现这种情况呢?这就要从浮点数的特性说起了。 万物皆二进制 我们都知道,在计算机中,一切的一切都是二进制表示的。假设一个 4 字节整型的十进制数 8,在大端表示的机器中,表示成 00000000 00000000 00000000 00001000(0x0000008)。将十进制整数转换成二进制数,是非常容易的。可是,小数呢?比如,我们要表示 1.75,该怎么存储在计算机中呢?显然,不能像整数一样存储了。 小数的二进制 让我们回忆一下,在十进制中,小数是怎么计算的。上面的 1.75 我们是这么算的:1 × 10^0 + 7 × 10^-1 + 5 × 10^-2 。那么我们按照相同的规则,来用二进制计算一下小数部分:0.75 = 1⁄2 + 1/4,也就是 1 × 2^-1 + 1 × 2^-2 ,再加上前面的整数部分,那么整个式子就变成了 1 × 2^0 + 1 × 2^-1 + 1 × 2^-2 ,写成二进制形式就是 1.11。所以,1.75 的二进制表示是 1.11。 对于将小数转换为二进制,和整数部分除二取余相反的,是乘二取整。
Read more →

php 内核探秘之 PHP_FUNCTION 宏

本人也只是个初入门的菜鸟,因对技术有着向往,故在“无趣”的工作之余,尽自己所能提升自己。由于我的 C 语言功底也有限,故本文的深度也有限,如有幸得大牛阅读,还望指导一二,小弟感激不尽。 PHP 的函数 作为 PHPer,我们几乎每天都在写函数,我们一定会好奇,那些 PHP 内置的函数,是长什么样子的。如果写过 PHP 扩展的话,一定知道这个宏:PHP_FUNCTION。在定义一个函数的时候,这样来使用这个宏。例如 array_change_key_case,它的定义是这样的:PHP_FUNCTION(array_change_key_case)。没错,就是这么简单。但是,在这个简单的背后,却没有这么简单。 PHP_FUNCTION 追根溯源 宏 相信对这篇文章感兴趣的同学,一定多少对 C 语言以及它的宏定义有一定的了解。如果没有,也不要紧,我这里来简单解释一下,什么是宏。 C 语言中的宏,我认为,可以理解为一种简单的封装。通过宏定义,可以对开发者隐去一些细节,让开发者在使用简单的语法来完成重复的复杂的编码。当然,宏定义还有其它的用途,但是,我们在 PHP_FUNCTION 涉及到的就是这个作用。有下面的代码。 #define TEST(test) void test(int a) TEST(haha) 宏,就是完全的替换,即使用后面的语句替换前面的。那么对于下面的 TEST(haha) 就相当于下面的样子。 void haha(int a) PHP_FUNCTION 的定义 首先,我们要定义函数,这样使用这个宏。 PHP_FUNCTION(array_change_key_case) { // TODO } 我们在 php-src/main/php.h 中找到了下面的定义。 #define PHP_FUNCTION ZEND_FUNCTION 也就是说,这里用 ZEND_FUNCTION 替换了 PHP_FUNCTION 这个宏。所以,我们的定义就相当于变成了这样。 ZEND_FUNCTION(array_change_key_case) { // TODO } 我们继续往下找,因为,这里还是宏,我们并没有看到我们希望看到的代码。我们可以在 php-src/Zend/zend_API.h 中找到下面的定义。 #define ZENDFN(name) zif##name #define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name)) #define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS) 我们看到,在宏定义中,使用了另外的宏。不要怕,还是一个词,替换。我们按照这样的步骤来。(## 是一个连接符,它的作用是,是将它前面的与后面的,按照字符串的方式连接起来。
Read more →

每日一个 PHP 函数————array_change_key_case

因为已经有文档了,可能有些人觉得我写这个有些多余了。可是并不是每一个 PHPer 都会好好地去阅读文档,自然有一些函数可能都没有听说过(很不幸我也是这其中的一员)。我也希望能通过写这些文章,能够促使我完整地读完文档,同时,能够给其它的 PHPer 一个参考,“啊,原来还有这个函数” 的感觉。同时,我也希望我能通过写这些文章,去阅读各个函数的 C 语言实现。也实现自我驱动地学习。 函数原型 array array_change_key_case ( array $array [, int $case = CASE_LOWER ] ) 该函数的具体作用是,将一个数组中的所有的英文字母转换为大写或小写。 我们可以看到,这个函数接收两个参数,返回一个数组。第一个参数数组没有使用引用的方式,那么说明该函数并不会改变原数组,它会生成新的数组作为返回值。而第二个参数是可选的,它控制着该函数是转换成大写还是小写。默认是转化为小写。 函数使用 第二个参数 函数的第二个参数传入的是一个预定义常量,分别是 CASE_LOWER 和 CASE_UPPER,前者是将 key 转换成小写,也是函数的默认值;后者是将 key 转换成大写。 使用 $arr = [ ‘loWer’ => 1, ]; $toLower = array_change_key_case($arr, CASE_LOWER); // 我认为,不管它的默认值是什么,我们都要写上这第二个参数。我们的代码写出来,是给人看的,不是给机器看的。 // 所以我们的代码应当尽量多的包含语义。 $toUpper = array_change_key_case($arr, CASE_UPPER); var_dump($toLower); var_dump($toUpper); 坑 这个函数的使用,是有个坑的,这个坑就是,当转换之后,如果结果中有两个相同的 key,那么就会保留最后的那个。举个例子。 $arr = [ ‘key’ => 1, ‘kEy’ => 2, ‘keY’ => 3, ]; $toLower = array_change_key_case($arr, CASE_UPPER); var_dump($toLower); // [‘key’ => 3] 在这个例子中,我们发现,当执行转换之后,三个 key 变成相同的了,那么在这种情况下,只会保留最后一个元素作为 key。这里得到的数组是 [‘key’ => 3]。
Read more →

浅入理解 PHP 中的 Generator

何为 Generator 从 PHP 5.5 开始,PHP 加入了一个新的特性,那就是 Generator,中文译为生成器。生成器可以简单地用来实现对象的迭代,让我们先从官方的一个小例子说起。 xrange 在 PHP 中,我们都知道,有一个函数叫做 range,用来生成一个等差数列的数组,然后我们可以用这个数组进行 foreach 的迭代。具体就想这样。 foreach (range(1, 100, 2) as $num) { echo "{$num}\n"; } 这一段代码就会输出首项为 1,末项为 100,公差为 2 的等差数列。它的执行顺序是这样的。首先,range(1, 100, 2) 会生成一个数组,里面存了上面那样的一个等差数列,之后在 foreach 中对这个数组进行迭代。 那么,这样就会出现一个问题,如果我要生成 100 万个数字呢?那我们就要占用上百兆内存。虽然现在内存很便宜,但是我们也不能这么浪费内存嘛。那么这时,我们的生成器就可以排上用场了。考虑下面的代码。 function xrange($start, $limit, $step = 1) { while ($start <= $limit) { yield $start; $start += $step; } } foreach (xrange(1, 100, 2) as $num) { echo "{$num}\n"; } 这段代码所的出来的结果,和前面的那段代码一模一样,但是,它内部的原理是天翻地覆了。
Read more →

一个 PHPer 第一次用 Koa2 写 Node.js 的心路历程

学了一段时间的 js 了,突然想实践一下。正好公司有个小的项目要做,就顺手拿 Koa2 来做了。真是不做不知道,做了想不到。踩了一堆新手坑。 初次接触 Koa2 在知道 Koa2 之前,我也了解过 Express,可惜并没有实战用过。后来大家都说 Koa 是一个比 Express 更牛X的东西,于是在好(作)奇(死)心作祟下,直接去用 Koa2 了。后来证明的确是作死,原本用 PHP 一天就能写完东西,愣是让我搞了三天。 安装 最近 Node.js V8 发布了,原生支持 async 和 await 调用了,所以直接把 Node.js 升级了一下。 根据 Koa2 的教程,安装很简单,我是使用的 yarn 的(还真是比 npm 快)。 yarn add koa 默认就装了 Koa 2.2。然后装完了,其实我是一脸懵逼的,文档上说这样用。 const Koa = require(‘koa’) const app = new Koa() // response app.use(ctx => { ctx.body = ‘Hello Koa’ }) app.listen(3000) 我照着代码写了下来,的确成功了。可是,难不成我要把所有的逻辑写在 app.use 里? 中间件 我感觉我受到了惊吓,吓得我赶紧往下看文档。原来 Koa2 是一个中间件模型。app.
Read more →