virtualenv使用

  1. 安装
    pip install virtualenv

  2. 配置
    vi ~/.bashrc

export VIRTUALENV_USE_DISTRIBUTE=true

virtualenvwrapper使用

  1. 安装
    pip install virtualenvwrapper

  2. 指定virtualenvwrapper虚拟环境默认路径
    vi ~/.bashrc

1
2
3
4
5
# config virtualenvwrapper
if [ -f /usr/local/bin/virtualenvwrapper.sh ]; then
export WORKON_HOME=$HOME/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh
fi
  1. 创建虚拟环境
    mkvirtualenv env

  2. 查看所有虚拟环境和启动虚拟环境
    lsvirtualenv
    workon
    workon env

  3. 退出虚拟环境
    deactivate

我是一个Python程序开发者, 也C语言爱好者, 为了加强自己对Python语言实现的理解, 最近选择阅读下陈儒老师的<>一书, 对Python3.5.1源码进行阅读, 再次记录下读书笔记. 欢迎各位指正.

Python的总体结构

Python总体分为三部分: 文件系统(File Groups), 解释器(Interpreter), 运行时环境(Runtim Environement):

  • 文件系统用于存储Python脚本文件(.py), 主要分为内置模块、第三方模块、用户自定义模块
  • 解释器用于完成Python脚本文件的扫描、解析、编译和运行,主要分为Scanner、Parser、Complier、Exauator四部分
  1. Scanner
    用于对Python脚本的词法解析, 将脚本切分为token

  2. Parser
    对所有token进行语法解析, 创建抽象语法树

  3. Complier
    根据抽象语法树, 创建指令集合(Python字节码)

  4. Exauator
    运行Python字节码

  • 运行时环境主要分为对象/类型系统、内存分配器、运行时状态信息
  1. 对象/类型系统
    包含Python的所有内置对象、用户自定义的类型和对象

  2. 内存分配器
    负责维护Python对象创建时对内存的申请

  3. 运行时状态信息
    负责维护解释器在执行字节码时不同的状态之间切换动作

总体结构图:
Python总体结构图

Python是什么

Python(蟒蛇)是一门简单易学, 优雅健壮, 功能强大, 面向对象的解释型脚本语言. 具有20+年发展历史, 成熟稳定. 具有丰富和强大的类库支持日常应用.

Python来源

罗萨姆于1989年底在CWI(国家数学和计算机科学研究院)始创Python, 在1991年发布第一个公开发行版. 和其他开发语言类似, Python来源于项目研究.
当时在项目中主要使用解释型语言ABC, 罗萨姆期望可以开发出一套工具完成日常系统管理任务, 能够访问分布式操作系统Amoeba的系统调用. 于是从1898年底开始创作通用性开发语言Python.

Python的特点

  1. 简单
    Python是一种代表简单主义思想的语言, 阅读Python程序像是在读英语, 这种伪代码是其最大优点之一, 可以使开发者更专注于如何解决问题

  2. 易学
    Python 关键字少, 结构简单, 语法清晰, 同时支持面向过程和面向对象.

  3. 优雅
    Python没有其他语言定义的访问变量,定义代码块等命令式符号比如$, ;, ~, {, }等, 使得代码更加清晰, 易于阅读

  4. 健壮
    Python提供异常机制, 在程序发生错误崩溃, 解释器提供异常堆栈信息, 包括程序崩溃位置 原因等信息, 方便开发者进行问题跟踪排查

  5. 高级
    和Java, C#等语言一样, 在Python中提供了高级的数据结构, 内置对列表和字典的支持

  6. 面向对象
    面向对象语言的特征:封装、继承、多态
    Pyton支持将特定行为和功能与他们要处理的数据组合在一起

  7. 可升级
    Python提倡功能开发模块化, 以项目为单元. 在项目中持续程序的增加或修改,减少对项目外的功能影响

  8. 可扩展
    Python模块标准化使得开发者可以方便使用Python或者其他语言对进行扩展组件的开发. 例如常常由于性能原因需要使用C重写Python模块, 但对于提供的调用接口完全一致.

    目前Python解释器实现有多种, 比如CPython, IPython, IronPython, PyPy
    CPython是标准实现, 使用C语言开发, 可以使用C/C++编写扩展
    Ipython是使用Java开发, 可以使用Java编写扩展
    IronPython是使用C#开发, 可以使用C#编写扩展
    Pypy是使用Python开发

  9. 可移植性
    Python解释器使用C编写, 由于C具有移植性, 使得Python可以运行在任何带有ANSI C编译器的平台上. 对于使用Python开发的通用软件可以稍微修改或原封不动的在其他平台上运行, 适用于不同的架构和操作系统

  10. 内存管理
    Python中, 内存管理由Python解释器负责, 开发者不用再关注内存申请、释放等管理工作, 使错误更少、程序更健壮、开发周期更短

  11. 解释性和字节编译
    Python是一种解释型语言, 在开发过程中没有编译环节. 类似于Java, 在运行时Python解释器会将py源码编译为pyc的字节码, 从而加载执行

