⚡️闪电网络简明教程

闪电网络是什么

闪电网络是目前比特币的一个发展趋势,它解决了比特币的小额高频转账的问题。

比特币实现了区块链账本,通过算力竞争的方式实现账本的一致性,这种方式注定了账本的使用成本高、速度慢、容量小。目前比特币主网的处理速度上限大概是7TPS,也就是一秒钟只能处理7次转账。比特币的出块速度是每10分钟出一次块,为了保证安全一般把转账确认设置为6个区块,也就是说一次转账至少需要60分钟才能被彻底确认。比特币作为区块链账本毫无问题,但是要用作小额高频支付工具力不从心。

为了解决这些问题,产生了许多技术方向,例如ETH目前要上线的PoS、EOS独创的DPoS、IOTA的tangle等,另外就是今天要说的闪电网络Lightning Network。(说句题外话,扩容区块只是一个临时缓解的办法,并没有解决根本问题。就算区块大小扩容十倍,7TPS也顶多到70TPS而已,依然非常低)

闪电网络依赖隔离见证(SegWit),隔离见证在2017年8月被主网确认正式生效。隔离见证有两个用处,一是变相扩容了区块(几乎等效于区块大小从1M扩容到2M)、二是为闪电网络提供底层支持。有了隔离见证,闪电网络就具备可行性了。

2017年12月6日,闪电网络协议规范RC1版本发布,同时ACINQ,、Lightning Labs、Blockstream三个团队正在着手进行软件开发。

截止到2018年7月,这三个团队都分别发布了闪电网络节点软件,还上线了第一款钱包eclair发布在Google Play。

目前闪电网络主网上运行的节点数量有2837个,处于Active状态的Channel有10042个,闪电网络总共托管了94.40BTC。这个数字每天都在增长,你可以在1ML看到具体数字。

知名网站CoinGate已经支持闪电网络支付,另外还有几家网站(Bitrefill、Blockstream、HodlMonkey、TorGuard)也支持了闪电网络。闪电网络目前的状态是几乎已经正式上线,虽然开发者警告不要放太多钱进去以防丢失。我们可以认为闪电网络已经不会再有大改动。

闪电网络的原理

闪电网络通过引入智能合约的思想来完成交易。通过事先约定的智能合约,将大量的交易放在链下进行,不受区块链速度的限制,只将最终的确认环节放在链上。这样大大提升了交易速度。

闪电网络的基础是隔离见证(SegWit),隔离见证在2017年8月被主网确认正式生效。隔离见证有两个用处,一是变相扩容了区块(几乎等效于区块大小从1M扩容到2M)、二是为闪电网络提供底层支持。有了隔离见证,闪电网络就具备可行性了。

闪电网络通过的核心概念可以分为两大块,其一是RSMC(Recoverable Sequence Maturity Contract),用来完成两个节点之间的交易确认,其二是HTLC(Hashed Timelock Contract),用来将多个节点连接成一个网络,实现跨节点支付。

RSMC:实现两个节点之间的交易确认

首先假定交易双方之间存在一个“微支付通道”(资金池)。交易双方先预存一部分资金到“微支付通道”里,初始情况下双方的分配方案等于预存的金额。每次发生交易,需要对交易后产生资金分配结果共同进行确认,同时签字把旧版本的分配方案作废掉。任何一方需要提现时,可以将他手里双方签署过的交易结果写到区块链网络中,从而被确认。从这个过程中可以可以看到,只有在提现时候才需要通过区块链。

任何一个版本的方案都需要经过双方的签名认证才合法。任何一方在任何时候都可以提出提现,提现时需要提供一个双方都签名过的资金分配方案(意味着肯定是某次交易后的结果,被双方确认过,但未必是最新的结果)。在一定时间内,如果另外一方拿出证明表明这个方案其实之前被作废了(非最新的交易结果),则资金罚没给质疑方;否则按照提出方的结果进行分配。罚没机制可以确保了没人会故意拿一个旧的交易结果来提现。

引用自:https://yeasy.gitbooks.io/blockchain_guide/content/bitcoin/lightning_network.html

