还没想好用什么标题

0%

青檬音乐

http://tingapi.ting.baidu.com/v1/restserver/ting?from=android&version=5.6.5.6&format=json&method=baidu.ting.song.getRecommandSongList&num=200&song_id=100575177

###一、百度音乐api
01、首页
http://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1

02、百度在线音乐歌单
    /**
     * #主打榜单
     * 1.新歌榜
     * 2.热歌榜
     * #分类榜单
     * 20.华语金曲榜
     * 21.欧美金曲榜
     * 24.影视金曲榜
     * 23.情歌对唱榜
     * 25.网络歌曲榜
     * 22.经典老歌榜
     * 11.摇滚榜
     * #媒体榜单
     * 6.KTV热歌榜
     * 8.Billboard
     * 18.Hito中文榜
     * 7.叱咤歌曲榜
     */
//热歌榜
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=2&size=3&offset=0

新歌榜
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=1&size=3&offset=0

华语金曲榜
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=20&size=3&offset=0

欧美金曲榜
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=21&size=3&offset=0

影视金曲榜
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=24&size=3&offset=0

情歌对唱榜
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=23&size=3&offset=0


网络歌曲榜
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=25&size=3&offset=0

摇滚榜
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=11&size=3&offset=0

KTV热歌榜
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=6&size=3&offset=0


Billboard
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=8&size=3&offset=0

Hito中文榜
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=18&size=3&offset=0

叱咤歌曲榜
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=7&size=3&offset=0

经典老歌榜
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=22&size=3&offset=0



03、歌曲榜单返回20条数据
热歌榜
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=2&size=20&offset=0


04、歌曲详情
获取歌曲播放url(成都)
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.song.play&songid=274841326

获取播放音乐url(桃花诺)
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.song.play&&songid=541680641
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.song.play&&songid=293272027


05、搜索歌曲
http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.search.catalogSug&&query=林俊杰



开源中国---百度音乐api
1、获取歌手专辑信息(第二步)
    http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.album.getAlbumInfo&format=json&album_id=11053974

2、搜索音乐
    http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.search.common&query=爱我还是他&page_size=30&page_no=1&format=json


3、获取歌手的所有歌曲(第四步)
    http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.artist.getSongList&format=json&tinguid=1432&offset=0&limits=30&order=2


4、排行榜
    http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=11&format=json



5、获取所有专辑(第一步)
    http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.album.getList&format=json&area=0&order=0&style=0&offset=0&limit=10&is_recommend=1



6、获取所有歌手列表 (第三步)
    http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.artist.getList&format=xml&offset=0&limit=500&area=1&order=1&sex=3&abc=0
    http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.radio.getCategoryList&format=xml
    http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.song.getInfo&format=json&songid=639512&bduss=unvalidbduss
    http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.radio.getChannelSong&channelid=3&pn=0&rn=50&format=json&key=34737226


7、获取歌手的专辑列表 (第二步)
    http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.artist.getAlbumList&format=json&tinguid=12894013&offset=0&limits=10&order=1


8、获取歌手信息
    http://http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.artist.getInfo&format=xml&tinguid=1157


9、歌词搜索 
    http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.search.lrcys&format=json&query=我的歌声里
    http://tingapi.ting.baidu.com/v1/restserver/ting?from=android&version=2.4.0&method=baidu.ting.artist.get72HotArtist&format=json&order=1&offset=0&limit=50
    http://tingapi.ting.baidu.com/v1/restserver/ting?from=android&version=2.4.0&method=baidu.ting.plaza.getPartDesc&format=json
    http://tingapi.ting.baidu.com/v1/restserver/ting?from=android&version=2.4.0&method=baidu.ting.plaza.getFocusPic&format=json&limit=111

10、歌曲文件详细信息接口
    http://tingapi.ting.baidu.com/v1/restserver/ting?from=android&version=2.4.0&method=baidu.ting.song.getInfos&format=json&songid=354387&ts=1354960702678&e=pOwOqqTY0fS5jmtSdOJBh4XW4rQHDI7EhrJgILD3Z%2FQ%3D&nw=1&bduss=



11、获取频道歌曲列表
    http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.radio.getChannelSong&format=json

12、获取专辑的歌曲列表 
    http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.radio.getArtistChannelSong&format=json

13、获取歌曲的详细信息(第三步)
    http://ting.baidu.com/data/music/links?songIds=歌曲id
    http://ting.baidu.com/data/music/links?songIds=540489965

网页云音乐
https://binaryify.github.io/NeteaseCloudMusicApi/#/?id=neteasecloudmusicapi

转自: https://linux.cn/article-9181-1.html

有时我的探索会在屏幕上输出一些奇怪的东西。比如,有一次我不小心用 cat 命令查看了一下二进制文件的内容 —— cat /sbin/*。这种情况下你将无法再访问终端里的 bash/ksh/zsh 了。大量的奇怪字符充斥了你的终端。这些字符会隐藏你输入的内容和要显示的字符,取而代之的是一些奇怪的符号。要清理掉这些屏幕上的垃圾可以使用以下方法。本文就将向你描述在 Linux/ 类 Unix 系统中如何真正清理终端屏幕或者重置终端。

clear 命令

clear 命令会清理掉屏幕内容,连带它的回滚缓存区一起也会被清理掉。(LCTT 译注:这种情况下你输入的字符回显也是乱码,不必担心,正确输入后回车即可生效。)

1
$ clear

你也可以按下 CTRL+L 来清理屏幕。然而,clear 命令并不会清理掉终端屏幕(LCTT 译注:这句话比较难理解,应该是指的运行 clear 命令并不是真正的把以前显示的内容删掉,你还是可以通过向上翻页看到之前显示的内容)。使用下面的方法才可以真正地清空终端,使你的终端恢复正常。

使用 reset 命令修复显示

要修复正常显示,只需要输入 reset 命令。它会为你再初始化一次终端:

1
$ reset

或者:

1
$ tput reset

如果 reset 命令还不行,那么输入下面命令来让绘画回复到正常状态:

1
$ stty sane

按下 CTRL + L 来清理屏幕(或者输入 clear 命令):

1
$ clear

使用 ANSI 转义序列来真正地清空 bash 终端

另一种选择是输入下面的 ANSI 转义序列:

1
2
clear
echo -e "\033c"

更多信息请阅读 stty 和 reset 的 man 页: stty(1),reset(1),bash(1)。

①将物理硬盘格式化成PV(物理卷)  使用的是 pvcreate 命令

这里我已经事先虚拟化了3快物理硬盘,每块硬盘的大小为8G,通过 fdisk -l 命令可以查看

复制代码
[root@xiaoluo ~]# fdisk -l

Disk /dev/sda: 21.5 GB, 21474836480 bytes
255 heads, 63 sectors/track, 2610 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00093d90

Device Boot Start End Blocks Id System
/dev/sda1 1 523 4194304 82 Linux swap / Solaris
Partition 1 does not end on cylinder boundary.
/dev/sda2 * 523 2611 16776192 83 Linux

Disk /dev/sdb: 8589 MB, 8589934592 bytes
255 heads, 63 sectors/track, 1044 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000

Disk /dev/sdc: 8589 MB, 8589934592 bytes
255 heads, 63 sectors/track, 1044 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000

Disk /dev/sdd: 8589 MB, 8589934592 bytes
255 heads, 63 sectors/track, 1044 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000
复制代码
这里我们根据上面图所示,我们先将 /dev/sdb、 /dev/sdc 两块硬盘格式化成PV

[root@xiaoluo ~]# pvcreate /dev/sdb /dev/sdc

Physical volume “/dev/sdb” successfully created
Physical volume “/dev/sdc” successfully created
创建完PV以后,我们可以使用pvdisplay(显示详细信息)、pvs命令来查看当前pv的信息

复制代码
[root@xiaoluo ~]# pvdisplay
“/dev/sdb” is a new physical volume of “8.00 GiB”
— NEW Physical volume —
PV Name /dev/sdb
VG Name
PV Size 8.00 GiB
Allocatable NO
PE Size 0
Total PE 0
Free PE 0
Allocated PE 0
PV UUID 93UEEl-cxBU-A4HC-LNSh-jp9G-uU5Q-EG8LM9

“/dev/sdc” is a new physical volume of “8.00 GiB”
— NEW Physical volume —
PV Name /dev/sdc
VG Name
PV Size 8.00 GiB
Allocatable NO
PE Size 0
Total PE 0
Free PE 0
Allocated PE 0
PV UUID lH1vul-KBHx-H2C6-wbt1-8AdK-yHpr-bBIul5

[root@xiaoluo ~]# pvs
PV VG Fmt Attr PSize PFree
/dev/sdb lvm2 a– 8.00g 8.00g
/dev/sdc lvm2 a– 8.00g 8.00g
复制代码
通过这两个命令我们可以看到我们已经创建好的PV的信息,两个PV都是8G,目前还没有使用,PFree都是8G.

②创建卷组(VG),并将PV加入到卷组中  通过 vgcreate 命令

在创建完PV以后,这时候我们需要创建一个VG,然后将我们的PV都加入到这个卷组当中,在创建卷组时要给该卷组起一个名字

[root@xiaoluo ~]# vgcreate xiaoluo /dev/sdb /dev/sdc

Volume group “xiaoluo” successfully created
同样,在创建好VG以后,我们也可以使用 vgdisplay 或者 vgs 命来来查看VG的信息

复制代码
[root@xiaoluo ~]# vgdisplay
— Volume group —
VG Name xiaoluo
System ID
Format lvm2
Metadata Areas 2
Metadata Sequence No 1
VG Access read/write
VG Status resizable
MAX LV 0
Cur LV 0
Open LV 0
Max PV 0
Cur PV 2  // 当前这里有两个PV,分别是我们的 /dev/sdb 和 /dev/sdc
Act PV 2
VG Size 15.99 GiB  // 当前VG的大小
PE Size 4.00 MiB  // 通过这个我们也可以看到我们LVM默认的PE大小就是4M
Total PE 4094  // 因为VG里面存放的就是各个PV中的PE,所以PE的数量就是VG大小除以默认PE的大小
Alloc PE / Size 0 / 0
Free PE / Size 4094 / 15.99 GiB
VG UUID B8eavI-21kD-Phnm-F1t1-eo4K-wgvg-T5qUbt

[root@xiaoluo ~]# vgs
VG #PV #LV #SN Attr VSize VFree
xiaoluo 2 0 0 wz–n- 15.99g 15.99g
复制代码
③基于卷组(VG)创建逻辑卷(LV)  通过 lvcreate 命令

因为创建好的PV、VG都是底层的东西,我们上层使用的是逻辑卷,所以我们要基于VG创建我们的逻辑卷才行

[root@xiaoluo ~]# lvcreate -n mylv -L 2G xiaoluo

Logical volume “mylv” created
通过 lvcreate 命令基于VG创建好我们的逻辑卷,名字为mylv,大小为2G,同样我们可以使用 lvdisplay 或者 lvs 命令来查看创建好的逻辑卷的信息

复制代码
[root@xiaoluo ~]# lvdisplay
— Logical volume —
LV Path /dev/xiaoluo/mylv  // 逻辑卷的路径
LV Name mylv  // 逻辑卷的名字
VG Name xiaoluo  // 逻辑卷所属卷组的名字
LV UUID PYuiYy-WpI6-XZB8-IhnQ-ANjM-lcz0-dlk4LR
LV Write Access read/write
LV Creation host, time xiaoluo, 2013-05-23 23:45:08 +0800
LV Status available

open 0

LV Size 2.00 GiB  // 逻辑卷的大小
Current LE 512
Segments 1
Allocation inherit
Read ahead sectors auto

  • currently set to 256
    Block device 253:0

[root@xiaoluo ~]# lvs
LV VG Attr LSize Pool Origin Data% Move Log Cpy%Sync Convert
mylv xiaoluo -wi-a—- 2.00g
复制代码
这样子我们的逻辑卷也就已经创建好了,我们这个时候再通过 vgs 还有 pvs 命令查看一下我们的PV与VG的信息

复制代码
[root@xiaoluo mnt]# vgs
VG #PV #LV #SN Attr VSize VFree
xiaoluo 2 1 0 wz–n- 15.99g 13.99g  // 我们看到LV的数量此时变成了1,因为我们刚创建好了一个LV,LVFree还有14G

[root@xiaoluo mnt]# pvs
PV VG Fmt Attr PSize PFree
/dev/sdb xiaoluo lvm2 a– 8.00g 6.00g  // 刚创建好的LV用的是 /dev/sdb 这块硬盘的,所以这块硬盘的PFree还剩下6G
/dev/sdc xiaoluo lvm2 a– 8.00g 8.00g
复制代码
我们发现,当我们每创建完一个LV时,VG与PV的信息都是时时在变化的,并且我们创建LV的大小是根据当前VG的大小来决定的,不能超过当前VG的剩余大小!

我们在上一篇随笔里面有讲过,每创建好一个逻辑卷,都会在 /dev 目录下出现一个以该卷组命名的文件夹,基于该卷组创建的所有的逻辑卷都是存放在这个文件夹下面,我们可以查看一下

[root@xiaoluo ~]# ls /dev/xiaoluo/mylv

/dev/xiaoluo/mylv
我们每创建一个新的逻辑卷,该VG目录下都会多出这么一个设备。

二、格式化并使用我们的逻辑卷

我们已经创建好了我们的PV、VG以及LV,这时候我们如果要使用逻辑卷,就必须将其格式化成我们需要用的文件系统,并将其挂载起来,然后就可以像使用分区一样去使用逻辑卷了

复制代码
[root@xiaoluo ~]# mkfs.ext4 /dev/xiaoluo/mylv

mke2fs 1.41.12 (17-May-2010)
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
131072 inodes, 524288 blocks
26214 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=536870912
16 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912

Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done

This filesystem will be automatically checked every 31 mounts or
180 days, whichever comes first. Use tune2fs -c or -i to override.
复制代码

格式化我们的逻辑卷以后,就可以使用 mount 命令将其进行挂载,我们将其挂载到 /mnt 目录下

复制代码
[root@xiaoluo ~]# mount /dev/xiaoluo/mylv /mnt

[root@xiaoluo ~]# mount
/dev/sda2 on / type ext4 (rw)
proc on /proc type proc (rw)
sysfs on /sys type sysfs (rw)
devpts on /dev/pts type devpts (rw,gid=5,mode=620)
tmpfs on /dev/shm type tmpfs (rw,rootcontext=”system_u:object_r:tmpfs_t:s0”)
none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)
sunrpc on /var/lib/nfs/rpc_pipefs type rpc_pipefs (rw)
/dev/mapper/xiaoluo-mylv on /mnt type ext4 (rw)

[root@xiaoluo ~]# cd /mnt/
[root@xiaoluo mnt]# ls
lost+found
[root@xiaoluo mnt]# touch xiaoluo.txt
[root@xiaoluo mnt]# ls
lost+found xiaoluo.txt
复制代码
我们看到,我们的卷组已经挂载好了,并且可以像使用分区一样来对其进行文件操作了。

三、删除逻辑卷

我们在创建好逻辑卷后可以通过创建文件系统,挂载逻辑卷来使用它,如果说我们不想用了也可以将其删除掉。

【注意:】对于创建物理卷、创建卷组以及创建逻辑卷我们是有严格顺序的,同样,对于删除逻辑卷、删除卷组以及删除物理卷也是有严格顺序要求的

①首先将正在使用的逻辑卷卸载掉  通过 umount 命令

②将逻辑卷先删除  通过 lvremove 命令

③删除卷组  通过 vgremove 命令

④最后再来删除我们的物理卷  通过 pvremove 命令

复制代码
[root@xiaoluo /]# mount /dev/xiaoluo/mylv /mnt/
[root@xiaoluo /]# umount /mnt/
[root@xiaoluo /]# lvremove /dev/xiaoluo/mylv
Do you really want to remove active logical volume mylv? [y/n]: y
Logical volume “mylv” successfully removed

[root@xiaoluo /]# vgremove xiaoluo
Volume group “xiaoluo” successfully removed

[root@xiaoluo /]# pvremove /dev/sdb
Labels on physical volume “/dev/sdb” successfully wiped
复制代码
此时我们的刚创建的逻辑卷 mylv,卷组 xiaoluo以及物理卷 /dev/sdb 已经从我们当前操作系统上删除掉了,通过 lvs、vgs、pvs命令可以查看一下

复制代码
[root@xiaoluo /]# lvs
No volume groups found  // 逻辑卷已经没有了

[root@xiaoluo /]# vgs
No volume groups found  // 卷组也没有了

[root@xiaoluo /]# pvs
PV VG Fmt Attr PSize PFree
/dev/sdc lvm2 a– 8.00g 8.00g  // sdb物理卷已经没有了,只剩下 sdc物理卷了

  1. 在挂载主机目录的到容器后,操作挂载的目录出现权限问题:

    1
    2
    3
    4
    5
    6
    # 将主机上的/data/share/master目录挂载到容器的/opt/share目录
    docker run -it --name=master --hostname=master -v /data/share/master:/opt/share centos-hadoop /bin/bash
    [root@master share]# pwd  #进入挂载目录
    /opt/share
    [root@master share]# touch hello  #建立新文件  
    touch: cannot touch 'hello': Permission denied  # 无权建立文件
  2. 问题原因及解决

  原因是CentOS7中的安全模块selinux把权限禁掉了,至少有以下三种方式解决挂载的目录没有权限的问题:

  2.1在运行容器的时候,给容器加特权,也就是加上 –privileged=true 参数:

  如下运行容器则无此问题:

1
docker run -it --name=master --hostname=master -v /data/share/master:/opt/share --privileged=true   centos-hadoop /bin/bash

  2.2临时关闭selinux:

1
     setenforce 0

  2.3 添加selinux规则,改变要挂载的目录的安全性文本

  关于SElinux的知识可以去看《鸟哥的linux私房菜》

1
2
3
4
5
6
7
8
# 更改安全性文本的格式如下
chcon [-R] [-t type] [-u user] [-r role] 文件或者目录

选顷不参数:
-R :连同该目录下癿次目录也同时修改;
-t :后面接安全性本文的类型字段!例如 httpd_sys_content_t ;
-u :后面接身份识别,例如 system_u;
-r :后面街觇色,例如 system_r

  根据格式我们需要更改/data/share/master的安全性文档

1
chcon -Rt svirt_sandbox_file_t /data/share/master

链接:https://www.zhihu.com/question/57013926/answer/151506606

1.Linux 内核中使用 task_struct 作为进程描述符,该结构定义在<linux/sched.h>文件中:

1
2
3
4
5
6
7
8
9
struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;
atomic_t usage;
unsigned int flags; /* per process flags, defined below */
unsigned int ptrace;
int lock_depth; /* BKL lock depth */
/* ...... */
};