Python解释器安装&执行

在Linux环境一般会默认安装Python解释器环境, Window下需要开发者手动安装

  1. 下载
    Windwos x64 下载 点击此处
    Windwos x86 下载 点击此处

  2. 安装
    和其他安装程序一致, 点击下一步进行安装即可

  3. 配置环境变量
    将Python安装目录配置到系统PATH中

  4. 启动python
    在命令行中执行python即可打开python命令行工具

  5. 运行python脚本
    在命令行执行python file.py即可

CentOS 7.0 Docker安装

系统要求:
x64 cpu架构, linux内核版本>=3.10, 开启cgroups和namespace

命令查询:

  1. x64cpu架构查询
    输入: uname -m
    输出: x86_64

  2. linux内核查询
    输入: uname -r
    输出: 3.10.0-229.el7.x86_64

安装:

  1. 添加docker源

    1
    2
    3
    4
    5
    6
    7
    8
    cat > /etc/yum.repos.d/docker.repo <<EOF
    [dockerrepo]
    name=Docker Repository
    baseurl=https://yum.dockerproject.org/repo/main/centos/7
    enabled=1
    gpgcheck=1
    gpgkey=https://yum.dockerproject.org/gpg
    EOF
  2. 安装docker:

    1
    yum install -y docker-engine docker-selinux

可能由于网络原因在下载源或者gpgkey时失败, 可以使用其他工具手动下载然后将docker-engine的包拷贝到yum cache目录, 然后在使用yum install安装
a. 源下载失败:

1
2
3
4
wget https://yum.dockerproject.org/repo/main/centos/7/Packages/docker-engine-1.8.3-1.el7.centos.x86_64.rpm -O docker-engine-1.8.3-1.el7.centos.x86_64.rpm

# cache目录: /var/cache/yum/x86_64/7/dockerrepo/packages/
cp docker-engine-1.8.3-1.el7.centos.x86_64.rpm /var/cache/yum/x86_64/7/dockerrepo/packages/

b.gpgkey下载失败
可以将repo配置中的gpgcheck=1改为gpgcheck=0表示不对源进行检查

  1. 启动docker daemon
    1
    sudo service docker start

在centos7.0会发现不能重启服务, 测试应该为centos7.0默认使用firewalld防火墙与docker在网络方面产生某些冲突, 未深揪, 但是安装docker-selinux后可以重启, 但是当firewalled在docker daemon之后重启, docker的网络就会有问题, 原因是firewalld在启动时会删除docker在iptables中添加的规则
在测试环境可以选择停用firewalld而是用iptables

1
2
systemctl stop firewalld
systemctl disable firewalld

  1. 添加当前用户到docker组总

    1
    sudo usermod -aG docker $USER
  2. 添加docker daemon随机启动

    1
    sudo chkconfig docker on
  3. 测试

    1
    docker info
  4. 卸载

1
2
3
4
5
6
7
8
9
10
#查看安装的docker包
yum list installed | grep docker

#根据查询的安装包进行卸载
sudo yum -y remove docker-engine.***

#删除所有镜像、容器和卷
rm -rf /var/lib/docker

#删除配置信息