简单来说,通过RSMC实现了两个节点之间的交易确认:

  1. A创建和B的支付通道Channel
  2. A投入准备金100元
  3. A给B转账实际上是在扣除准备金,A可以多次给B转账(发生在链下,不用区块确认)
  4. A或者B关闭闪电网络通道,进行提现,闪电网络将A、B的最终余额写入链上

可以看到转账都发生在链下,只有关闭通道才会产生一次链上交易

HTLC:将多个节点连接成一个网络,实现跨节点支付

可以看到,RSMC实现了两个节点之间的交易,但在现实场景中这显然不够。举个例子:

消费者A要在B、C、D网站上买东西,A要分别开通到B、C、D的交易通道,每个通道都要存入一定的准备金。如果A只和B交易一次就关闭通道,那实际上闪电网络并没有降低交易成本。

这个时候HTLC就应运而生了,HTLC实现了将闪电网络上的节点连接成一个网络,一笔交易可以路由经多个节点最终到达目的。

微支付通道是通过 Hashed Timelock Contract 来实现的,中文意思是“哈希的带时钟的合约”。这个其实就是限时转账。理解起来也很简单,通过智能合约,双方约定转账方先冻结一笔钱,并提供一个哈希值,如果在一定时间内有人能提出一个字符串,使得它哈希后的值跟已知值匹配(实际上意味着转账方授权了接收方来提现),则这笔钱转给接收方。

不太恰当的例子,约定一定时间内,有人知道了某个暗语(可以生成匹配的哈希值),就可以拿到这个指定的资金。

推广一步,甲想转账给丙,丙先发给甲一个哈希值。甲可以先跟乙签订一个合同,如果你在一定时间内能告诉我一个暗语,我就给你多少钱。乙于是跑去跟丙签订一个合同,如果你告诉我那个暗语,我就给你多少钱。丙于是告诉乙暗语,拿到乙的钱,乙又从甲拿到钱。最终达到结果是甲转账给丙。这样甲和丙之间似乎构成了一条完整的虚拟的“支付通道”。

引用自:https://yeasy.gitbooks.io/blockchain_guide/content/bitcoin/lightning_network.html

简单来说:

  1. A需要转账50给C
  2. A和B有交易通道(余额大于50),B和C有交易通道(余额大于50)
  3. A -> B -> C,交易完成

换成刚才那个例子:

消费者A要在B、C、D网站上买东西。消费者A和一个比较大的节点Z开通通道,网站B、C、D也和这个大节点Z开通通道。交易得以完成。消费者A只需要和Z打通,不需要关心网站BCD,网站BCD也只需要和Z打通不需要关心消费者A。

HTLC机制可以扩展到许多节点的场景,这里的节点Z并不一定就是同一个节点,只要闪电网络最终能找到一条可用的通道就能完成支付。可以是A->Z1->Z2->Zn….->B。

你可以在这里看到目前闪电网络的节点连接情况:https://lnmainnet.gaben.win/

HTLC的路由和手续费机制

开通通道需要存入准备金,一个足够大的节点需要大量的准备金来维持交易。目前最大的节点LN.SHITCOIN.COM已经存入了43.79478257BTC,大概是30万美元。节点不可能无偿提供服务,闪电网络设计了一套手续费体系来维持闪电网络的运行。

闪电网络上的每一个节点,都可以设置自己的手续费(fee)大小。

如果节点Z1设置了手续费1元,Z2设置了手续费2元,A->Z1->Z2->B这条支付路径里,A就需要额外付给Z1 1元,Z2 2元。

闪电网络的交易通常不只有一条通路,可能有很多条。客户端在发起支付之前可以通过getroute方法来确定每条通路的手续费情况,可以选择一条最便宜的通路来完成交易。

闪电网络的节点充满竞争,手续费太高可能造成你的节点无人使用,闪电网络节点理论上都会维持在一个相对低廉的手续费水平。