可以发现 task_struct 中有一个 stack 成员,而 stack 正好用于保存内核栈地址。内核栈在进程创建时绑定在 stack 上。可以观察 fork 流程:Linux 通过 clone() 系统调用实现 fork(),然后由 fork() 去调用 do_fork()。定义在<kernel/fork.c>中的 do_fork() 负责完成进程创建的大部分工作,它通过调用 copy_process() 函数,然后让进程运行起来。copy_process() 完成了许多工作,这里重点看内核栈相关部分。copy_process() 调用 dup_task_struct 来创建内核栈、thread_infotask_struct

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static struct task_struct *dup_task_struct(struct task_struct *orig) { 
struct task_struct *tsk;
struct thread_info *ti;
unsigned long *stackend;
int err; prepare_to_copy(orig);
tsk = alloc_task_struct();
if (!tsk) return NULL;
ti = alloc_thread_info(tsk);
if (!ti) {
free_task_struct(tsk);
return NULL;
}
err = arch_dup_task_struct(tsk, orig);
if (err) goto out;
tsk->stack = ti;
err = prop_local_init_single(&tsk->dirties);
if (err) goto out;
setup_thread_stack(tsk, orig);
stackend = end_of_stack(tsk);
*stackend = STACK_END_MAGIC;
/* for overflow detection */
#ifdef CONFIG_CC_STACKPROTECTOR
tsk->stack_canary = get_random_int();
#endif
/* One for us, one for whoever does the "release_task()"
(usually parent) */
atomic_set(&tsk->usage,2);
atomic_set(&tsk->fs_excl, 0);
#ifdef CONFIG_BLK_DEV_IO_TRACE
tsk->btrace_seq = 0;
#endif
tsk->splice_pipe = NULL;
account_kernel_stack(ti, 1);
return tsk;
out:
free_thread_info(ti);
free_task_struct(tsk);
return NULL;
}

其中重点是下面部分:

1
2
3
4
5
6
7
8
9
10
tsk = alloc_task_struct(); 
if (!tsk) return NULL;
ti = alloc_thread_info(tsk);
if (!ti) {
free_task_struct(tsk);
return NULL;
}
err = arch_dup_task_struct(tsk, orig);
if (err) goto out;
tsk->stack = ti;

这里可以看到内核栈的创建过程。可能会疑惑为何 stack 指向了 thread_info,那是因为在2.6以前的内核中,各个进程的 task_struct 存放在内核栈的尾端,这样做是为了在寄存器较少的体系结构中直接使用栈指针加偏移就可以算出它的位置。2.6以后使用slab分配器动态分配 task_struct ,所以只需要在栈顶创建一个 thread_info 记录 task_struct 的地址。所以这里回答了第一个问题, 每个进程都有一个单独的内核栈。

2.从内核模块编程的角度看(不涉及用户态进程),内核栈该怎么理解?和用户进程进行系统调用使用的栈空间有什么不同?

每个进程运行时都持有上下文,用于保证并行性。为了保证内核和用户态隔离,陷入内核不影响用户态,所以使用了不同的栈。内核栈只是对内核态上下文中的栈的称谓。为了方便管理用户程序,限制用户程序权限,所以区分了内核态和用户态。内核态中拥有高特权级,能够执行io等特权指令,而用户态程序想要执行特权级指令则必须陷入内核态。从用户程序角度来看,内核更类似与库文件的存在。内核通过虚拟地址访问权限来限制用户程序访问内存地址,比如内核空间的代码和数据不应该被用户程序访问到。因此内核运行时使用的栈不应该能被用户态代码访问到,否则用户态代码完全可以通过构造特定的数据控制内核(参考ret2libc)。因此,用户态使用的栈空间和内核栈并无本质区别,它们均处于同一块页表映射中,内核栈处于高特权级访问限制的虚拟地址中,防止用户态代码访问内核数据。

3.怎么理解linux内核栈空间只有4KB或8KB,linux内核编程中的堆(heap)和栈(stack)有什么区别?

内核中的资源是非常宝贵的,而一个比较大的栈空间多数时间是浪费了。那为何不设计小一点,然后保证内核调用层次低、局部变量小,做到不溢出?而内核编程中的堆和栈并非通常写程序时所说的堆和栈有严格的区分。

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
26
27
28
29
30
    最高内存地址
+-------------------+
| 堆栈段 |
| | |
| | |
| | |
| v |
| |
| |
| |





| |
| ^ |
| | |
| | |
| | |
| 堆 |
|-------------------|
| BSS段 |
| |
| |
| |
| 数据段 |
|-------------------|
| 代码段 |
+-------------------+

内核中的堆和栈没有严格的地址区分,只是程序角度的不同解释而已。

mysql大多数磁盘空间被 InnoDB 的共享表空间 ibdata1 使用。而你已经启用了 innodb_file_per_table,所以问题是:

ibdata1存了什么?

当你启用了innodb_file_per_table,表被存储在他们自己的表空间里,但是共享表空间仍然在存储其它的 InnoDB 内部数据:

  • 数据字典,也就是 InnoDB 表的元数据
  • 变更缓冲区
  • 双写缓冲区
  • 撤销日志