Ubuntu 安装

1
2
3
4
5
wget -qO- https://get.docker.com/gpg | sudo apt-key add -

wget -qO- https://get.docker.com/ | sh

sudo usermod -aG docker ${USER}

重新开启shell

Linux中的隔离机制

Linux中使用namespace做隔离, namespace分为以下6种:

namespace 系统参数 隔离内容
UTS CLONE_NEWUTS 主机名和域名
IPC CLONE_NEWIPC 信号量、消息队列、共享内存
PID CLONE_NEWPID 进程编号
Network CLONE_NEWNET 网络设备、网络栈、端口等
Mount CLONE_NEWNS 挂载点(文件系统)
User CLONE_NEWUSER 用户和用户组

namespace的4个API:
|API|API定义|说明|
|clone|int clone(int (child_func)(void args), void child_stack, int flags, void arg))|创建一个子进程并设置namespace|
|setns|int setns(int fd, int nstype)|将当前进程设置已有的namespace, 但当前进程不进入到新的pid namespace, 只有新创建的子进程才加入到已有的pid namesapce中|
|unshare|int unshare(int flags)|在原有进程中创建namespace, 但当前进程并不进入到新的pid namespace, 只有新启动的子进程才会进入创建的pid namespace中|
|/proc/$$/ns|目录下的文件描述符|每个文件对应namespace的文件描述符, []中的编号为namespace编号, 相同namespace表示在同一个namespace下|

/proc/$$/ns下目录文件:

1
2
3
4
5
6
7
dr-x--x--x. 2 root root 0 Oct 28 13:38 .
dr-xr-xr-x. 8 root root 0 Oct 28 13:29 ..
lrwxrwxrwx. 1 root root 0 Oct 28 13:38 ipc -> ipc:[4026531839]
lrwxrwxrwx. 1 root root 0 Oct 28 13:38 mnt -> mnt:[4026532171]
lrwxrwxrwx. 1 root root 0 Oct 28 13:38 net -> net:[4026531956]
lrwxrwxrwx. 1 root root 0 Oct 28 13:38 pid -> pid:[4026531836]
lrwxrwxrwx. 1 root root 0 Oct 28 13:38 uts -> uts:[4026531838]

隔离机制测试

  1. utc namespace测试

测试过程:
a. 启动test_utc.o, shell中的hostname为程序中指定的hostname, 使用uname -a查询hostname信息为设置的SilenceHostName
b. exit结束进程后, shell的hostname恢复, 并且使用uname -a命令查询仍然是系统默认hostname

代码:

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
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];

char* const child_args[] = {
"/bin/bash",
NULL
};

int child_main(void* args) {
printf("子进程启动\n");
sethostname("SilenceHostName", 15);
execv(child_args[0], child_args);
return 1;
}

int main(int argc, char** argv) {
printf("父进程启动\n");
int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWUTS | SIGCHLD, NULL);
waitpid(child_pid, NULL, 0);
printf("父进程结束\n");
return 0;
}

编译:

1
gcc -Wall test_utc.c -o test_utc.o && ./test_utc.o

  1. ipc namespace

可以使用命令ipcmk -Q创建一个进程间通信的消息队列, 使用ipcs -q查询所有消息队列, 使用ipcrm -q msqid删除消息队列

测试过程:
a. 使用ipcmk -Q创建消息队列
b. 启动test_ipc.o, 在shell中使用ipcs -q查询消息队列, 并使用ipcrm -q删除
c. exit结束进程后, 使用ipcs -q查询消息队列

代码:

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
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];

char* const child_args[] = {
"/bin/bash",
NULL
};

int child_main(void* args) {
printf("子进程启动\n");
sethostname("SilenceHostName", 15);
execv(child_args[0], child_args);
return 1;
}

int main(int argc, char** argv) {
printf("父进程启动\n");
int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, NULL);
waitpid(child_pid, NULL, 0);
printf("父进程结束\n");
return 0;
}

  1. pid namesapce