笔者通过闪电网络支付了一个在线轮盘赌网站,共花费手续费0.0000000204BTC,大概等于0.001元人民币。挑选一个手续费低的节点还能做到更低。就目前来看闪电网络的转账手续费几乎可以忽略不计。

体验闪电网络:eclair

闪电网络拥有一套完整的协议规范,在2017年12月发布了RC1版本。现在常见的闪电网络节点软件都是按照协议规范书写的,运行在同一个闪电网络上,可以互相兼容。

eclair是ACINQ团队创造的闪电网络程序,涵盖了Windows、Linux、macOS、Android平台。

桌面平台:https://github.com/ACINQ/eclair

安卓平台:https://github.com/ACINQ/eclair-wallet

以安卓为例,下载安卓钱包客户端:https://github.com/ACINQ/eclair-wallet/releases

朝eclair钱包充值一定数量的BTC(不要充值太多)

创建一个通道,这里可以选择ACINQ团队的官方通道,存入一定数量的BTC(不用担心,这里输入的金额只是通道保证金,随时可以取回)

选择一个收款方测试支付是否成功(因为手续费极低,可以测试转账一毛两毛的)

可以在这个网站搏一搏运气,最低支付一元钱:https://www.lightningspin.com/

以上是闪电网络钱包的使用方法,非常简单方便。

搭建一个自己的闪电网络节点

如果你不满足只是使用闪电网络,你还可以自己搭建一个闪电网络节点,参与到闪电网络中来,并且收取转账手续费。

一、购买云主机并安装Docker

除了临时测试外,不推荐使用自己的PC来搭建节点,一是因为国内网络环境恶劣造成很大麻烦,二是因为闪电网络的机制要求节点随时在线,长时间不在线容易被攻击。

运行比特币全节点需要磁盘空间在200G以上,并且这个大小还在持续增长。

以Vultr为例,购买NJ节点5$方案+20$的200G磁盘,月付25$。Vultr常年有新用户注册优惠活动,通过邀请链接注册送20$,足够跑一个月:https://www.vultr.com/?ref=6897056 (注意需要双币信用卡)

在Vultr购买主机可以选择预装Docker。需要自己安装Docker可以相应教程。

二、运行比特币全节点

clone项目到本地,编译bitcoind docker镜像

git clone https://github.com/dougvk/lightning-node.git
cd lightning-node
docker build . -t dougvk/bitcoind

运行bitcoind

mkdir -p /scratch/bitcoin/mainnet/bitcoind
docker run --name bitcoind_mainnet -d -v /scratch/bitcoin/mainnet/bitcoind:/data -p 8333:8333 -p 9735:9735 dougvk/bitcoind:latest

这样就启动了比特币全节点。比特币的数据文件存在了/scratch目录下。这个命令暴露了两个必要的对外端口8333和9735,不用担心,RPC端口并没有被暴露。

节点启动之后就开始了同步过程,需要同步180G左右的数据,大约需要12h。

通过这个命令可以查看同步进度:

docker run --rm --network container:bitcoind_mainnet -v /scratch/bitcoin/mainnet/bitcoind:/data dougvk/bitcoind:latest bitcoin-cli getblockchaininfo
{
  "chain": "main",
  "blocks": 532914,
  "headers": 532914,
  "bestblockhash": "0000000000000000001f2deba673d0bb65ac20a3690240324b0bb6bfe9286e23",
  "difficulty": 5178671069072.251,
  "mediantime": 1532152533,
  "verificationprogress": 0.9999936154754053,
  "initialblockdownload": false,
  "chainwork": "0000000000000000000000000000000000000000026ea366193d216bb3d37ef4",
  "size_on_disk": 200991969130,
  "pruned": false,
  "softforks": [
    {
      "id": "bip34",
      "version": 2,
      "reject": {
        "status": true
      }
    },
    {
      "id": "bip66",
      "version": 3,
      "reject": {
        "status": true
      }
    },
    {
      "id": "bip65",
      "version": 4,
      "reject": {
        "status": true
      }
    }
  ],
  "bip9_softforks": {
    "csv": {
      "status": "active",
      "startTime": 1462060800,
      "timeout": 1493596800,
      "since": 419328
    },
    "segwit": {
      "status": "active",
      "startTime": 1479168000,
      "timeout": 1510704000,
      "since": 481824
    }
  },
  "warnings": ""
}