其中的一些在 Percona 服务器上可以被配置来避免增长过大的。例如你可以通过 innodb_ibuf_max_size 设置最大变更缓冲区,或设置 innodb_doublewrite_file来将双写缓冲区存储到一个分离的文件。

MySQL 5.6 版中你也可以创建外部的撤销表空间,所以它们可以放到自己的文件来替代存储到 ibdata1。可以看看这个文档

什么引起 ibdata1 增长迅速?

当 MySQL 出现问题通常我们需要执行的第一个命令是:

1
SHOW ENGINE INNODB STATUS/G

这将展示给我们一些很有价值的信息。我们从** TRANSACTION(事务)**部分开始检查,然后我们会发现这个:

1
2
3
4
---TRANSACTION 36E, ACTIVE 1256288 sec
MySQL thread id 42, OS thread handle 0x7f8baaccc700, query id 7900290 localhost root
show engine innodb status
Trx read view will not see trx with id >= 36F, sees < 36F

这是一个最常见的原因,一个14天前创建的相当老的事务。这个状态是活动的,这意味着 InnoDB 已经创建了一个数据的快照,所以需要在撤销日志中维护旧页面,以保障数据库的一致性视图,直到事务开始。如果你的数据库有大量的写入任务,那就意味着存储了大量的撤销页。

如果你找不到任何长时间运行的事务,你也可以监控INNODB STATUS 中的其他的变量,“History list length(历史记录列表长度)”展示了一些等待清除操作。这种情况下问题经常发生,因为清除线程(或者老版本的主线程)不能像这些记录进来的速度一样快地处理撤销。

我怎么检查什么被存储到了 ibdata1 里了?

很不幸,MySQL 不提供查看什么被存储到 ibdata1 共享表空间的信息,但是有两个工具将会很有帮助。第一个是马克·卡拉汉制作的一个修改版 innochecksum ,它发布在这个漏洞报告里。

它相当易于使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ./innochecksum /var/lib/mysql/ibdata1
0 bad checksum
13 FIL_PAGE_INDEX
19272 FIL_PAGE_UNDO_LOG
230 FIL_PAGE_INODE
1 FIL_PAGE_IBUF_FREE_LIST
892 FIL_PAGE_TYPE_ALLOCATED
2 FIL_PAGE_IBUF_BITMAP
195 FIL_PAGE_TYPE_SYS
1 FIL_PAGE_TYPE_TRX_SYS
1 FIL_PAGE_TYPE_FSP_HDR
1 FIL_PAGE_TYPE_XDES
0 FIL_PAGE_TYPE_BLOB
0 FIL_PAGE_TYPE_ZBLOB
0 other
3 max index_id

全部的 20608 中有 19272 个撤销日志页。这占用了表空间的 93%。

第二个检查表空间内容的方式是杰里米·科尔制作的 InnoDB Ruby 工具。它是个检查 InnoDB 的内部结构的更先进的工具。例如我们可以使用 space-summary 参数来得到每个页面及其数据类型的列表。我们可以使用标准的 Unix 工具来统计撤销日志页的数量:

1
2
# innodb_space -f /var/lib/mysql/ibdata1 space-summary | grep UNDO_LOG | wc -l
19272

尽管这种特殊的情况下,innochedcksum 更快更容易使用,但是我推荐你使用杰里米的工具去了解更多的 InnoDB 内部的数据分布及其内部结构。

好,现在我们知道问题所在了。下一个问题:

我该怎么解决问题?

这个问题的答案很简单。如果你还能提交语句,就做吧。如果不能的话,你必须要杀掉线程开始回滚过程。那将停止 ibdata1 的增长,但是很显然,你的软件会出现漏洞,有些人会遇到错误。现在你知道如何去鉴定问题所在,你需要使用你自己的调试工具或普通的查询日志来找出谁或者什么引起的问题。

如果问题发生在清除线程,解决方法通常是升级到新版本,新版中使用一个独立的清除线程替代主线程。更多信息查看该文档

有什么方法回收已使用的空间么?

没有,目前还没有一个容易并且快速的方法。InnoDB 表空间从不收缩…参见10 年之久的漏洞报告,最新更新自詹姆斯·戴(谢谢):

当你删除一些行,这个页被标为已删除稍后重用,但是这个空间从不会被回收。唯一的方法是使用新的 ibdata1 启动数据库。要做这个你应该需要使用 mysqldump 做一个逻辑全备份,然后停止 MySQL 并删除所有数据库、ib_logfile、ibdata1 文件。当你再启动 MySQL 的时候将会创建一个新的共享表空间。然后恢复逻辑备份。

总结

当 ibdata1 文件增长太快,通常是 MySQL 里长时间运行的被遗忘的事务引起的。尝试去解决问题越快越好(提交或者杀死事务),因为不经过痛苦缓慢的 mysqldump 过程,你就不能回收浪费的磁盘空间。

也是非常推荐监控数据库以避免这些问题。我们的 MySQL 监控插件包括一个 Nagios 脚本,如果发现了一个太老的运行事务它可以提醒你。

随着网络的逐步普及,网络安全已成为INTERNET路上事实上的焦点,它关系着INTERNET的进一步发展和普及,甚至关系着INTERNET的生存。可喜的是我们那些互联网专家们并没有令广大INTERNET用户失望,网络安全技术也不断出现,使广大网民和企业有了更多的放心,下面就网络安全中的主要技术作一简介,希望能为网民和企业在网络安全方面提供一个网络安全方案参考。

DNS的工作原理

  DNS分为Client和Server,Client扮演发问的角色,也就是问Server一个Domain Name,而Server必须要回答此Domain Name的真正IP地址。而当地的DNS先会查自己的资料库。如果自己的资料库没有,则会往该DNS上所设的的DNS询问,依此得到答案之后,将收到的答案存起来,并回答客户。

  DNS服务器会根据不同的授权区(Zone),记录所属该网域下的各名称资料,这个资料包括网域下的次网域名称及主机名称。

  在每一个名称服务器中都有一个快取缓存区(Cache),这个快取缓存区的主要目的是将该名称服务器所查询出来的名称及相对的IP地址记录在快取缓存区中,这样当下一次还有另外一个客户端到次服务器上去查询相同的名称 时,服务器就不用在到别台主机上去寻找,而直接可以从缓存区中找到该笔名称记录资料,传回给客户端,加速客户端对名称查询的速度。例如:

  当DNS客户端向指定的DNS服务器查询网际网路上的某一台主机名称 DNS服务器会在该资料库中找寻用户所指定的名称 如果没有,该服务器会先在自己的快取缓存区中查询有无该笔纪录,如果找到该笔名称记录后,会从DNS服务器直接将所对应到的IP地址传回给客户端 ,如果名称服务器在资料记录查不到且快取缓存区中也没有时,服务器首先会才会向别的名称服务器查询所要的名称。例如:

  DNS客户端向指定的DNS服务器查询网际网路上某台主机名称,当DNS服务器在该资料记录找不到用户所指定的名称时,会转向该服务器的快取缓存区找寻是否有该资料 ,当快取缓存区也找不到时,会向最接近的名称服务器去要求帮忙找寻该名称的IP地址 ,在另一台服务器上也有相同的动作的查询,当查询到后会回复原本要求查询的服务器,该DNS服务器在接收到另一台DNS服务器查询的结果后,先将所查询到的主机名称及对应IP地址记录到快取缓存区中 ,最后在将所查询到的结果回复给客户端

常见的DNS攻击

1) 域名劫持

  通过采用黑客手段控制了域名管理密码和域名管理邮箱,然后将该域名的NS纪录指向到黑客可以控制的DNS服务器,然后通过在该DNS服务器上添加相应域名纪录,从而使网民访问该域名时,进入了黑客所指向的内容。

  这显然是DNS服务提供商的责任,用户束手无策。

2) 缓存投毒

  利用控制DNS缓存服务器,把原本准备访问某网站的用户在不知不觉中带到黑客指向的其他网站上。其实现方式有多种,比如可以通过利用网民ISP端的DNS缓存服务器的漏洞进行攻击或控制,从而改变该ISP内的用户访问域名的响应结果;或者,黑客通过利用用户权威域名服务器上的漏洞,如当用户权威域名服务器同时可以被当作缓存服务器使用,黑客可以实现缓存投毒,将错误的域名纪录存入缓存中,从而使所有使用该缓存服务器的用户得到错误的DNS解析结果。

  最近发现的DNS重大缺陷,就是这种方式的。只所以说是“重大”缺陷,据报道是因为是协议自身的设计实现问题造成的,几乎所有的DNS软件都存在这样的问题。

3)DDOS攻击

  一种攻击针对DNS服务器软件本身,通常利用BIND软件程序中的漏洞,导致DNS服务器崩溃或拒绝服务;另一种攻击的目标不是DNS服务器,而是利用DNS服务器作为中间的“攻击放大器”,去攻击其它互联网上的主机,导致被攻击主机拒绝服务。

4)DNS欺骗

  DNS欺骗就是攻击者冒充域名服务器的一种欺骗行为。

  原理:如果可以冒充域名服务器,然后把查询的IP地址设为攻击者的IP地址,这样的话,用户上网就只能看到攻击者的主页,而不是用户想要取得的网站的主页了,这就是DNS欺骗的基本原理。DNS欺骗其实并不是真的“黑掉”了对方的网站,而是冒名顶替、招摇撞骗罢了。

防止DNS被攻击的若干防范性措施

  互联网上的DNS放大攻击(DNS amplification attacks)急剧增长。这种攻击是一种数据包的大量变体能够产生针对一个目标的大量的虚假的通讯。这种虚假通讯的数量有多大?每秒钟达数GB,足以阻止任何人进入互联网。

  与老式的“smurf attacks”攻击非常相似,DNS放大攻击使用针对无辜的第三方的欺骗性的数据包来放大通讯量,其目的是耗尽受害者的全部带宽。但是,“smurf attacks”攻击是向一个网络广播地址发送数据包以达到放大通讯的目的。DNS放大攻击不包括广播地址。相反,这种攻击向互联网上的一系列无辜的第三方DNS服务器发送小的和欺骗性的询问信息。这些DNS服务器随后将向表面上是提出查询的那台服务器发回大量的回复,导致通讯量的放大并且最终把攻击目标淹没。因为DNS是以无状态的UDP数据包为基础的,采取这种欺骗方式是司空见惯的。

  这种攻击主要依靠对DNS实施60个字节左右的查询,回复最多可达512个字节,从而使通讯量放大8.5倍。这对于攻击者来说是不错的,但是,仍没有达到攻击者希望得到了淹没的水平。最近,攻击者采用了一些更新的技术把目前的DNS放大攻击提高了好几倍。

  当前许多DNS服务器支持EDNS。EDNS是DNS的一套扩大机制,RFC 2671对次有介绍。一些选择能够让DNS回复超过512字节并且仍然使用UDP,如果要求者指出它能够处理这样大的DNS查询的话。攻击者已经利用这种方法产生了大量的通讯。通过发送一个60个字节的查询来获取一个大约4000个字节的记录,攻击者能够把通讯量放大66倍。一些这种性质的攻击已经产生了每秒钟许多GB的通讯量,对于某些目标的攻击甚至超过了每秒钟10GB的通讯量。

  要实现这种攻击,攻击者首先要找到几台代表互联网上的某个人实施循环查询工作的第三方DNS服务器(大多数DNS服务器都有这种设置)。由于支持循环查询,攻击者可以向一台DNS服务器发送一个查询,这台DNS服务器随后把这个查询(以循环的方式)发送给攻击者选择的一台DNS服务器。接下来,攻击者向这些服务器发送一个DNS记录查询,这个记录是攻击者在自己的DNS服务器上控制的。由于这些服务器被设置为循环查询,这些第三方服务器就向攻击者发回这些请求。攻击者在DNS服务器上存储了一个4000个字节的文本用于进行这种DNS放大攻击。

  现在,由于攻击者已经向第三方DNS服务器的缓存中加入了大量的记录,攻击者接下来向这些服务器发送DNS查询信息(带有启用大量回复的EDNS选项),并采取欺骗手段让那些DNS服务器认为这个查询信息是从攻击者希望攻击的那个IP地址发出来的。这些第三方DNS服务器于是就用这个4000个字节的文本记录进行回复,用大量的UDP数据包淹没受害者。攻击者向第三方DNS服务器发出数百万小的和欺骗性的查询信息,这些DNS服务器将用大量的DNS回复数据包淹没那个受害者。

  如何防御这种大规模攻击呢?首先,保证你拥有足够的带宽承受小规模的洪水般的攻击。一个单一的T1线路对于重要的互联网连接是不够的,因为任何恶意的脚本少年都可以消耗掉你的带宽。如果你的连接不是执行重要任务的,一条T1线路就够了。否则,你就需要更多的带宽以便承受小规模的洪水般的攻击。不过,几乎任何人都无法承受每秒钟数GB的DNS放大攻击。

  因此,你要保证手边有能够与你的ISP随时取得联系的应急电话号码。这样,一旦发生这种攻击,你可以马上与ISP联系,让他们在上游过滤掉这种攻击。要识别这种攻击,你要查看包含DNS回复的大量通讯(源UDP端口53),特别是要查看那些拥有大量DNS记录的端口。一些ISP已经在其整个网络上部署了传感器以便检测各种类型的早期大量通讯。这样,你的ISP很可能在你发现这种攻击之前就发现和避免了这种攻击。你要问一下你的ISP是否拥有这个能力。

  最后,为了帮助阻止恶意人员使用你的DNS服务器作为一个实施这种DNS放大攻击的代理,你要保证你的可以从外部访问的DNS服务器仅为你自己的网络执行循环查询,不为任何互联网上的地址进行这种查询。大多数主要DNS服务器拥有限制循环查询的能力,因此,它们仅接受某些网络的查询,比如你自己的网络。通过阻止利用循环查询装载大型有害的DNS记录,你就可以防止你的DNS服务器成为这个问题的一部分。