测试过程:
a. 在shell中启动进程/bin/bash -c "while true; do echo test; sleep 1; done" >/dev/null 2>&1
b. 启动test_pid.o, 在shell中使用查看当前shell的进程id是否为1(echo $$)
c. ps aux查看进程信息, 并使用kill -9删除步骤一中进程
d. exit结束进程后, 使用ps aux查看刚刚删除的pid是否存储

代码:

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
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];

char* const child_args[] = {
"/bin/bash",
NULL
};

int child_main(void* args) {
printf("子进程启动\n");
sethostname("SilenceHostName", 15);
execv(child_args[0], child_args);
return 1;
}

int main(int argc, char** argv) {
printf("父进程启动\n");
int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, NULL);
waitpid(child_pid, NULL, 0);
printf("父进程结束\n");
return 0;
}

测试结果:
a. test_pid.o启动后查看, shell进程id为1, 说明该shell进程为init进程
b. ps aux可以看到系统所有的进程信息
c. kill -9 不能删除系统中的进程
d. 系统中进程未kill

对于b步骤可以看到所有进程信息当时是不合理, 原因是test_pid.o启动的进程中的proc目录挂载到了系统中的/proc目录:
a. 可以使用mount重新挂载/proc目录: mount -t proc proc /proc, 此时使用ps aux只能看到两个进程信息
b. 发现在exit子进程后, 发现ps aux命令报错, 可以执行mount -t proc proc /proc修复,在此程序中未进行mount namespace隔离文件, 此时修改已影响到系统的文件系统信息

  1. mount namespace

设置挂载传播事件状态:

1
2
3
4
mount --make-shared <mount-object>      # 设置为共享挂载
mount --make-private <mount-object> # 设置为私有挂载
mount --make-slave <mount-object> # 设置为从属挂载
mount --make-unbindable <mount-object> # 设置为不可绑定挂载

测试过程:
a. 设置/proc目录为私有挂载
b. 启动test_ns.o, 在shell中挂载proc目录
c. exit子进程后, 使用ps aux查看进程信息是否报错

代码:

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
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];

char* const child_args[] = {
"/bin/bash",
NULL
};

int child_main(void* args) {
printf("子进程启动\n");
sethostname("SilenceHostName", 15);
execv(child_args[0], child_args);
return 1;
}

int main(int argc, char** argv) {
printf("父进程启动\n");
int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, NULL);
waitpid(child_pid, NULL, 0);
printf("父进程结束\n");
return 0;
}

测试结果:
a. 启动test_ns.o并执行mount -t proc proc /proc后使用ps aux只可看到两个进程
b. exit退出口使用ps aux可以看到系统上运行的所有进程

  1. netwrok namespace

  2. user namespace

测试过程:
a. 安装libcap-dev包,用来获取用户权限
b. 使用uid为1000的user编译test_user.c文件,并运行test_user.o

代码:

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
48
49
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/capability.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024)
#define PATH_SIZE 1024

static char child_stack[STACK_SIZE];

char* const child_args[] = {
"/bin/bash",
NULL
};

int set_map(pid_t pid, char* map_type, int inside_id, int outside_id, int length) {
char path[PATH_SIZE];
sprintf(path, "/proc/%d/%s", getpid(), map_type);
printf("%s %d %d %d\n", path, inside_id, outside_id, length);
FILE* handler = fopen(path, "w");
fprintf(handler, "%d %d %d", inside_id, outside_id, length);
fclose(handler);
return 0;
}

int child_main(void* args) {
printf("子进程启动\n");
set_map(getpid(), "gid_map", 0, 1000, 1);
set_map(getpid(), "uid_map", 0, 1000, 1);
cap_t caps;
printf("eUID=%d, eGID=%d\n", geteuid(), getegid());
caps = cap_get_proc();
printf("capabilities:%s\n", cap_to_text(caps, NULL));
execv(child_args[0], child_args);
return 1;
}

int main(int argc, char** argv) {
printf("父进程启动\n");
int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWUSER | SIGCHLD, NULL);
//int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, NULL);
waitpid(child_pid, NULL, 0);
printf("父进程结束\n");
return 0;
}

测试结果:
a. 启动test_user.o发现用户id已经修改为0
b. 组ID失败(需要查找原因)