blocksheaders数量一致代表同步完成

三、运行闪电网络节点

这里选择了clightning这个节点程序,运行节点(命令里可以设置节点名和颜色):

mkdir -p /scratch/bitcoin/mainnet/clightning
docker run --rm --name lightning --network container:bitcoind_mainnet -p9735:9735 -v /scratch/bitcoin/mainnet/bitcoind:/root/.bitcoin -v /scratch/bitcoin/mainnet/clightning:/root/.lightning --entrypoint /usr/bin/lightningd cdecker/lightningd:master --network=bitcoin --alias yournodename --rgb aa381e --announce-addr 本机ip --bind-addr 0.0.0.0 --log-level=debug

创建一个lightning-cli的命令,写入文件/usr/local/bin/lightning-cli

#!/usr/bin/env bash
docker run --rm -v /scratch/bitcoin/mainnet/clightning:/root/.lightning --entrypoint /usr/bin/lightning-cli cdecker/lightningd:master "[email protected]"

添加执行权限

chmod +x /usr/local/bin/lightning-cli

通过getinfo可以看到闪电网络节点的运行状况

lightning-cli getinfo

到这里闪电网络节点已经启动成功

四、给闪电网络钱包充值

执行命令lightning-cli newaddr生成一个充值地址

{ "address" : "1Cd6C8i6sc1ZReskWWGHJRXvSnvM3277qz" }

朝这个地址转账适量的BTC,等待比特币网络的6次确认,设置适当的手续费一般在60分钟左右。等充值被确认了以后就可以开始创建通道了。

五、创建闪电网络通道

你可以通过这个网站来找到你想连接的节点:https://1ml.com/

或者你也可以连接我创建的节点,执行命令进行连接:

lightning-cli connect 02e[email protected]5.135.180.50:9735

查看你连接过的节点状态:

lightning-cli getpeers

设置一个合理的手续费(这个手续费用作闪电网络合约的创建):

bitcoin-cli settxfee 0.00005

你可以查看当前的手续费水平,选择一个合适的值。如果是5sat/B,这里应该设置为0.00005。

手续费参考:https://p2sh.info/dashboard/db/fee-estimation

设置完后续费之后就可以开启通道了,开启通道需要设置准备金的数量:

lightning-cli fundchannel 02ed9102fe81a117b19f0c1fe0586536a04c962838677dcdb762aeb9804d9f33c7 20000

会返回一个txid,通过txid可以查看通道开通的进度,经过6次确认之后通道就正式开通了。

六、测试支付

同样,你也可以找个网站支付几毛钱测试一下能否成功

lightning-cli pay xxxxxxxx

通过lightning-cli getpeers命令可以查看当前通道的情况和剩余的BTC

七、查看并分享你的节点

顺利的话你的闪电网络节点已经创建成功了。

如果你和闪电网络任意一个节点创建过通道,你应该能在1ml.com上搜索到你的节点信息(可能需要过几个小时的同步时间)

https://1ml.com/node/02ed9102fe81a117b19f0c1fe0586536a04c962838677dcdb762aeb9804d9f33c7

通过lightning-cli getinfo 能查看本机节点信息,拼成[email protected]:port的形式分享给别人,别人就能通过connect命令连上你的节点,比如我的节点:

02e[email protected]5.135.180.50:9735

别人的转账路由经过了你的节点,就会向你支付一定的手续费。当然你也可以随时调整这个手续费。

0. 概览和基础原理 – 应用层通信原理与排错指南

微服务(Microservices)的兴起导致了系统的拆解、分离,以往的单个大型系统被拆分成多个单一职责的小型功能模块,模块拥有自己独立的进程和数据库,每个模块都是一个完整的小服务。