https://github.com/MintCN/linux-insides-zh

介绍

我不会告诉你怎么在自己的电脑上去构建、安装一个定制化的 Linux 内核,这样的资料太多了,它们会对你有帮助。本文会告诉你当你在内核源码路径里敲下 make 时会发生什么。

当我刚刚开始学习内核代码时,Makefile 是我打开的第一个文件,这个文件看起来真令人害怕 :)。那时候这个 Makefile 还只包含了 1591 行代码,当我开始写本文时,内核已经是 4.2.0 的第三个候选版本 了。

这个 Makefile 是 Linux 内核代码的根 Makefile ,内核构建就始于此处。是的,它的内容很多,但是如果你已经读过内核源代码,你就会发现每个包含代码的目录都有一个自己的 Makefile 。当然了,我们不会去描述每个代码文件是怎么编译链接的,所以我们将只会挑选一些通用的例子来说明问题。而你不会在这里找到构建内核的文档、如何整洁内核代码、 tags 的生成和交叉编译 相关的说明,等等。我们将从 make 开始,使用标准的内核配置文件,到生成了内核镜像 bzImage 结束。

如果你已经很了解 make 工具,那是最好,但是我也会描述本文出现的相关代码。

让我们开始吧!

编译内核前的准备

在开始编译前要进行很多准备工作。最主要的就是找到并配置好配置文件,make 命令要使用到的参数都需要从这些配置文件获取。现在就让我们深入内核的根 Makefile 吧。

内核的根 Makefile 负责构建两个主要的文件: vmlinux (内核镜像可执行文件)和模块文件。内核的 Makefile 从定义如下变量开始:

1
2
3
4
5
VERSION = 4
PATCHLEVEL = 2
SUBLEVEL = 0
EXTRAVERSION = -rc3
NAME = Hurr durr I'ma sheep

这些变量决定了当前内核的版本,并且被使用在很多不同的地方,比如同一个 Makefile 中的 KERNELVERSION

1
KERNELVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION)

接下来我们会看到很多 ifeq 条件判断语句,它们负责检查传递给 make 的参数。内核的 Makefile 提供了一个特殊的编译选项 make help ,这个选项可以生成所有的可用目标和一些能传给 make 的有效的命令行参数。举个例子,make V=1 会在构建过程中输出详细的编译信息,第一个 ifeq 就是检查传递给 makeV=n 选项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ifeq ("$(origin V)", "command line")
KBUILD_VERBOSE = $(V)
endif
ifndef KBUILD_VERBOSE
KBUILD_VERBOSE = 0
endif

ifeq ($(KBUILD_VERBOSE),1)
quiet =
Q =
else
quiet=quiet_
Q = @
endif

export quiet Q KBUILD_VERBOSE

如果 V=n 这个选项传给了 make ,系统就会给变量 KBUILD_VERBOSE 选项附上 V 的值,否则的话, KBUILD_VERBOSE 就会为 0 。然后系统会检查 KBUILD_VERBOSE 的值,以此来决定 quietQ 的值。符号 @ 控制命令的输出,如果它被放在一个命令之前,这条命令的输出将会是 CC scripts/mod/empty.o ,而不是 Compiling .... scripts/mod/empty.o(LCTT 译注:CC 在 Makefile 中一般都是编译命令)。在这段最后,系统导出了所有的变量。

下一个 ifeq 语句检查的是传递给 make 的选项 O=/dir,这个选项允许在指定的目录 dir 输出所有的结果文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ifeq ($(KBUILD_SRC),)

ifeq ("$(origin O)", "command line")
KBUILD_OUTPUT := $(O)
endif

ifneq ($(KBUILD_OUTPUT),)
saved-output := $(KBUILD_OUTPUT)
KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \
&& /bin/pwd)
$(if $(KBUILD_OUTPUT),, \
$(error failed to create output directory "$(saved-output)"))

sub-make: FORCE
$(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR) \
-f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS))

skip-makefile := 1
endif # ifneq ($(KBUILD_OUTPUT),)
endif # ifeq ($(KBUILD_SRC),)

系统会检查变量 KBUILD_SRC ,它代表内核代码的顶层目录,如果它是空的(第一次执行 makefile 时总是空的),我们会设置变量 KBUILD_OUTPUT 为传递给选项 O 的值(如果这个选项被传进来了)。下一步会检查变量 KBUILD_OUTPUT ,如果已经设置好,那么接下来会做以下几件事:

  • 将变量 KBUILD_OUTPUT 的值保存到临时变量 saved-output
  • 尝试创建给定的输出目录;
  • 检查创建的输出目录,如果失败了就打印错误;
  • 如果成功创建了输出目录,那么就在新目录重新执行 make 命令(参见选项-C)。

下一个 ifeq 语句会检查传递给 make 的选项 CM

1
2
3
4
5
6
7
8
9
10
ifeq ("$(origin C)", "command line")
KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
KBUILD_CHECKSRC = 0
endif

ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endif

第一个选项 C 会告诉 Makefile 需要使用环境变量 $CHECK 提供的工具来检查全部 c 代码,默认情况下会使用 sparse 。第二个选项 M 会用来编译外部模块(本文不做讨论)。

系统还会检查变量 KBUILD_SRC,如果 KBUILD_SRC 没有被设置,系统会设置变量 srctree.

1
2
3
4
5
6
7
8
9
ifeq ($(KBUILD_SRC),)
srctree := .
endif

objtree := .
src := $(srctree)
obj := $(objtree)

export srctree objtree VPATH

这将会告诉 Makefile 内核的源码树就在执行 make 命令的目录,然后要设置 objtree 和其他变量为这个目录,并且将这些变量导出。接着就是要获取 SUBARCH 的值,这个变量代表了当前的系统架构(LCTT 译注:一般都指CPU 架构):

1
2
3
4
5
6
SUBARCH := $(shell uname -m | sed -e s/i.86/x86/ -e s/x86_64/x86/ \
-e s/sun4u/sparc64/ \
-e s/arm.*/arm/ -e s/sa110/arm/ \
-e s/s390x/s390/ -e s/parisc64/parisc/ \
-e s/ppc.*/powerpc/ -e s/mips.*/mips/ \
-e s/sh[234].*/sh/ -e s/aarch64.*/arm64/ )

如你所见,系统执行 uname 得到机器、操作系统和架构的信息。因为我们得到的是 uname 的输出,所以我们需要做一些处理再赋给变量 SUBARCH 。获得 SUBARCH 之后就要设置SRCARCHhfr-archSRCARCH 提供了硬件架构相关代码的目录,hfr-arch 提供了相关头文件的目录:

1
2
3
4
5
6
7
8
ifeq ($(ARCH),i386)
SRCARCH := x86
endif
ifeq ($(ARCH),x86_64)
SRCARCH := x86
endif

hdr-arch := $(SRCARCH)

注意: ARCHSUBARCH 的别名。如果没有设置过代表内核配置文件路径的变量 KCONFIG_CONFIG ,下一步系统会设置它,默认情况下就是 .config

1
2
KCONFIG_CONFIG	?= .config
export KCONFIG_CONFIG

以及编译内核过程中要用到的 shell

1
2
3
CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
else if [ -x /bin/bash ]; then echo /bin/bash; \
else echo sh; fi ; fi)

接下来就要设置一组和编译内核的编译器相关的变量。我们会设置主机的 CC++ 的编译器及相关配置项:

1
2
3
4
HOSTCC       = gcc
HOSTCXX = g++
HOSTCFLAGS = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89
HOSTCXXFLAGS = -O2

接下来会去适配代表编译器的变量 CC,那为什么还要 HOST* 这些变量呢?这是因为 CC 是编译内核过程中要使用的目标架构的编译器,但是 HOSTCC 是要被用来编译一组 host 程序的(下面我们就会看到)。

然后我们就看到变量 KBUILD_MODULESKBUILD_BUILTIN 的定义,这两个变量决定了我们要编译什么东西(内核、模块或者两者):

1
2
3
4
5
6
KBUILD_MODULES :=
KBUILD_BUILTIN := 1

ifeq ($(MAKECMDGOALS),modules)
KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1)
endif

在这我们可以看到这些变量的定义,并且,如果们仅仅传递了 modulesmake,变量 KBUILD_BUILTIN 会依赖于内核配置选项 CONFIG_MODVERSIONS

下一步操作是引入下面的文件:

1
include scripts/Kbuild.include

文件 Kbuild 或者又叫做 Kernel Build System 是一个用来管理构建内核及其模块的特殊框架。Kbuild 文件的语法与 Makefile 一样。文件 scripts/Kbuild.includeKbuild 系统提供了一些常规的定义。因为我们包含了这个 Kbuild 文件,我们可以看到和不同工具关联的这些变量的定义,这些工具会在内核和模块编译过程中被使用(比如链接器、编译器、来自 binutils 的二进制工具包):

1
2
3
4
5
6
7
8
9
10
11
12
13
AS		= $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
AWK = awk
...
...
...

在这些定义好的变量后面,我们又定义了两个变量: USERINCLUDELINUXINCLUDE 。他们包含了头文件的路径(第一个是给用户用的,第二个是给内核用的):

1
2
3
4
5
6
7
8
9
10
USERINCLUDE    := \
-I$(srctree)/arch/$(hdr-arch)/include/uapi \
-Iarch/$(hdr-arch)/include/generated/uapi \
-I$(srctree)/include/uapi \
-Iinclude/generated/uapi \
-include $(srctree)/include/linux/kconfig.h

LINUXINCLUDE := \
-I$(srctree)/arch/$(hdr-arch)/include \
...

以及给 C 编译器的标准标志:

1
2
3
4
5
KBUILD_CFLAGS   := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
-fno-strict-aliasing -fno-common \
-Werror-implicit-function-declaration \
-Wno-format-security \
-std=gnu89

这并不是最终确定的编译器标志,它们还可以在其他 Makefile 里面更新(比如 arch/ 里面的 Kbuild )。变量定义完之后,全部会被导出供其他 Makefile 使用。

下面的两个变量 RCS_FIND_IGNORERCS_TAR_IGNORE 包含了被版本控制系统忽略的文件:

1
2
3
4
5
export RCS_FIND_IGNORE := \( -name SCCS -o -name BitKeeper -o -name .svn -o    \
-name CVS -o -name .pc -o -name .hg -o -name .git \) \
-prune -o
export RCS_TAR_IGNORE := --exclude SCCS --exclude BitKeeper --exclude .svn \
--exclude CVS --exclude .pc --exclude .hg --exclude .git

这就是全部了,我们已经完成了所有的准备工作,下一个点就是如何构建 vmlinux

###直面内核构建

现在我们已经完成了所有的准备工作,根 Makefile(注:内核根目录下的 Makefile )的下一步工作就是和编译内核相关的了。在这之前,我们不会在终端看到 make 命令输出的任何东西。但是现在编译的第一步开始了,这里我们需要从内核根 Makefile 的 598 行开始,这里可以看到目标 vmlinux

1
2
all: vmlinux
include arch/$(SRCARCH)/Makefile

不要操心我们略过的从 export RCS_FIND_IGNORE.....all: vmlinux..... 这一部分 Makefile 代码,他们只是负责根据各种配置文件(make *.config)生成不同目标内核的,因为之前我就说了这一部分我们只讨论构建内核的通用途径。

目标 all: 是在命令行如果不指定具体目标时默认使用的目标。你可以看到这里包含了架构相关的 Makefile(在这里就指的是 arch/x86/Makefile )。从这一时刻起,我们会从这个 Makefile 继续进行下去。如我们所见,目标 all 依赖于根 Makefile 后面声明的 vmlinux

1
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE

vmlinux 是 linux 内核的静态链接可执行文件格式。脚本 scripts/link-vmlinux.sh 把不同的编译好的子模块链接到一起形成了 vmlinux 。

第二个目标是 vmlinux-deps ,它的定义如下:

1
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)

它是由内核代码下的每个顶级目录的 built-in.o 组成的。之后我们还会检查内核所有的目录,Kbuild 会编译各个目录下所有的对应 $(obj-y) 的源文件。接着调用 $(LD) -r 把这些文件合并到一个 build-in.o 文件里。此时我们还没有 vmlinux-deps,所以目标 vmlinux 现在还不会被构建。对我而言 vmlinux-deps 包含下面的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
arch/x86/kernel/vmlinux.lds arch/x86/kernel/head_64.o
arch/x86/kernel/head64.o arch/x86/kernel/head.o
init/built-in.o usr/built-in.o
arch/x86/built-in.o kernel/built-in.o
mm/built-in.o fs/built-in.o
ipc/built-in.o security/built-in.o
crypto/built-in.o block/built-in.o
lib/lib.a arch/x86/lib/lib.a
lib/built-in.o arch/x86/lib/built-in.o
drivers/built-in.o sound/built-in.o
firmware/built-in.o arch/x86/pci/built-in.o
arch/x86/power/built-in.o arch/x86/video/built-in.o
net/built-in.o

下一个可以被执行的目标如下:

1
2
3
$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;
$(vmlinux-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@

就像我们看到的,vmlinux-dir 依赖于两部分: preparescripts 。第一个 prepare 定义在内核的根 Makefile 中,准备工作分成三个阶段:

1
2
3
4
5
6
7
8
9
prepare: prepare0
prepare0: archprepare FORCE
$(Q)$(MAKE) $(build)=.
archprepare: archheaders archscripts prepare1 scripts_basic

prepare1: prepare2 $(version_h) include/generated/utsrelease.h \
include/config/auto.conf
$(cmd_crmodverdir)
prepare2: prepare3 outputmakefile asm-generic

第一个 prepare0 展开到 archprepare ,后者又展开到 archheaderarchscripts ,这两个变量定义在 x86_64 相关的 Makefile 。让我们看看这个文件。x86_64 特定的 Makefile 从变量定义开始,这些变量都是和特定架构的配置文件 (defconfig,等等)有关联。在定义了编译 16-bit 代码的编译选项之后,根据变量 BITS 的值,如果是 32 ,汇编代码、链接器、以及其它很多东西(全部的定义都可以在arch/x86/Makefile找到)对应的参数就是 i386,而 64 就对应的是 x86_84

第一个目标是 Makefile 生成的系统调用列表(syscall table)中的 archheaders

1
2
archheaders:
$(Q)$(MAKE) $(build)=arch/x86/entry/syscalls all

第二个目标是 Makefile 里的 archscripts

1
2
archscripts: scripts_basic
$(Q)$(MAKE) $(build)=arch/x86/tools relocs

我们可以看到 archscripts 是依赖于根 Makefile 里的 scripts_basic 。首先我们可以看出 scripts_basic 是按照 scripts/basic 的 Makefile 执行 make 的:

1
2
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic

scripts/basic/Makefile 包含了编译两个主机程序 fixdepbin2 的目标:

1
2
3
4
5
hostprogs-y	:= fixdep
hostprogs-$(CONFIG_BUILD_BIN2C) += bin2c
always := $(hostprogs-y)

$(addprefix $(obj)/,$(filter-out fixdep,$(always))): $(obj)/fixdep

第一个工具是 fixdep :用来优化 gcc 生成的依赖列表,然后在重新编译源文件的时候告诉 make 。第二个工具是 bin2c ,它依赖于内核配置选项 CONFIG_BUILD_BIN2C ,并且它是一个用来将标准输入接口(LCTT 译注:即 stdin)收到的二进制流通过标准输出接口(即:stdout)转换成 C 头文件的非常小的 C 程序。你可能注意到这里有些奇怪的标志,如 hostprogs-y 等。这个标志用于所有的 Kbuild 文件,更多的信息你可以从 documentation 获得。在我们这里, hostprogs-y 告诉 Kbuild 这里有个名为 fixed 的程序,这个程序会通过和 Makefile 相同目录的 fixdep.c 编译而来。

执行 make 之后,终端的第一个输出就是 Kbuild 的结果:

1
2
$ make
HOSTCC scripts/basic/fixdep

当目标 script_basic 被执行,目标 archscripts 就会 make arch/x86/tools 下的 Makefile 和目标 relocs :

1
$(Q)$(MAKE) $(build)=arch/x86/tools relocs

包含了重定位的信息的代码 relocs_32.crelocs_64.c 将会被编译,这可以在 make 的输出中看到:

1
2
3
4
HOSTCC  arch/x86/tools/relocs_32.o
HOSTCC arch/x86/tools/relocs_64.o
HOSTCC arch/x86/tools/relocs_common.o
HOSTLD arch/x86/tools/relocs

在编译完 relocs.c 之后会检查 version.h

1
2
3
$(version_h): $(srctree)/Makefile FORCE
$(call filechk,version.h)
$(Q)rm -f $(old_version_h)

我们可以在输出看到它:

1
CHK     include/config/kernel.release

以及在内核的根 Makefile 使用 arch/x86/include/generated/asm 的目标 asm-generic 来构建 generic 汇编头文件。在目标 asm-generic 之后,archprepare 就完成了,所以目标 prepare0 会接着被执行,如我上面所写:

1
2
prepare0: archprepare FORCE
$(Q)$(MAKE) $(build)=.

注意 build ,它是定义在文件 scripts/Kbuild.include ,内容是这样的:

1
build := -f $(srctree)/scripts/Makefile.build obj

或者在我们的例子中,它就是当前源码目录路径- .

1
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=.

脚本 scripts/Makefile.build 通过参数 obj 给定的目录找到 Kbuild 文件,然后引入 Kbuild 文件:

1
include $(kbuild-file)

并根据这个构建目标。我们这里 . 包含了生成 kernel/bounds.sarch/x86/kernel/asm-offsets.sKbuild 文件。在此之后,目标 prepare 就完成了它的工作。 vmlinux-dirs 也依赖于第二个目标 scripts ,它会编译接下来的几个程序: filealiasmk_elfconfigmodpost 等等。之后, scripts/host-programs 就可以开始编译我们的目标 vmlinux-dirs 了。

首先,我们先来理解一下 vmlinux-dirs 都包含了那些东西。在我们的例子中它包含了下列内核目录的路径:

1
2
3
init usr arch/x86 kernel mm fs ipc security crypto block
drivers sound firmware arch/x86/pci arch/x86/power
arch/x86/video net lib arch/x86/lib

我们可以在内核的根 Makefile 里找到 vmlinux-dirs 的定义:

1
2
3
4
5
6
7
8
9
10
11
vmlinux-dirs	:= $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
$(core-y) $(core-m) $(drivers-y) $(drivers-m) \
$(net-y) $(net-m) $(libs-y) $(libs-m)))

init-y := init/
drivers-y := drivers/ sound/ firmware/
net-y := net/
libs-y := lib/
...
...
...

这里我们借助函数 patsubstfilter 去掉了每个目录路径里的符号 / ,并且把结果放到 vmlinux-dirs 里。所以我们就有了 vmlinux-dirs 里的目录列表,以及下面的代码:

1
2
$(vmlinux-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@

符号 $@ 在这里代表了 vmlinux-dirs ,这就表明程序会递归遍历从 vmlinux-dirs 以及它内部的全部目录(依赖于配置),并且在对应的目录下执行 make 命令。我们可以在输出看到结果:

1
2
3
4
5
6
7
8
9
10
11
12
CC      init/main.o
CHK include/generated/compile.h
CC init/version.o
CC init/do_mounts.o
...
CC arch/x86/crypto/glue_helper.o
AS arch/x86/crypto/aes-x86_64-asm_64.o
CC arch/x86/crypto/aes_glue.o
...
AS arch/x86/entry/entry_64.o
AS arch/x86/entry/thunk_64.o
CC arch/x86/entry/syscall_64.o

每个目录下的源代码将会被编译并且链接到 built-io.o 里:

1
2
3
4
5
6
7
8
$ find . -name built-in.o
./arch/x86/crypto/built-in.o
./arch/x86/crypto/sha-mb/built-in.o
./arch/x86/net/built-in.o
./init/built-in.o
./usr/built-in.o
...
...

好了,所有的 built-in.o 都构建完了,现在我们回到目标 vmlinux 上。你应该还记得,目标 vmlinux 是在内核的根 Makefile 里。在链接 vmlinux 之前,系统会构建 samples , Documentation 等等,但是如上文所述,我不会在本文描述这些。

1
2
3
4
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
...
...
+$(call if_changed,link-vmlinux)

你可以看到,调用脚本 scripts/link-vmlinux.sh 的主要目的是把所有的 built-in.o 链接成一个静态可执行文件,和生成 System.map 。最后我们来看看下面的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
LINK    vmlinux
LD vmlinux.o
MODPOST vmlinux.o
GEN .version
CHK include/generated/compile.h
UPD include/generated/compile.h
CC init/version.o
LD init/built-in.o
KSYM .tmp_kallsyms1.o
KSYM .tmp_kallsyms2.o
LD vmlinux
SORTEX vmlinux
SYSMAP System.map

vmlinuxSystem.map 生成在内核源码树根目录下。

1
2
$ ls vmlinux System.map 
System.map vmlinux

这就是全部了,vmlinux 构建好了,下一步就是创建 bzImage.

制作bzImage

bzImage 就是压缩了的 linux 内核镜像。我们可以在构建了 vmlinux 之后通过执行 make bzImage 获得 bzImage。同时我们可以仅仅执行 make 而不带任何参数也可以生成 bzImage ,因为它是在 arch/x86/kernel/Makefile 里预定义的、默认生成的镜像:

1
all: bzImage

让我们看看这个目标,它能帮助我们理解这个镜像是怎么构建的。我已经说过了 bzImage 是被定义在 arch/x86/kernel/Makefile ,定义如下:

1
2
3
4
bzImage: vmlinux
$(Q)$(MAKE) $(build)=$(boot) $(KBUILD_IMAGE)
$(Q)mkdir -p $(objtree)/arch/$(UTS_MACHINE)/boot
$(Q)ln -fsn ../../x86/boot/bzImage $(objtree)/arch/$(UTS_MACHINE)/boot/$@

在这里我们可以看到第一次为 boot 目录执行 make ,在我们的例子里是这样的:

1
boot := arch/x86/boot

现在的主要目标是编译目录 arch/x86/bootarch/x86/boot/compressed 的代码,构建 setup.binvmlinux.bin ,最后用这两个文件生成 bzImage。第一个目标是定义在 arch/x86/boot/Makefile$(obj)/setup.elf

1
2
$(obj)/setup.elf: $(src)/setup.ld $(SETUP_OBJS) FORCE
$(call if_changed,ld)

我们已经在目录 arch/x86/boot 有了链接脚本 setup.ld ,和扩展到 boot 目录下全部源代码的变量 SETUP_OBJS 。我们可以看看第一个输出:

1
2
3
4
5
6
7
8
9
10
AS      arch/x86/boot/bioscall.o
CC arch/x86/boot/cmdline.o
AS arch/x86/boot/copy.o
HOSTCC arch/x86/boot/mkcpustr
CPUSTR arch/x86/boot/cpustr.h
CC arch/x86/boot/cpu.o
CC arch/x86/boot/cpuflags.o
CC arch/x86/boot/cpucheck.o
CC arch/x86/boot/early_serial_console.o
CC arch/x86/boot/edd.o

下一个源码文件是 arch/x86/boot/header.S ,但是我们不能现在就编译它,因为这个目标依赖于下面两个头文件:

1
$(obj)/header.o: $(obj)/voffset.h $(obj)/zoffset.h

第一个头文件 voffset.h 是使用 sed 脚本生成的,包含用 nm 工具从 vmlinux 获取的两个地址:

1
2
#define VO__end 0xffffffff82ab0000
#define VO__text 0xffffffff81000000

这两个地址是内核的起始和结束地址。第二个头文件 zoffset.harch/x86/boot/compressed/Makefile 可以看出是依赖于目标 vmlinux 的:

1
2
$(obj)/zoffset.h: $(obj)/compressed/vmlinux FORCE
$(call if_changed,zoffset)

目标 $(obj)/compressed/vmlinux 依赖于 vmlinux-objs-y —— 说明需要编译目录 arch/x86/boot/compressed 下的源代码,然后生成 vmlinux.binvmlinux.bin.bz2 ,和编译工具 mkpiggy 。我们可以在下面的输出看出来:

1
2
3
4
5
6
7
8
LDS     arch/x86/boot/compressed/vmlinux.lds
AS arch/x86/boot/compressed/head_64.o
CC arch/x86/boot/compressed/misc.o
CC arch/x86/boot/compressed/string.o
CC arch/x86/boot/compressed/cmdline.o
OBJCOPY arch/x86/boot/compressed/vmlinux.bin
BZIP2 arch/x86/boot/compressed/vmlinux.bin.bz2
HOSTCC arch/x86/boot/compressed/mkpiggy

vmlinux.bin 是去掉了调试信息和注释的 vmlinux 二进制文件,加上了占用了 u32 (LCTT 译注:即4-Byte)的长度信息的 vmlinux.bin.all 压缩后就是 vmlinux.bin.bz2 。其中 vmlinux.bin.all 包含了 vmlinux.binvmlinux.relocs(LCTT 译注:vmlinux 的重定位信息),其中 vmlinux.relocsvmlinux 经过程序 relocs 处理之后的 vmlinux 镜像(见上文所述)。我们现在已经获取到了这些文件,汇编文件 piggy.S 将会被 mkpiggy 生成、然后编译:

1
2
MKPIGGY arch/x86/boot/compressed/piggy.S
AS arch/x86/boot/compressed/piggy.o

这个汇编文件会包含经过计算得来的、压缩内核的偏移信息。处理完这个汇编文件,我们就可以看到 zoffset 生成了:

1
ZOFFSET arch/x86/boot/zoffset.h

现在 zoffset.hvoffset.h 已经生成了, arch/x86/boot 里的源文件可以继续编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
AS      arch/x86/boot/header.o
CC arch/x86/boot/main.o
CC arch/x86/boot/mca.o
CC arch/x86/boot/memory.o
CC arch/x86/boot/pm.o
AS arch/x86/boot/pmjump.o
CC arch/x86/boot/printf.o
CC arch/x86/boot/regs.o
CC arch/x86/boot/string.o
CC arch/x86/boot/tty.o
CC arch/x86/boot/video.o
CC arch/x86/boot/video-mode.o
CC arch/x86/boot/video-vga.o
CC arch/x86/boot/video-vesa.o
CC arch/x86/boot/video-bios.o

所有的源代码会被编译,他们最终会被链接到 setup.elf

1
LD      arch/x86/boot/setup.elf

或者:

1
ld -m elf_x86_64   -T arch/x86/boot/setup.ld arch/x86/boot/a20.o arch/x86/boot/bioscall.o arch/x86/boot/cmdline.o arch/x86/boot/copy.o arch/x86/boot/cpu.o arch/x86/boot/cpuflags.o arch/x86/boot/cpucheck.o arch/x86/boot/early_serial_console.o arch/x86/boot/edd.o arch/x86/boot/header.o arch/x86/boot/main.o arch/x86/boot/mca.o arch/x86/boot/memory.o arch/x86/boot/pm.o arch/x86/boot/pmjump.o arch/x86/boot/printf.o arch/x86/boot/regs.o arch/x86/boot/string.o arch/x86/boot/tty.o arch/x86/boot/video.o arch/x86/boot/video-mode.o arch/x86/boot/version.o arch/x86/boot/video-vga.o arch/x86/boot/video-vesa.o arch/x86/boot/video-bios.o -o arch/x86/boot/setup.elf

最后的两件事是创建包含目录 arch/x86/boot/* 下的编译过的代码的 setup.bin

1
objcopy  -O binary arch/x86/boot/setup.elf arch/x86/boot/setup.bin

以及从 vmlinux 生成 vmlinux.bin

1
objcopy  -O binary -R .note -R .comment -S arch/x86/boot/compressed/vmlinux arch/x86/boot/vmlinux.bin

最最后,我们编译主机程序 arch/x86/boot/tools/build.c ,它将会用来把 setup.binvmlinux.bin 打包成 bzImage

1
arch/x86/boot/tools/build arch/x86/boot/setup.bin arch/x86/boot/vmlinux.bin arch/x86/boot/zoffset.h arch/x86/boot/bzImage

实际上 bzImage 就是把 setup.binvmlinux.bin 连接到一起。最终我们会看到输出结果,就和那些用源码编译过内核的同行的结果一样:

1
2
3
4
Setup is 16268 bytes (padded to 16384 bytes).
System is 4704 kB
CRC 94a88f9a
Kernel: arch/x86/boot/bzImage is ready (#5)

全部结束。

结论

这就是本文的结尾部分。本文我们了解了编译内核的全部步骤:从执行 make 命令开始,到最后生成 bzImage 。我知道,linux 内核的 Makefile 和构建 linux 的过程第一眼看起来可能比较迷惑,但是这并不是很难。希望本文可以帮助你理解构建 linux 内核的整个流程。

注: 本文由 LCTT 原创翻译,Linux中国 荣誉推出

链接

转自:
https://github.com/nginx/unit/pull/18/
感谢:
https://www.v2ex.com/t/389528

English
简体中文
繁體中文

NGINX Unit

NGINX Unit 是一个动态的网络应用服务器,它的设计初衷就是可运行多种编程语言的。通过API可以轻巧,多面化的动态配置Unit。当工程师或操作者有需要时,可以轻松重构服务器已适配特殊的应用参数。

NGINX Unit 现在是beta版本。你现在虽然可以使用它,但建议仅用于测试环境,不建议用于生产环境。

本项目的源代码及分发均使用Apache 2.0 license。

核心功能

  • 使用RESTful JSON API可完整的动态重配置服务器。
  • 可同时运行多语言及多版本的应用。
  • 动态语言的进程管理功能。 (敬请期待)
  • TLS 支持 (敬请期待)
  • TCP, HTTP, HTTPS, HTTP/2 路由和代理 (敬请期待)

Supported Application Languages

  • Python
  • PHP
  • Go
  • JavaScript/Node.js (敬请期待)
  • Java (敬请期待)
  • Ruby (敬请期待)

安装

系统需求

NGINX Unit 已被测试通过在以下系统上运行:

  • Linux 2.6 或更高
  • FreeBSD 9 或更高
  • MacOS X
  • Solaris 11

系统架构:

  • i386
  • amd64
  • powerpc
  • arm

NGINX Unit 支持不同的编程语言,你可以选择下面列出的版本:

  • Python 2.6, 2.7, 3
  • PHP 5, 7
  • Go 1.6 or later

你可以在一个系统上运行不同版本的相同编程语言。

安装包

你可以在CentOS 7.0 和 Ubuntu 16.04 LTS。上直接安装 NGINX Next

CentOS 安装

  1. /etc/yum.repos.d/unit.repo 目录下建立文件,并包含以下内容:

    1
    2
    3
    4
    5
    [unit]
    name=unit repo
    baseurl=http://nginx.org/packages/mainline/centos/7/$basearch/
    gpgcheck=0
    enabled=1
  2. 安装Nginx Unit:

    1
    # yum install unit

Ubuntu 安装

  1. 下载key key 用于签名NGINX, Inc. 的源和包。

  2. 添加秘钥到 apt 程序里。程序将会认证NGINX源的签名,这样将会消除警告。

    1
    # sudo apt-key add nginx_signing.key
  3. 将下面两行写入文件尾部。

/etc/apt/sources.list:

1
2
deb http://nginx.org/packages/mainline/ubuntu/ xenial nginx
deb-src http://nginx.org/packages/mainline/ubuntu/ xenial nginx
  1. 安装NGINX Unit:

    1
    2
    # apt-get update
    # apt-get install unit

源代码

本章将会完整的解释如何通过源代码安装NIGIX Unit。

获得源代码

你可以通过三种方法获得NGINX Unit的源代码: 从 NGINX, Inc.
的Mercurial源,Github,或从tarball获得源代码。

无论从那种方式获得源代码,你均可以在 unit 子目录中,找到我们的源代码。

Mercurial源
  1. 如果你没有安装Mercurial软件。你需要先下载并安装它。
    示例:在 Ubuntu 系统下,运行下面的命令:

    1
    # apt-get install mercurial
  2. 下载NIGIX Unit的源代码:

    1
    # hg clone http://hg.nginx.org/unit
GitHub 源
  1. 如果你没有Git,请先移步Github帮助你安装Git
    GitHub 帮助文档

  2. 下载NGINX Unit源:

    1
    # git clone https://github.com/nginx/unit
Tarball

NGINX Unit源代码请在以下链接获得
http://unit.nginx.org/download/

安装依赖软件

在配置和安装 NGINX Unit 之前,你必须现安装依赖文件和必要的工具。以及你希望运行的编程语言。如 Go,PHP,和 Python。

Ubuntu 依赖安装
  1. 安装 build tools。

    1
    # apt-get install build-essential
  2. 如果想支持Go语言应用,请安装 golang 包。

    1
    # apt-get install golang
  3. 如果想支持PHP语言应用,请安装 php-devlibphp-embed 包。

    1
    2
    # apt-get install php-dev
    # apt-get install libphp-embed
  4. 如果想支持Python语言应用,请安装 python-dev 包。

    1
    # apt-get install python-dev
CentOS 依赖
  1. 安装 build tools。

    1
    # yum install gcc make
  2. 如果想支持Go语言应用,请安装 golang 包。

    1
    # yum install golang
  3. 如果想支持PHP语言应用,请安装 php-devellibphp-embedded 包。

    1
    # yum install php-devel php-embedded
  4. 如果想支持Python语言应用,请安装 python-devel 包。

    1
    # yum install python-devel

配置源代码

使用NGINX Unit,你可以同时运行不同版本的编程语言。(Go,PHP,或者Python)。你需要配置一个separate(分区)。
NGINX Unit有不同的模块。下面的命令会给不同的模块创建分区。 Makefile

配置Go语言环境

NGINX Unit 会提供Go的依赖包,供你方便配置NGINX Unit内的Go使用环境。

  1. 使用下面的设置 GOPATH 的环境变量。

    1
    # export GOPATH=/home/user/go_apps
  2. 运行以下命令:

    1
    2
    3
    4
    5
    # ./configure go
    configuring Go package
    checking for Go ... found
    + go version go1.6.2 linux/amd64
    + Go package path: "/home/user/go_apps"
建立Go应用
  1. 定义Go的源文件,以适应NGINX Unit。

    a. 在 import 区域,在尾行添加 "unit"

    1
    2
    3
    4
    5
    import {
    "fmt"
    "net/http"
    "unit"
    }

    b. 在 main() 功能中, 注释 http.ListenandServe
    功能并添加 unit.ListenAndServe 功能。

    1
    2
    3
    4
    func main() {
    http.HandleFunc("/", handler)
    //http.ListenAndServe(":8080", nil)
    unit.ListenAndServe(":8080", nil)
  2. 建立 GO应用。

    1
    # go build

如果你直接运行Go应用,Go将会自动使用http.ListenandServe。如果使用NGINX Unit,启动Go程序,将会自动执行unit.ListenandServe。程序将会与Unit的路由进行交互。

配置PHP模块

配置PHP模块。( php.unit.so) 运行下面的命令进行自动配置PHP:

1
# ./configure php

如需要配置不同版本的PHP,请使用下面的命令:

1
# ./configure php --module=<prefix> --config=<script-name> --lib-path=<pathname>

  • --module 设置文件名的前缀。Unit被设置为特定的版本。( <prefix>.unit.so)。

  • --config 钦定的文件名, php-config 特定版本的PHP。

  • --lib-path PHP的路径。

实例:下面这个命令将会生成一个模块 php70.unit.so 已适配PHP7.0
PHP 7.0:

1
2
3
4
5
6
7
8
9
# ./configure php --module=php70  \
--config=/usr/lib64/php7.0/php-config \
--lib-path=/usr/lib64/php7.0/lib64
configuring PHP module
checking for PHP ... found
+ PHP version: 7.0.22-0ubuntu0.16.04.1
+ PHP SAPI: [apache2handler embed cgi cli fpm]
checking for PHP embed SAPI ... found
+ PHP module: php70.unit.so
配置Python模块

配置特定的Python模块,已适配NGINX Unit。 (叫 python.unit.so) 。
在操作系统的根目录可以找到configure,使用下面的命令。

1
# ./configure python

如果需要配置其他的Python版本,请使用下面的命令。

1
# ./configure python --module=<prefix> --config=<script-name>

  • --module 会生成一个模块,设置文件名的前缀。Unit被设置为特定的版本, (就这么简单,将会生成一个
    <prefix>.unit.so).

  • --config 钦定的文件名 python-config 将会生成特定版本的模块。

示例:下面的命令将会生成一个名为 py33.unit.so 已适配
Python 3.3:

1
2
3
4
5
6
# ./configure php --module=py33  \
--config=python-config-3.3
configuring Python module
checking for Python ... found
checking for Python version ... 3.3
+ Python module: py33.unit.so

完成编译

当完成NGINX Unit的PHP, Python模块编译后, 运行下面的命令:

1
# make all

编译适用于Go语言的NGINX Unit:

  1. 确认GOPATH 环境变量已被正确设置。

    1
    2
    3
    # go env GOPATH

    # export GOPATH=<path>
  2. 完成编译:

    1
    # make go-install

从源代码安装

如果需要安装完整的全面模块和Go包,运行下面的命令:

1
# make install

配置

默认情况下,Control Socket内包含的的API来控制NGINX Unit
unit.control.sock.

应用

每个单独的应用,你都可以在NGINX Unit的配置文件中,使用JSON语法来定义一个
applications。使用JSON语法来定义里面的内容,如使用的编程语言,需要的工作数,文件的本地路径,以及其他参数。
这个示例配置了一个PHP网站,名为 blogs 而这个网站的本地路径为。 /www/blogs/scripts。默认页面为 index.php

1
2
3
4
5
6
7
8
9
10
11
{
...
"applications": {
"blogs": {
"type": "php",
"workers": 20,
"root": "/www/blogs/scripts",
"index": "index.php"
}
}
}

监听器

当应用被通过HTTP访问时,你必须定义至少一个监听器 listeners。监听器是一个IP地址和一个被定义的端口,当用户访问时,Unit的监听器会返回正确结果。IP地址可以是一个完整的IP地址(示例,
127.0.0.1:8300)或(示例,*:8300).

在这个示例中,端口 8300 的请求全部会被发送至 blogs
这个应用:

1
2
3
4
5
6
7
8
{
"listeners": {
"*:8300": {
"application": "blogs"
}
},
...
}

完整的JSON语法细节,请点击下面的链接。
JSON 详细的监听器配置和应用配置.

最小化配置

配置中至少需要包含一个监听器和一个应用配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"listeners": {
"*:8300": {
"application": "blogs"
}
},
"applications": {
"blogs": {
"type": "php",
"workers": 20,
"root": "/www/blogs/scripts",
"index": "index.php"
}
}
}

创建配置文件

你可以创建一个配置文件,并且发送一个 PUT 请求。为了减少发生错误的可能,当使用 curl 命令式,请包含 -d 选项。

示例:创建一个完整的配置文件

通过下面的命令,可以创建一个初始的配置文件
start.json :

1
2
# curl -X PUT -d @/path/to/start.json  \
--unix-socket ./control.unit.sock http://localhost/

示例:新建一个应用对象

通过 wiki.json 我们可以创建一个 wiki 应用。

1
2
# curl -X PUT -d @/path/to/wiki.json  \
--unix-socket ./control.unit.sock http://localhost/applications/wiki

wiki.json 里包含了:

1
2
3
4
5
6
7
8
{
"type": "python",
"workers": 10,
"module": "wsgi",
"user": "www-wiki",
"group": "www-wiki",
"path": "/www/wiki"
}

显示配置的对象

要显示配置的对象,它被附加在curl 的URL内。

示例:显示完整的配置文件

如果你想显示完整的配置文件,你可以通过下面的指令来查看:

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
26
27
28
29
30
31
32
33
34
# curl --unix-socket ./control.unit.sock http://localhost/
{
"applications": {
"blogs": {
"type": "php",
"user": "nobody",
"group": "nobody",
"workers": 20,
"root": "/www/blogs/scripts",
"index": "index.php"
},

"wiki": {
"type": "python",
"user": "nobody",
"group": "nobody",
"workers": 10,
"path": "/www/wiki",
"module": "wsgi"
}
},


"listeners": {
"*:8300": {
"application": "blogs"
},


"*:8400": {
"application": "wiki"
}
}
}

示例:显示一个对象

显示 wiki 这个应用的配置,只需:

1
2
3
4
5
6
7
8
9
# curl --unix-socket ./control.unit.sock http://localhost/applications/wiki
{
"type": "python",
"workers": 10,
"module": "wsgi",
"user": "www",
"group": "www",
"path": "/www/wiki"
}

修改配置的对象:

要更改配置的对象,使用 curl 命令和-d 选项来实现特定的对象的JSON数据,然后发送一个PUT请求。

示例:修改监听端口指向的应用

在端口 :8400上修改 application 应用指向 *wiki-dev**:

1
2
3
4
5
# curl -X PUT -d '"wiki-dev"' --unix-socket ./control.unit.sock  \
'http://localhost/listeners/*:8400/application'
{
"success": "Reconfiguration done."
}

示例:修改应用的本地路径

修改root对象的 blogs 应用的本地路径至
/www/blogs-dev/scripts:

1
2
3
4
5
6
# curl -X PUT -d '"/www/blogs-dev/scripts"'  \
--unix-socket ./control.unit.sock \
http://localhost/applications/blogs/root
{
"success": "Reconfiguration done."
}

删除对象

要删除配置的对象,你可以通过 curl 发送一个DELETE 请求到对象目录。

示例:删除监听器

删除对 *:8400 端口的监听:

1
2
3
4
5
# curl -X DELETE --unix-socket ./control.unit.sock  \
'http://localhost/listeners/*:8400'
{
"success": "Reconfiguration done."
}

监听器和应用对象

监听器

对象 描述
<IP地址>:<端口> IP地址和端口需在不同的Unit监听器上均需要配置应用的名字 ,IP地址可以是完整的 (127.0.0.1:8300) 或者(*:8300).
application 应用名。

示例:

1
2
3
"*:8300": {
"application": "blogs"
}

Go语言应用

对象 描述
type 应用的编程语言 (go)。
workers 应用的工作数量。
executable 完整的本地路径。
user (optional) 运行进程的用户,如未定义,则默认(nobody)。
group (optional) 用户所在的用户组 。如未定义,则默认。

示例:

1
2
3
4
5
6
"go_chat_app": {
"type": "go",
"executable": "/www/chat/bin/chat_app",
"user": "www-go",
"group": "www-go"
}

PHP语言应用

对象 描述
type 应用的编程语言 (php).
workers 应用的工作数量。
root 文件的本地路径。
index 默认的index文件路径。
script (optional) 访问Unit内任意的URL均会运行,填写路径将不要填写物理路径,请填写虚拟路径。
user (optional) 运行进程的用户,如未定义,则默认(nobody)。
group (optional) 用户所在的用户组 。如未定义,则默认。

示例:

1
2
3
4
5
6
7
8
"blogs": {
"type": "php",
"workers": 20,
"root": "/www/blogs/scripts",
"index": "index.php",
"user": "www-blogs",
"group": "www-blogs"
},

Python语言应用

Object Description
type 应用的编程语言 (python)。
workers 应用的工作数量。
path wsgi.py 的路径。
module 必填。目前只支持 wsgi
user (optional) 运行进程的用户,如未定义,则默认(nobody)。
group (optional) 用户所在的用户组 。如未定义,则默认。

示例:

1
2
3
4
5
6
7
8
"shopping_cart": {
"type": "python",
"workers": 10,
"path": "/www/store/cart",
"module": "wsgi",
"user": "www",
"group": "www"
},

完整示例:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
{
"listeners": {
"*:8300": {
"application": "blogs"
},
"*:8400": {
"application": "wiki"
},
"*:8401": {
"application": "shopping_cart"
},
"*:8500": {
"application": "go_chat_app"
}
},
"applications": {
"blogs": {
"type": "php",
"workers": 20,
"root": "/www/blogs/scripts",
"user": "www-blogs",
"group": "www-blogs",
"index": "index.php"
},
"wiki": {
"type": "python",
"workers": 10,
"user": "www-wiki",
"group": "www-wiki",
"path": "/www/wiki"
},
"shopping_cart": {
"type": "python",
"workers": 10,
"module": "wsgi",
"user": "www",
"group": "www",
"path": "/www/store/cart"
},
"go_chat_app": {
"type": "go",
"user": "www-chat",
"group": "www-chat",
"executable": "/www/chat/bin/chat_app"
}
}
}

NGINX一起使用

和NGINX一起使用

配置NGINX来进行静态文件的处理和接受代理的请求。
NGINX服务器将直接处理静态文件的访问请求,动态文件的处理将会直接转发到NGINX Unit。
新建一个上传模块,在NGINX的配置中,将http的请求转发给Unit,示例:

1
2
3
upstream unit_backend {
server 127.0.0.1:8300;
}

新建或修改NGINX的配置文件 server块和 location块 。指定的静态文件的路径和上传模块。

示例 1

这个例子适用于基于PHP编程语言开发的程序。,全部的URL请求,如已.php结尾,均会被转发至Unit服务器,其他的全部文件将会直接被服务器返回文件:

1
2
3
4
5
6
7
8
9
10
11
server {

location / {
root /var/www/static-data;
}

location ~ \.php$ {
proxy_pass http://unit_backend;
proxy_set_header Host $host;
}
}

示例 2

All other requests will be proxied to Unit:
下面的应用,全部都静态文件需要被放置在/var/www/files 目录下,在前端调用时,请直接使用/static

1
2
3
4
5
6
7
8
9
10
11
 server {

location /static {
root /var/www/files;
}

location / {
proxy_pass http://unit_backend;
proxy_set_header Host $host;
}
}

相关的NGINX 文档将会在http://nginx.org提供。
相关的支持和更多的功能将在https://www.nginx.com上提供。

安全和代理Unit API

默认情况下,Unit的API将会在Unix domain socket下。如果你希望API可以被远程访问,你需要使用NGINX配置代理。
NGINX 可以提供安全的、可信的和可控制的API
使用下面的示例配置来配置NGINX:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
server {

# Configure SSL encryption
server 443 ssl;
ssl_certificate /path/to/ssl/cert.pem;
ssl_certificate_key /path/to/ssl/cert.key;

# Configure SSL client certificate validation
ssl_client_certificate /path/to/ca.pem;
ssl_verify_client on;

# Configure network ACLs
#allow 1.2.3.4; # Uncomment and change to the IP addresses and networks
# of the administrative systems.
deny all;

# Configure HTTP Basic authentication
auth_basic on;
auth_basic_user_file /path/to/htpasswd.txt;

location / {
proxy_pass http://unix:/path/to/control.unit.sock
}
}

贡献

NGINX Unit的发布和分发均使用Apache 2.0 license。
如果想贡献自己的力量,你可以选择通过邮件[email protected]
或者在Github上提交PRhttps://github.com/nginx/unit
如果在中文翻译方面需要改近请联系@tuzimoe

疑难解答

Unit 日志一般在默认的位置,可以在/var/log/unit.log 中找到。
Log 文件的位置也可以通过运行 unitd --help 来快速定位。
详细的Debug日志可以通过输入命令来获得:

1
./configure --debug

输入完命令后,请务必重新编译和重装NGINX Unit。
请注意,debug日志的内容将会以快速的增长。

社区邮箱的列表将会在unit@nginx.org上找到。
订阅邮箱列表,可以通过发送任何内容至订阅
[email protected]
或直接点击此处订阅
没错就是我

Vim 是一个上古神器,本篇文章主要持续总结使用 Vim 的过程中不得不了解的一些指令和注意事项,以及持续分享一个前端工作者不得不安装的一些插件,而关于 Vim 的简介,主题的选择,以及为何使用 vim-plug 来管理插件等内容,有兴趣的同学下来可以自己了解和对比下。

安装

1
sudo apt-get install vim  // Ubuntu

其他平台,可以自行谷歌。

新手指南

1
vimtutor  // vim 教程

上面是史上最简单,最全面的Vim基础教程,至今无人超越。

下面是作者基于上面的归纳:

移动光标

1
2
3
4
5
6
7
8
9
10
hjkl
2w 向前移动两个单词
3e 向前移动到第 3 个单词的末尾
0 移动到行首
$ 当前行的末尾
gg 文件第一行
G 文件最后一行
行号+G 指定行
<ctrl>+o 跳转回之前的位置
<ctrl>+i 返回跳转之前的位置

退出

1
2
3
<esc> 进入正常模式
:q! 不保存退出
:wq 保存后退出

删除

1
2
3
4
5
6
x 删除当前字符
dw 删除至当前单词末尾
de 删除至当前单词末尾,包括当前字符
d$ 删除至当前行尾
dd 删除整行
2dd 删除两行

修改

1
2
3
4
i 插入文本
A 当前行末尾添加
r 替换当前字符
o 打开新的一行并进入插入模式

撤销

1
2
u 撤销
<ctrl>+r 取消撤销

复制粘贴剪切

1
2
3
4
5
v 进入可视模式
y 复制
p 粘贴
yy 复制当前行
dd 剪切当前行

状态

1
<ctrl>+g 显示当前行以及文件信息

查找

1
2
3
4
5
6
7
/ 正向查找(n:继续查找,N:相反方向继续查找)
? 逆向查找
% 查找配对的 {,[,(
:set ic 忽略大小写
:set noic 取消忽略大小写
:set hls 匹配项高亮显示
:set is 显示部分匹配

替换

1
2
3
:s/old/new 替换该行第一个匹配串
:s/old/new/g 替换全行的匹配串
:%s/old/new/g 替换整个文件的匹配串

折叠

1
2
3
4
zc 折叠
zC 折叠所有嵌套
zo 展开折叠
zO 展开所有折叠嵌套

执行外部命令

1
:!shell 执行外部命令

.vimrc

.vimrc 是 Vim 的配置文件,需要我们自己创建:

1
2
3
4
5
6
7
8
9
10
11
cd Home               // 进入 Home 目录
touch .vimrc // 配置文件

# Unix
# vim-plug
# Vim
curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
# Neovim
curl -fLo ~/.local/share/nvim/site/autoload/plug.vim --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

其他平台,可以查看 vim-plug。

基本配置

取消备份

1
2
set nobackup
set noswapfile

文件编码

1
set encoding=utf-8

显示行号

1
set number

取消换行

1
set nowrap

显示光标当前位置

1
set ruler

设置缩进

1
2
3
set cindent
set tabstop=2
set shiftwidth=2

突出显示当前行

1
set cursorline

查找

1
2
3
set ic
set hls
set is

左下角显示当前 vim 模式

1
set showmode

代码折叠

1
2
# 启动 vim 时关闭折叠代码
set nofoldenable

主题

1
2
3
syntax enable
set background=dark
colorscheme solarized

altercation/vim-colors-solarized
Anthony25/gnome-terminal-colors-solarized

插件配置

树形目录

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Plug 'scrooloose/nerdtree'
Plug 'jistr/vim-nerdtree-tabs'
Plug 'Xuyuanp/nerdtree-git-plugin'

autocmd vimenter * NERDTree
map <C-n> :NERDTreeToggle<CR>
let NERDTreeShowHidden=1
let g:NERDTreeShowIgnoredStatus = 1
let g:nerdtree_tabs_open_on_console_startup=1
let g:NERDTreeIndicatorMapCustom = {
\ "Modified" : "✹",
\ "Staged" : "✚",
\ "Untracked" : "✭",
\ "Renamed" : "➜",
\ "Unmerged" : "═",
\ "Deleted" : "✖",
\ "Dirty" : "✗",
\ "Clean" : "✔︎",
\ 'Ignored' : '☒',
\ "Unknown" : "?"
\ }

# o 打开关闭文件或目录
# e 以文件管理的方式打开选中的目录
# t 在标签页中打开
# T 在标签页中打开,但光标仍然留在 NERDTree
# r 刷新光标所在的目录
# R 刷新当前根路径
# X 收起所有目录
# p 小写,跳转到光标所在的上一级路径
# P 大写,跳转到当前根路径
# J 到第一个节点
# K 到最后一个节点
# I 显示隐藏文件
# m 显示文件操作菜单
# C 将根路径设置为光标所在的目录
# u 设置上级目录为根路径
# ctrl + w + w 光标自动在左右侧窗口切换
# ctrl + w + r 移动当前窗口的布局位置
# :tabc 关闭当前的 tab
# :tabo 关闭所有其他的 tab
# :tabp 前一个 tab
# :tabn 后一个 tab
# gT 前一个 tab
# gt 后一个 tab

scrooloose/nerdtree
vim-nerdtree-tabs
nerdtree-git-plugin

代码,引号,路径补全

1
2
3
Plug 'Valloric/YouCompleteMe'
Plug 'Raimondi/delimitMate'
Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' }

Valloric/YouCompleteMe
Raimondi/delimitMate
Shougo/deoplete.nvim

语法高亮,检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Plug 'sheerun/vim-polyglot'
Plug 'w0rp/ale'

let g:ale_linters = {
\ 'javascript': ['eslint'],
\ 'css': ['stylelint'],
\}
let g:ale_fixers = {
\ 'javascript': ['eslint'],
\ 'css': ['stylelint'],
\}
let g:ale_fix_on_save = 1

let g:ale_sign_column_always = 1
let g:ale_sign_error = '●'
let g:ale_sign_warning = '▶'

nmap <silent> <C-k> <Plug>(ale_previous_wrap)
nmap <silent> <C-j> <Plug>(ale_next_wrap)

w0rp/ale
sheerun/vim-polyglot

文件,代码搜索

1
2
Plug 'rking/ag.vim'
Plug 'kien/ctrlp.vim'

kien/ctrlp.vim
ggreer/the_silver_searcher
rking/ag.vim

加强版状态栏

1
2
3
Plug 'vim-airline/vim-airline'
Plug 'vim-airline/vim-airline-themes'
let g:airline_theme='papercolor'

vim-airline/vim-airline
vim-airline/vim-airline-themes

代码注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Plug 'scrooloose/nerdcommenter'

# <leader>cc // 注释
# <leader>cm 只用一组符号注释
# <leader>cA 在行尾添加注释
# <leader>c$ /* 注释 */
# <leader>cs /* 块注释 */
# <leader>cy 注释并复制
# <leader>c<space> 注释/取消注释
# <leader>ca 切换 // 和 /* */
# <leader>cu 取消注释

let g:NERDSpaceDelims = 1
let g:NERDDefaultAlign = 'left'
let g:NERDCustomDelimiters = {
\ 'javascript': { 'left': '//', 'leftAlt': '/**', 'rightAlt': '*/' },
\ 'less': { 'left': '/**', 'right': '*/' }
\ }

scrooloose/nerdcommenter

git

1
2
Plug 'airblade/vim-gitgutter'
Plug 'tpope/vim-fugitive'

airblade/vim-gitgutter
tpope/vim-fugitive

###Markdown

1
2
3
4
5
Plug 'suan/vim-instant-markdown'

let g:instant_markdown_slow = 1
let g:instant_markdown_autostart = 0
# :InstantMarkdownPreview

suan/vim-instant-markdown

Emmet

1
2
3
4
5
6
7
8
Plug 'mattn/emmet-vim'

let g:user_emmet_leader_key='<Tab>'
let g:user_emmet_settings = {
\ 'javascript.jsx' : {
\ 'extends' : 'jsx',
\ },
\ }

mattn/emmet-vim

html 5

1
Plug'othree/html5.vim'

othree/html5.vim

css 3

1
2
3
4
5
6
7
8
Plug 'hail2u/vim-css3-syntax'
Plug 'ap/vim-css-color'

augroup VimCSS3Syntax
autocmd!

autocmd FileType css setlocal iskeyword+=-
augroup END

hail2u/vim-css3-syntax
ap/vim-css-color

JavaScipt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Plug 'pangloss/vim-javascript'
let g:javascript_plugin_jsdoc = 1
let g:javascript_plugin_ngdoc = 1
let g:javascript_plugin_flow = 1
set foldmethod=syntax
let g:javascript_conceal_function = "ƒ"
let g:javascript_conceal_null = "ø"
let g:javascript_conceal_this = "@"
let g:javascript_conceal_return = "⇚"
let g:javascript_conceal_undefined = "¿"
let g:javascript_conceal_NaN = "ℕ"
let g:javascript_conceal_prototype = "¶"
let g:javascript_conceal_static = "•"
let g:javascript_conceal_super = "Ω"
let g:javascript_conceal_arrow_function = "⇒"
let g:javascript_conceal_noarg_arrow_function = " "
let g:javascript_conceal_underscore_arrow_function = " "
set conceallevel=1

pangloss/vim-javascript
(注:上述脚本中存在特殊字符,有的情况下显示不正确,请直接用上述链接的内容。)

React

1
2
Plug 'mxw/vim-jsx'
let g:jsx_ext_required = 0

mxw/vim-jsx

Prettier

1
2
3
4
5
6
7
8
Plug 'prettier/vim-prettier', {
\ 'do': 'yarn install',
\ 'for': ['javascript', 'typescript', 'css', 'less', 'scss', 'json', 'graphql'] }
let g:prettier#config#bracket_spacing = 'true'
let g:prettier#config#jsx_bracket_same_line = 'false'
let g:prettier#autoformat = 0
autocmd BufWritePre *.js,*.jsx,*.mjs,*.ts,*.tsx,*.css,*.less,*.scss,*.json,*.graphql PrettierAsync
# :Prettier

prettier/vim-prettier

总结

最后,呈上参考配置 .vimrc