模块和模块之间需要相互通信,成熟的系统架构中应用层间通信至少涉及到三大块:服务发现、具体通信协议、负载均衡(容灾)。不仅在复杂的系统中,在你执行一次curl命令的时候你也能感受到他们的存在,通常一次curl请求至少包含了:DNS请求、HTTP协议、Nginx转发。

本文不准备学究式的去讨论技术实现,而是希望从实践的角度去讲解常见的应用层通信方式的原理和排错方法。

适宜读者:

  1. 平常只管开发的程序员
  2. 想要搞清大概原理的产品经理
  3. 还没入门的运维

 

一、互联网通信的基石:TCP/IP模型

四层简化协议的封装顺序

TCP/IP网络模型是一个金字塔结构,层层封装,越往上越应用,越往下越底层。

我们可以将这个模型简化一下,将网络连接层和传输层合起来看,四层模型中你只需要关注这两个:网络连接层+传输层(TCP/UDP + IP)、应用层(HTTP、DNS)。

应用层在传输层之上,应用层负责传输业务数据,传输层负责转发数据包。

(因为几乎不会将TCP/IP分开使用,本文统一将网络连接层和传输层合并成为传输层或者四层

二、传输层 TCP/UDP

当一条数据需要发送出去,数据包一层一层打包封装,封装到传输层的时候,数据包里都有什么信息呢?

以UDP为例,UDP/IP的报文逻辑上可以总结为这样:

UDP/IP报文简化逻辑图

可以看到,传输层UDP/IP只关心来源目的的地址和端口,只负责将数据从来源发送到目的,不会有任何别的逻辑。

另外,比起UDP,TCP保证了数据顺序的一致和报文传输的可靠,简单的说就是UDP只管将报文发出去,而TCP会保证发送的报文的先后顺序、保证每个报文都发送到位,除此之外TCP和UDP没有本质区别。

总的来看,TCP和UDP都是实现了以来源地址、来源端口、目的地址、目的端口构成四元组,将报文从来源发送到目的。

三、应用层 (以HTTP为例)

HTTP和TCP/IP的关系

HTTP协议和TCP/IP协议关系如图,TCP包的内容包着整个HTTP协议包,这里我们先不考虑分段传输等。接下来几篇文章中将着重介绍HTTP协议。

HTTP 协议中自定义返回码描述字段

HTTP 协议中每一个 Response 都有一个 Status Code,协议中预定义了若干状态码例如:

200 OK

301 Moved Permanently

404 Not Found

返回码由服务器控制,有些特殊场景下服务器会定义一些特殊返回码来表示返回状态。例如CDN服务商可能会返回自定义的5xx状态码作为回源失败的错误码。

除了状态码,状态码后面的描述其实也是包含在了 HTTP 协议里面,能够自定义返回,例如这是一个腾讯云的彩蛋页面:

HTTP 返回419状态码

HTTP 返回419状态码

http://cloud.tencent.com/419

 

HTTP 返回419状态码

HTTP 返回419状态码

抓包可以看到服务器返回了

HTTP/1.1 419 For One Night

这个419 For One Night当然是这帮工程师想出来的无聊的彩蛋页面的状态码了。

那么如何实现返回自定义状态码描述呢,在PHP里面可以通过header函数实现:

header(‘HTTP/1.1 419 For One Night’);

当然也不是服务端返回什么客户端就用什么,大部分客户端会优先使用预定义描述,也就是在HTTP标准里定义过的状态码描述客户端都会直接用,服务端返回一个非法描述客户端是不会认可的。

那么自定义状态码描述又有什么用呢,当然并没有什么卵用。

Flask路由使用装饰器的问题

需要实现验证用户登录的功能,考虑用装饰器实现:

def check_permission(func):
    def _check_permission(**args):
        #do check...
        return func(**args)
    return _check_permission

@app.route('/routea')
@check_permission
def a():
    pass

@app.route('/routeb')
@check_permission
def b():
    pass

当@check_permission只有一个时可以正常使用,但是超过一个就会报错:

AssertionError: View function mapping is overwriting an existing endpoint function: _check_permission

这样的错误是因为Flask在注册路由的时候使用了函数的名字,这个例子中是a和b,使用装饰器后a、b两个函数的名字都变成了_check_permission,造成冲突,提示overwriting。解决方法是加上@wraps():

from functools import wraps
def check_permission(func):
    @wraps(func)
    def _check_permission(**args):
        #do check...
        return func(**args)
    return _check_permission

@app.route('/routea')
@check_permission
def a():
    pass

@app.route('/routeb')
@check_permission
def b():
    pass

Python自带辅助库functools中的@wraps装饰器能够改变函数的__name__等,解决问题。具体原理参考下面:

http://coolshell.cn/articles/11265.html
http://stackoverflow.com/questions/19261833/what-is-an-endpoint-in-flask

补充:
完事之后才发现官方文档已经介绍过这个方法了,事实证明我是对的……

http://docs.jinkan.org/docs/flask/patterns/viewdecorators.html

没钱就用Arch!

在办公室找到一个2007年的完好的古董PC,然后就生命在于折腾啦。

系统安装

机器只有一个CD光驱,DVD碟读不出来,改用U盘但是引导不进去。拔掉硬盘线通过SATA to USB转接器接在笔记本电脑上,打开主机供电,成功读出硬盘。笔记本插上安装碟进了ArchLinux,/dev下果然有两个硬盘sda、sdb,转接器支持得还不错。格式化之后分好区,跟着ArchWiki走顺利装好系统。不料重启之后进了Grub,手动引导成功进入系统,Grub就懒得修复了反正又不重启。

Awesome

为了分屏+纯键盘操作+低性能支持用了Awesome,原来在笔记本上没能体会到它的优点,现在装在低性能无鼠标的环境上十分带感。作死忘加交换区,装了FireFox打开就死机,添加4G交换区之后吃上100MB的还能正常运行,趁热把密钥生成好导入到Github上。
Awesome的rc.lua配置很强大,简单配置一下就能发挥大作用。为了完全的自定义用lua脚本来做配置文件也是醉了。
装Awesome花了大量时间,主要是忘了装X嗯,下次记住了,xorg-apps也要装上。

终端

试过好多终端软件,回头发现Awesome的自带终端完全不需要换,改改颜色改改字体还能再战。
不仅主机是老古董,显示器也是旧的,KTC 7009S,1280×1024,作死的5:4。分辨率太低没办法,把终端字号改成8px,勉勉强强Vim可以开三个竖排。
中文支持很麻烦,改了LANG装了WenQuanYi,少数情况下依然有方块。

Vim

刚说了显示器很烂,Vim下的配色试了许多,花花绿绿的很刺眼。最后选了molokai,普通语句和字符串都是不刺眼的颜色,Python下def、class、return、if这些出现频率不高的关键字是红色和黄色。
考虑过用Vim来分屏还是Awesome。Vim开:vs分屏之后,中间的分界线是很粗的一条白色线条,在整个暗色的编辑环境里十分碍眼。Awesome本身对分屏支持也更好,怒选Awesome。不知道白条可不可以自定义。

目前的环境

左边是刚装好的Arch,右边是笔记本Windows。Arch写代码Win搜资料,大家都是程序员为什么要相互伤害,Linux和Win不是配合得好好的吗233333
不管怎么说环境都是自己营造的啦,虽然再恶劣也能靠自己的一点一滴来改变。没钱就用Arch!

Node_redis对数组支持的BUG

做微信公众平台的时候发现Node_redis(https://github.com/mranney/node_redis)一个严重BUG
执行redis.lpush(‘mylist’,[‘a’,’b’,’c’],callback)的时候,预计返回

mylist
a
b
c

实际返回

mylist
a,b,c

Github已经有人提出过这个问题

https://github.com/mranney/node_redis/issues/369#issuecomment-13050944

产生原因

所有的client.COMMAND会调用client.send_command()

RedisClient.prototype[command] = function (args, callback) {
    if (Array.isArray(args) && typeof callback === "function") {
        return this.send_command(command, args, callback);
    } else {
        return this.send_command(command, to_array(arguments));
    }
};

调用client.COMMAND的时候

client.COMMAND(key, ["arg1", "arg2", "arg3"], callback);

会被解析成

client.send_command(COMMAND, [key, ["arg1", "arg2", "arg3"]], callback);

send_command()有如下段落

    for (i = 0, il = args.length, arg; i < il; i += 1) {
        arg = args[i];
        if (typeof arg !== "string") {
            arg = String(arg);
        }
        command_str += "$" + Buffer.byteLength(arg) + "\r\n" + arg + "\r\n";
    }

示例中循环到第二次 arg = ["arg1", "arg2", "arg3"] ,会被String()强制转换为"arg, arg2, arg3",产生BUG。

解决方法

直接调用

client.send_command(COMMAND, [key, "arg1", "arg2", "arg3"], callback);

或者将key包含到数组里面

client.COMMAND([key, "arg1", "arg2", "arg3"], callback);

吐个槽

既然都知道原因了,2013年2月的issue为什么到现在都还没解决!!

系统引导流程、重建和Grub

手抖格式化了硬盘,好在及时停止,只是引导被破坏。原系统中有Win7+Ubuntu双系统,Win7在/dev/sda1,Linux在/dev/sda7。重装引导的时候出了不少问题,也得到了很多新的认识。

Grub和Grub2

Grub有Grub和Grub2。Grub2在10年前已经发布,目前预装在了大多数发行版上。网上各种教程多为Grub的而不是Grub2。
Grub和Grub2有很大区别,经常碰到的有:

  • /boot/grub/menu.lst被/boot/grub/grub.cfg所取代
  • update-grub被grub-mkconfig -o /boot/grub/grub.cfg取代
  • Grub中加载内核命令kernel在Grub2中有相似的命令linux

已经入过一次坑,请大家明鉴。

乱糟糟的中文wiki:http://linux-wiki.cn/wiki/zh-hans/Grub2%E9%85%8D%E7%BD%AE
grub和grub2的区别:http://lesca.me/archives/differences-between-grub-and-grub2.html

MBR和各种启动器的关系

each disk has an mbr (master boot record) or vbr (volume boot record)
– a partition does not have an mbr or vbr

the mbr is in the first 512 bytes of a disk and is not normally
accessible

the mbr (basically) holds the information for your disk and partitions
and where to find the boot files

bcdedit in win7 and vista is the boot configuration data editor, this
has replaced ntldr (new technology loader) that is in xp and w2k
linux uses grub (grand unified bootloader) or lilo (linux loader)

all 4 of these write to your mbr so your mbr can access the boot
configuration file (boot.ini in xp/w2k, menu.lst for grub legacy or
grub.conf for grub2 in linux, etc) and allow the system to boot,

windows bootloaders do not recognise other systems so cannot boot
non-windows systems, grub can be installed on its own and can boot
any operating system,
原文:http://answers.yahoo.com/question/index?qid=20100902034736AAUYSXC

大致翻译

每块硬盘都有一个MBR(Master Boot Record,主引导记录)或者VBR(Volume Boot Record,卷引导记录)。
MBR在磁盘的头512字节(这就是为什么低级格式化磁盘造成MBR损坏的原因),保存了磁盘和分区的启动信息。
winxp、win2k使用ntldr(New Technology Loader),Win7、Vista使用bcdedit来作为引导编辑器,Linux一般使用grub、lilo等。
以上这四个工具会写MBR来引导启动相应的操作系统。但是Win的引导器只能引导Win的操作系统,Linux的例如grub2可以引导Win、Linux甚至Mac。

MBR由三个部分组成:

  1. 第1-446字节:调用操作系统的机器码
  2. 第447-510字节:分区表(Partition table)
  3. 第511-512字节:主引导记录签名(0x55和0xAA)

http://zh.wikipedia.org/wiki/MBR

启动流程

  1. BIOS
  2. MBR中的第一部分机器码执行
  3. 各种启动器(grub、lilo……)
  4. 操作系统等

这样看来系统分区设为活动分区并不是必须的,这主要视引导程序而定,有些引导程序例如Grub4Dos仅仅遍历所有分区并运行第一个找到的引导器grldr。同样,grub写入MBR中的程序会搜寻逻辑分区,所以将grub主程序安装在逻辑分区(/dev/sda7)里面依然能够正常启动。

Grub2重建引导

Grub2中重建引导命令

grub-install --root-directory=/ /dev/sda
--root-directory是指安装的位置,一般安装在分区根目录下
/dev/sda是MBR的写入磁盘,是整个磁盘而不是分区

例如单硬盘机器Linux安装在/dev/sda7:

mount /dev/sda7 /mnt
grub-install --root-directory=/mnt/ /dev/sda

这样可以将Grub2安装在Linux分区里面,当然也可以安装在其他分区。Grub2会自动搜索NTFS分区, 理论上安装好后不用配置就可以引导Windows。(有些地方似乎是grub2-install)
很多教程将命令写作

grub-install --boot-directory=/boot

不知道是否有这个用法,但是我没成功。
另外

grub-update 自动更新启动项目列表,添加有效操作系统(有的地方写作update-grub)

重建引导都干了什么

执行grub-install之后会重写MBR的第一部分,这样计算机启动的时候就会执行重写过后的机器码,这段机器码会将控制权交给grub主程序,这里是/dev/sda7里的/boot/grub(当然/boot/grub也会在install的时候重新安装)。开始第二阶段引导加载。

Linux 引导过程内幕https://www.ibm.com/developerworks/cn/linux/l-linuxboot/

让织梦缩略图自适应裁剪

Ecjtu.net有三个页面需要调用同一个文章的缩略图,但大小比例都不一样。用如下方法实现缩略图自动裁切。

需要PHP安装GD库,http://www.php.net/manual/zh/intro.image.php

代码

/include/extend.func.php里添加thumb()函数:

function thumb($imgurl, $width, $height)
{
    global $cfg_mainsite,$cfg_multi_site;
    $thumb = eregi("http://",$imgurl)?str_replace($cfg_mainsite,'',$imgurl):$imgurl; 
    list($thumbname,$extname) = explode('.',$thumb);
    $newthumb = $thumbname.'_'.$width.'_'.$height.'.'.$extname;
    if(!$thumbname || !$extname || !file_exists(DEDEROOT.$thumb)) return $imgurl;
    if(!file_exists(DEDEROOT.$newthumb))
    {
        include_once DEDEINC.'/image.func.php';
        $src_im = imagecreatefromjpeg(DEDEROOT.$thumb);
        $dst_im = imagecreatetruecolor($width, $height);
        $width_im = imagesx($src_im);
        $height_im = imagesy($src_im);
        if($width_im/$height_im < $width/$height)
        {
            $tmp = ($height_im-$width_im*($height/$width))/2;
            imagecopyresampled($dst_im, $src_im, 0, 0, 0, $tmp, $width, $height, $width_im, $height_im - $tmp*2);
        }
        else
        {
            $tmp = ($width_im-$height_im*($width/$height))/2;
            imagecopyresampled($dst_im, $src_im, 0, 0, $tmp, 0, $width, $height, $width_im - $tmp*2, $height_im);
        }
        imagejpeg($dst_im, DEDEROOT.$newthumb, 100);
        imagedestroy($dst_im);
        imagedestroy($src_im);
    }
    return $cfg_multi_site=='Y'?$cfg_mainsite.$newthumb:$newthumb;
}

调用

在任何调用缩略图的时候:

[field:picname function='thumb(@me,590,310)'/]
第二个第三个参数分别为宽高

规则

调用缩略图的时候首先查看缩略图目录是否有相应大小裁切过的图片,有就直接返回,没有就在同目录下生成并返回。
生成的缩略图命名规则:

原缩略图名_宽_高.xxx
例:142_1321254421_590_310.jpg

裁切规则:

  • 保持需要的比例
  • 尽可能大的保留图片
  • 超出尺寸部分裁剪两边保留中间

注意

相应尺寸的缩略图生成之后就永远不会被改变,因此某些特殊情况下可能需要你手动删除。