0%

Matplotlib数据可视化第三方库(官网)。由各种可视化类构成。

matplotlib.pyplot

matplotlib.pyplot是绘制各类可视化图形的命令子库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/python
#encoding:utf-8

import matplotlib.pyplot as plt

plt.plot([1,2,3,4,5,6])
plt.ylabel("value")

# 将图像保存成PNG格式
plt.savefig("./test", dpi=600)


# x值[0,2,4,6,8]; y值[3,1,4,5,2]
plt.plot([0,2,4,6,8], [3,1,4,5,2])
plt.ylabel("value")

# X轴[-1,10]; Y轴[0,10]
plt.axis([-1,10,0,10])

plt.show()

plot

plt.plot(x,y,format_string, **kwargs)

  • x X轴数据,列表或数组,只有一条曲线的时候可选。
  • y Y轴数据,列表或数组。
  • format_string 控制曲线的各式字符串,可选。
  • kwargs 第二组或更多(x,y,format_string)

format_string分别由“颜色字符”、“风格字符”、“标记字符”构成

** 颜色字符 **

颜色字符 说明 颜色字符 说明
‘b’ 蓝色 ‘m’ 洋红色
‘g’ 绿色 ‘y’ 黄色
‘r’ 红色 ‘k’ 黑色
‘c’ 青色 ‘w’ 白色
‘#008000’ RGB某颜色 ‘0.8’ 灰度值字符串

如果用户不指定颜色,系统会自动指定不重复的颜色。

** 风格字符 **

风格字符 说明
‘-‘ 实现
‘–’ 破折线
‘-.’ 点划线
‘:’ 虚线
‘’ 无线条

*** 更多关于标记内容请见HERE ***

1
2
3
4
5
6
import numpy as np
import matplotlib.pyplot as plt

a = np.arange(10)
plt.plot(a,a*1.5,'go-', a,a*2.5,'rx', a,a*3.5,'*', a,a*4.5,'b-.')
plt.show()

分区绘制

subplot

plt.subplot(nrows, ncols, plot_number) 将绘制区域分割成 nrows x ncols个区域,plot_number指定绘制的是哪个区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
import matplotlib.pyplot as plt

def f(t):
return np.exp(-t) * np.cos(2*np.pi*t)

a = np.arange(0.0,5.0,2.0)

plt.subplot(2,1,1)
plt.plot(a, f(a))

plt.subplot(2,1,2)
plt.plot(a, np.cos(2*np.pi*a), 'r--')

plt.show()

参考&鸣谢

  • NumPy
  • PIL
    PIL(Python Image Library)库是一个具有强大图像处理能力的第三方库

图像=>数组

图像采用的色彩模式为RGB模式,即每个像素点由R(红色)、G(绿色)、B(蓝色)组成。我们人眼所能看到的颜色都是由这三种颜色变化叠加得到的。

  • R红色,取值范围,0~255
  • G绿色,取值范围,0~255
  • B蓝色,取值范围,0~255

图像是一个由像素组成的二维矩阵,每个元素是一个RGB值。

PIL安装

OSX: pip install pillow

图像处理

图像加载

turkey_orig.jpg

1
2
3
4
5
6
7
8
9
10
>>> from PIL import Image
>>> import numpy as np
>>> img = np.array(Image.open("turkey_orig.jpg"))
>>> img_l = np.array(Image.open("turkey_orig.jpg").convert('L'))
>>> print img.shape, img.dtype
(1184, 2104, 3) uint8
# img是一个三维数组每一个元素代表RGB的一个值
>>> print img_l.shape, img_l.shape
(1184, 2104) uint8
# img_l是一个二维数组每一个元素对应一个灰度值

图像变换

返选效果

1
md_img = [255,255,255] - img

turkey_modify_1.jpg

底片效果

1
md_img = 255 - img_l

turkey_modify_2.jpg

区间变换

1
md_img = (100.0/255.0)*img_l + 150

turkey_modify_3.jpg

平方变换

1
md_img = 255 * ((img_l/255.0)**2)

turkey_modify_4.jpg

手绘效果

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
img_l_float = img_l.astype("float")

# 深度值,范围为0~100
depth = 10

# 提取x和y方向的梯度值
grad = np.gradient(img_l_float)
grad_x, grad_y = grad

# 根据深度调整梯度值
grad_x = grad_x*depth/100
grad_y = grad_y*depth/100

# 此处计算的是什么?
A = np.sqrt(grad_x**2 + grad_y**2 + 1.0)
uni_x = grad_x/A
uni_y = grad_y/A
uni_z = 1.0/A

# 为什么要引入光源?
vec_el = np.pi/2.2
vec_az = np.pi/4.0
dx = np.cos(vec_el)*np.cos(vec_az)
dy = np.cos(vec_el)*np.sin(vec_az)
dz = np.sin(vec_el)

#dx, dy, dz = 1,1,1
md_img = 255*(dx*uni_x + dy*uni_y + dz*uni_z)
md_img = md_img.clip(0,255)

无光源效果:
turkey_modify_6.jpg

有光源效果:
turkey_modify_5.jpg

图像保存

1
2
save_img = Image.fromarray(md_img.astype('uint8'))
save_img.save("turkey_modify.jpg")

参考&鸣谢

NumPy是一个开源的Python科学计算基础库

  • 强大的N维数据兑现ndarry
  • 广播功能函数
  • 整合C/C++/Fortran代码的工具
  • 线性代数、傅立叶变换、随机数生成等功能

安装

OSX: pip install numpy

数组ndarray

  • 实际数据
  • 描述这些数据的元数据(数据的维度、数据类型等)

属性

属性 描述
ndim 维度的数量
shape 矩阵的行列数
size 数组元素个数
dtype 数组元素的类型
itemsize 数组元素的大小
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> import numpy as np
>>> a = np.array([[0,1,2,3],[1,2,3,4]])
>>> print a
[[0 1 2 3]
[1 2 3 4]]
>>> a.dim
2
>>> a.shape
(2, 4)
>>> a.size
8
>>> a.dtype
dtype('int64')
>>> a.itemsize
8

*** ndarray数组可以由非同质对象构成,非同质ndarray元素为对象类型。非同质ndarray对象无法有效发挥NumPy优势,应尽量避免使用。 ***

1
2
3
4
5
6
7
>>> b = np.array([[1,2,3],[1,2]])
>>> print b
[list([1, 2, 3]) list([1, 2])]
>>> b.dtype
dtype('O') # 'O'为对象类型
>>> b.shape
(2,)

ndarray元素类型

数据类型 描述
bool 布尔类型 True or False
intc 与C语言中的int一致
intp 与C语言中的size_t一致
int8 8位长度的整数[-128,127]
int16 16位长度的整数[-32768,32767]
int32 32位长度的整数[-231,231-1]
int64 64位长度的整数[-263,263-1]
uint8 8位无符合整数
uint16 16位无符号整数
uint32 32位无符号整数
uint64 64位无符号整数
float16 16位半精度浮点数
float32 32位半精度浮点数
float64 64位半精度浮点数
complex64 复数类型,实部和虚部都是32位浮点数
complex128 复数类型,实部和虚部都是64位浮点数

ndarray创建

从Python中的列表、元组等类型创建数组

np.array(list/tuple) or np.array(list/tuple, dtype=np.float32)np.array不指定dtype时,NumPy将根据数据情况关联一个dtype类型。

使用NumPy中的函数创建数组

函数 描述
np.arange(n) 返回一个从0到n-1的整数类型数组
np.ones(shape) 根据shape生成一个全1的数组
np.zeros(shape) 根据shape生成一个全0的数组
np.full(shape,val) 根据shape生成一个全val的数组
np.eye(n) 返回一个n*n的数组,对角线为1,其余为0
np.ones_like(a) 根据数组a的shape生成一个全1的数组
np.zeros_like(a) 根据数组a的shape生成一个全0的数组
np.full_like(a,val) 根据数组a的shape生成一个全val的数组
np.linspace() 根据起止数据等间距的填充数据,形成数组
np.concatenate() 将两个或多个数组合并成一个新的数组

arange

1
2
3
>>> d = np.arange(10)
>>> print d
[0 1 2 3 4 5 6 7 8 9]

ones zeros full

ones,zeros生成的数组元素都是浮点数类型(float64),而full生成的是整数类型。若想要oneszeros也生成整数类型,增加参数dtype=np.int64

1
2
3
4
5
6
7
8
9
10
11
12
>>> print np.zeros((3,4))
[[ 0. 0. 0. 0.]
[ 0. 0. 0. 0.]
[ 0. 0. 0. 0.]]
>>> print np.ones((3,4))
[[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]]
>>> print np.full((3,4), 8)
[[8 8 8 8]
[8 8 8 8]
[8 8 8 8]]

eye

1
2
3
4
5
6
7
>>> c = np.eye(5)
>>> print c
[[ 1. 0. 0. 0. 0.]
[ 0. 1. 0. 0. 0.]
[ 0. 0. 1. 0. 0.]
[ 0. 0. 0. 1. 0.]
[ 0. 0. 0. 0. 1.]]

linspace

1
2
3
4
>>> print np.linspace(1,10,4)
[ 1. 4. 7. 10.]
>>> print np.linspace(1,10,4, endpoint=False)
[ 1. 3.25 5.5 7.75]

endpoint表示最后一个元素10是否作为最后一个元素出现

concatenate

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> print np.concatenate((c,c))
[[ 1. 0. 0. 0. 0.]
[ 0. 1. 0. 0. 0.]
[ 0. 0. 1. 0. 0.]
[ 0. 0. 0. 1. 0.]
[ 0. 0. 0. 0. 1.]
[ 1. 0. 0. 0. 0.]
[ 0. 1. 0. 0. 0.]
[ 0. 0. 1. 0. 0.]
[ 0. 0. 0. 1. 0.]
[ 0. 0. 0. 0. 1.]]
>>> print np.concatenate((c,c)).shape
(10, 5)

ndarray维度变换

函数 描述
.reshape(shape) 不改变数组元素,返回一个shape形状的数组,原数组不变
.resize(shape) 与reshape功能一般,但修改原数组
.swapaxes(ax1,ax2) 将数组n个维度中的两个维度进行调换
.flatten() 对数组进行降维,返回一维数组,原数组不变

reshape

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> f = np.ones((2,3,4))
>>> print f.reshape((3,8))
[[ 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1.]]
>>> print f
[[[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]]

[[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]
[ 1. 1. 1. 1.]]]

resize

1
2
3
4
5
>>> f.resize((3,8))
>>> print f
[[ 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1.]]

swapaxes

1
2
3
4
5
6
7
8
9
>>> print np.arange(12).reshape((3,4))
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
>>> print np.arange(12).reshape((3,4)).swapaxes(0,1)
[[ 0 4 8]
[ 1 5 9]
[ 2 6 10]
[ 3 7 11]]

flatten

1
2
>>> print np.arange(12).reshape((3,4)).flatten()
[ 0 1 2 3 4 5 6 7 8 9 10 11]

ndarray变换类型

1
2
3
4
5
6
7
8
9
10
>>> g = np.arange(12)
>>> g.dtype
dtype('int64')
>>> print g
[ 0 1 2 3 4 5 6 7 8 9 10 11]
>>> g.astype(np.float)
array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.,
11.])
>>> print g
[ 0 1 2 3 4 5 6 7 8 9 10 11]

使用.astype改变Array数据类型,并不改变原数组的数据类型。

NumPy数组转换成Python列表

1
2
3
4
5
6
>>> type(g)
<type 'numpy.ndarray'>
>>> type(g.tolist())
<type 'list'>
>>> g.tolist()
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

ndarray获取操作

索引

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> h = np.arange(24).reshape((2,3,4))
>>> print h
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]

[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
>>> h[1,2,3]
23
>>> h[-1,-2,-3]
17

切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> h[:,1,-3]
array([ 5, 17])
>>> h[:,1:3,:]
array([[[ 4, 5, 6, 7],
[ 8, 9, 10, 11]],

[[16, 17, 18, 19],
[20, 21, 22, 23]]])
>>> h[:,:,::2]
array([[[ 0, 2],
[ 4, 6],
[ 8, 10]],

[[12, 14],
[16, 18],
[20, 22]]])

ndarray运算

与标量运算

1
2
3
4
5
6
7
8
>>> h / h.mean()
array([[[ 0. , 0.08695652, 0.17391304, 0.26086957],
[ 0.34782609, 0.43478261, 0.52173913, 0.60869565],
[ 0.69565217, 0.7826087 , 0.86956522, 0.95652174]],

[[ 1.04347826, 1.13043478, 1.2173913 , 1.30434783],
[ 1.39130435, 1.47826087, 1.56521739, 1.65217391],
[ 1.73913043, 1.82608696, 1.91304348, 2. ]]])

一元函数

函数 描述
np.abs(x) np.fabs(x) 计算元素的绝对值
np.sqrt(x) 计算元素的平方根
np.square(x) 计算元素的平方
np.log(x) np.log10(x) np.log2(x) 计算元素的自然对数、10为底、2为底对数
np.ceil(x) np.floor(x) 计算元素的ceiliing floor值
np.rint(x) 计算元素的四舍五入值
np.modf(x) 计算元素的小数和整数部分以两个独立的数组形式返回
np.cos/cosh/sin/sinh/tan/tanh(x) 计算三角函数
np.exp(x) 计算元素的指数值
np.sign(x) 计算元素的符号值,1(+),0(0),-1(-)

二元函数

函数 描述
+ - * / ** 加、减、乘、除、幂
np.maximum(x,y) np.fmax() 取最大值
np.minimum(x,y) np.fmin() 取最小值
np.mod(x,y) 模运算
np.copysign(x,y) 将数组y中各元素的符号赋值给数组x对应的元素
> < >= <= == != 产生布尔型数组
1
2
3
4
5
6
7
8
>>> h > (h/h.mean())
array([[[False, True, True, True],
[ True, True, True, True],
[ True, True, True, True]],

[[ True, True, True, True],
[ True, True, True, True],
[ True, True, True, True]]], dtype=bool)

NumPy I/O

CSV文件

*** CSV只能有效存储一维和二维数组 ***

写入CSV文件

np.savetxt(frame, array, fmt='%.18e', delimiter=None)

  • frame
    文件、字符串或产生器,可以是.gz.bz2的压缩文件。
  • array
    存入文件的数组
  • fmt
    写入文件的格式,eg: %d %.2f %.18e
  • delimiter
    分割字符串,默认是空格。
1
2
>>> np.savetxt('h.csv', h.reshape((3,8)), fmt="%d", delimiter=',')
>>>
1
2
3
4
$ cat h.csv
0,1,2,3,4,5,6,7
8,9,10,11,12,13,14,15
16,17,18,19,20,21,22,23

读取CSV文件

np.loadtxt(frame, dtype=np.float, delimiter=None, unpack=False)

  • frame
    文件、字符串或产生器,可以是.gz.bz2的压缩文件。
  • dtype
    数据类型,可选。
  • delimiter
    分割字符串,默认是空格。
  • unpack
    True,表示读入属性将分别写入不同变量。
1
2
3
4
>>> np.loadtxt('h.csv', delimiter=',')
array([[ 0., 1., 2., 3., 4., 5., 6., 7.],
[ 8., 9., 10., 11., 12., 13., 14., 15.],
[ 16., 17., 18., 19., 20., 21., 22., 23.]])

多维度数据I/O

写入文件

ndarray.tofile(frame, sep='', format='%s')

  • frame
    文件,字符串
  • sep
    分割字符串,如果是空串,写入文件为二进制。
  • format
    写入数据的格式
1
2
>>> h.tofile('h.dat', sep=',', format='%d')
>>>
1
2
$ cat h.dat
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23

读取文件

np.fromfile(frame, dtype=np.float, count=-1, sep='')

  • frame
    文件、字符串
  • dtype
    读取的数据类型
  • count
    读入元素个数,-1表示读入整个文件。
  • sep
    分割字符串,如果是空串,读取文件为二进制。
1
2
3
4
5
6
7
8
>>> h.tofile('h.dat', sep=',', format='%d')
>>> np.fromfile('h.dat', dtype=np.int, sep=',')
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23])
>>> np.fromfile('h.dat', dtype=np.int, sep=',').reshape((3,8))
array([[ 0, 1, 2, 3, 4, 5, 6, 7],
[ 8, 9, 10, 11, 12, 13, 14, 15],
[16, 17, 18, 19, 20, 21, 22, 23]])

使用fromfile读取文件,需要知道数组的shape

便捷文件读写

写入文件

np.save(frame, array) or np.savez(frame, array)

  • frame
    文件名,以.npy为扩展名,压缩扩展名为.npz
  • array
    数组变量
1
>>> np.save('h.npy',h)
1
2
3
4
5
$ cat h.npy
�NUMPYF{'descr': '<i8', 'fortran_order': False, 'shape': (2, 3, 4), }



读取文件

np.load(frame)

  • frame
    文件名,以.npy为扩展名,压缩扩展名为.npz
1
2
3
4
5
6
7
8
>>> np.load('h.npy')
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]],

[[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]]])

NumPy的随机函数

import numpy.random.* or np.random.rand()np.random.randn()np.random.randint()

随机产生

函数 描述
rand(d0,d1…,dn) 根据d0-dn创建随机数数组,浮点数,[0,1),均匀分布
randn(d0,d1…,dn) 根据d0-dn创建随机数数组,标准正态分布
randint(low[,high,shape]) 根据shape创建随机整数或整数数组,范围是[low,high)
seed(s) 随机数种子,s是给定的种子值
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
>>> np.random.rand(2,3,4)
array([[[ 0.90829822, 0.22946923, 0.6652444 , 0.99272168],
[ 0.06080758, 0.44248097, 0.93218605, 0.52258628],
[ 0.72315102, 0.15270485, 0.78927709, 0.69494654]],

[[ 0.35967592, 0.94780636, 0.70360292, 0.78655759],
[ 0.99834954, 0.5114802 , 0.41374511, 0.52093554],
[ 0.76139948, 0.71690824, 0.05146873, 0.24752045]]])
>>> np.random.randn(2,3,4)
array([[[ 0.27215006, 0.88541886, -1.23316095, -0.33090933],
[-0.02253028, 0.09407495, 1.76527947, -0.29149431],
[-0.3821042 , 0.29664845, -0.73678807, -1.0182318 ]],

[[-0.46816445, -1.62242958, -0.98800274, 0.5167897 ],
[-0.14876304, 0.60325633, -1.22824121, 1.40063683],
[ 0.7508062 , 0.42850387, 0.67736631, 0.68300665]]])
>>> np.random.randint(50,80,(2,3,4))
array([[[67, 79, 65, 56],
[76, 60, 58, 70],
[74, 61, 53, 71]],

[[72, 67, 61, 53],
[58, 66, 52, 69],
[60, 70, 73, 64]]])
>>> np.random.seed(1111)
>>> np.random.randint(50,80,(2,3,4))
array([[[78, 73, 55, 67],
[62, 52, 70, 74],
[72, 70, 61, 64]],

[[58, 56, 62, 64],
[68, 72, 58, 59],
[79, 69, 60, 62]]])
>>> np.random.seed(1111)
>>> np.random.randint(50,80,(2,3,4))
array([[[78, 73, 55, 67],
[62, 52, 70, 74],
[72, 70, 61, 64]],

[[58, 56, 62, 64],
[68, 72, 58, 59],
[79, 69, 60, 62]]])

随机排序拾取

函数 描述
shuffle(a) 对数组a按照最外维度进行随机排列,数组a改变
permutation(a) 对数组a按照最外维度进行随机排列,数组a不改变,生成新数组
choice(a[,size,replace,p]) 从一维数组中以概率p抽取元素,形成size形状新数组,replace表示是否重复抽取(默认True)
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
>>> i = h.reshape((3,8))
>>> print i
[[ 0 1 2 3 4 5 6 7]
[ 8 9 10 11 12 13 14 15]
[16 17 18 19 20 21 22 23]]
>>> print np.random.permutation(i)
[[ 8 9 10 11 12 13 14 15]
[16 17 18 19 20 21 22 23]
[ 0 1 2 3 4 5 6 7]]
>>> print i
[[ 0 1 2 3 4 5 6 7]
[ 8 9 10 11 12 13 14 15]
[16 17 18 19 20 21 22 23]]
>>> np.random.shuffle(i)
>>> print i
[[16 17 18 19 20 21 22 23]
[ 0 1 2 3 4 5 6 7]
[ 8 9 10 11 12 13 14 15]]
>>> np.random.choice(i.flatten(), size=(3,4))
array([[ 1, 1, 15, 10],
[16, 20, 0, 21],
[13, 9, 5, 10]])
>>> np.random.choice(i.flatten(), size=(3,4), replace=False)
array([[ 6, 2, 12, 19],
[ 7, 9, 13, 22],
[17, 21, 20, 0]])

随机分布

函数 描述
uniform(low,high,size) 产生均匀分布的数组,low起始值,hight结束值,size形状
normal(loc,scale,size) 产生正态分布的数组,loc均值,scale标准差,size形状
poisson(lam,size) 产生泊松分布的数组,lam随机事件发生率,size形状
1
2
3
4
5
6
7
8
>>> np.random.uniform(0,10,(3,4))
array([[ 7.60854954, 6.30411065, 0.79332327, 1.30857967],
[ 3.00316754, 3.22281521, 1.61121489, 3.15193241],
[ 5.25528483, 6.45849382, 7.50645475, 0.56027292]])
>>> np.random.normal(10,5,(3,4))
array([[ 8.29746473, 14.35578466, 9.40820399, 9.47806228],
[ 20.81393417, 6.16603104, 2.10005772, 11.39870886],
[ 11.81287925, 5.19702161, 11.13850168, 13.95413254]])

NumPy的统计函数

NumPy直接提供库一级别的统计函数import numpy.* or np.std()np.var()

函数 描述
sum(a, axis=None) 对数组a求和,默认所有元素,axis指定维度
mean((a, axis=None)a 对数组a求平均值,默认所有元素,axis指定维度
average(a, axis=None, weights=None) 对数组a求加权平均值,默认所有元素,axis指定维度
std(a, axis=None) 对数组a求标准差,默认所有元素,axis指定维度
var(a, axis=None) 对数组a求方差,默认所有元素,axis指定维度
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
>>> print h
[[[16 17 18 19]
[20 21 22 23]
[ 0 1 2 3]]

[[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]]
>>> np.sum(h)
276
>>> np.sum(h,axis=0)
array([[20, 22, 24, 26],
[28, 30, 32, 34],
[12, 14, 16, 18]])
>>> np.sum(h,axis=1)
array([[36, 39, 42, 45],
[24, 27, 30, 33]])
>>> np.sum(h,axis=2)
array([[70, 86, 6],
[22, 38, 54]])
>>> np.average(h,axis=1,weights=[10,5,1])
array([[ 16.25, 17.25, 18.25, 19.25],
[ 5.75, 6.75, 7.75, 8.75]])
# (16*10 + 20*5 + 0*1)/(10+5+1)=16.25
# (17*10 + 21*5 + 1*1)/(10+5+1)=17.25
# (18*10 + 22*5 + 2*1)/(10+5+1)=18.25
函数 描述
min(a) max(a) 数组a中的最小值、最大值
argmin(a) argmax(a) 数组a中最小值、最大值降到一维后的下标索引
unravel_index(index,shape) 根据shape将一维下标索引index转换成多维下标索引
ptp(a) 数组a中最大值与最小值的差
median(a) 数组a中元素的中位数(中值)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> print h
[[[16 17 18 19]
[20 21 22 23]
[ 0 1 2 3]]

[[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]]
>>> np.max(h)
23
>>> np.argmax(h)
7
>>> np.unravel_index(np.argmax(h), (2,3,4))
(0, 1, 3)
>>> np.ptp(h)
23
>>> np.median(h)
11.5

NumPy的梯度函数

梯度

连续值之间的变化率,即斜率。若有a,b,c三个值,b存在两侧值,b的梯度=(c-a)/2;若有a,b两个值,b只有一侧值,b或a的梯度=(b-a)/1

梯度计算

np.gradient(a)计算数组a中的梯度,若a为多维数组,返回每个维度梯度

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
>>> np.gradient(h)
[array([[[-12., -12., -12., -12.],
[-12., -12., -12., -12.],
[ 12., 12., 12., 12.]],

[[-12., -12., -12., -12.],
[-12., -12., -12., -12.],
[ 12., 12., 12., 12.]]]),
array([[[ 4., 4., 4., 4.],
[ -8., -8., -8., -8.],
[-20., -20., -20., -20.]],

[[ 4., 4., 4., 4.],
[ 4., 4., 4., 4.],
[ 4., 4., 4., 4.]]]),
array([[[ 1., 1., 1., 1.],
[ 1., 1., 1., 1.],
[ 1., 1., 1., 1.]],

[[ 1., 1., 1., 1.],
[ 1., 1., 1., 1.],
[ 1., 1., 1., 1.]]])]
>>> print h
[[[16 17 18 19]
[20 21 22 23]
[ 0 1 2 3]]

[[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]]

*** 梯度计算用于发现声音或图像数据的边缘,当梯度发生很大变化时,此处即为边缘 ***

参考&鸣谢

在Hbase中,只有一个单一的按照字典序排序的rowKey索引,当使用rowKey来进行数据查询的时候速度较快,但是如果不使用rowKey来查询的话就会使用filter来对全表进行扫描,很大程度上降低了检索性能。而Phoenix提供了二级索引技术来应对这种使用rowKey之外的条件进行检索的场景。

Global Index

Global indexing适用于多读少写的业务场景。使用Global indexing的话在写数据的时候会消耗大量开销,因为所有对数据表的更新操作(DELETE, UPSERT VALUES and UPSERT SELECT),会引起索引表的更新,而索引表是分布在不同的数据节点上的,跨节点的数据传输带来了较大的性能消耗。在读数据的时候Phoenix会选择索引表来降低查询消耗的时间。*** 在默认情况下如果想查询的字段不是索引字段的话索引表不会被使用,也就是说不会带来查询速度的提升。 ***

配置

HBase集群的每个regionserver节点的hbase-site.xml中加入配置,并重启HBase集群

1
2
3
4
<property>
<name>hbase.regionserver.wal.codec</name>
<value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>

注: phoenix有两种链接方式JDBC和phoenix-client,phoenix-client可以正常创建索引;zeppelin使用jdbc连接不能正常创建索引,提示如下错误。

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
java.sql.SQLException: ERROR 1029 (42Y88): Mutable secondary indexes must have the hbase.regionserver.wal.codec property set to org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec in the hbase-sites.xml of every region server. tableName=CMP_IDX
at org.apache.phoenix.exception.SQLExceptionCode$Factory$1.newException(SQLExceptionCode.java:464)
at org.apache.phoenix.exception.SQLExceptionInfo.buildException(SQLExceptionInfo.java:150)
at org.apache.phoenix.schema.MetaDataClient.createIndex(MetaDataClient.java:1293)
at org.apache.phoenix.compile.CreateIndexCompiler$1.execute(CreateIndexCompiler.java:85)
at org.apache.phoenix.jdbc.PhoenixStatement$2.call(PhoenixStatement.java:358)
at org.apache.phoenix.jdbc.PhoenixStatement$2.call(PhoenixStatement.java:341)
at org.apache.phoenix.call.CallRunner.run(CallRunner.java:53)
at org.apache.phoenix.jdbc.PhoenixStatement.executeMutation(PhoenixStatement.java:340)
at org.apache.phoenix.jdbc.PhoenixStatement.execute(PhoenixStatement.java:1511)
at org.apache.commons.dbcp2.DelegatingStatement.execute(DelegatingStatement.java:291)
at org.apache.commons.dbcp2.DelegatingStatement.execute(DelegatingStatement.java:291)
at org.apache.zeppelin.jdbc.JDBCInterpreter.executeSql(JDBCInterpreter.java:581)
at org.apache.zeppelin.jdbc.JDBCInterpreter.interpret(JDBCInterpreter.java:692)
at org.apache.zeppelin.interpreter.LazyOpenInterpreter.interpret(LazyOpenInterpreter.java:97)
at org.apache.zeppelin.interpreter.remote.RemoteInterpreterServer$InterpretJob.jobRun(RemoteInterpreterServer.java:498)
at org.apache.zeppelin.scheduler.Job.run(Job.java:175)
at org.apache.zeppelin.scheduler.ParallelScheduler$JobRunner.run(ParallelScheduler.java:162)
at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)

索引操作

在开始创建索引以前先创建一个表,并向其中填充测试数据(此处填充的数据仅用于功能测试)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0: jdbc:phoenix:> create table usertable (id varchar primary key,firstname varchar, lastname varchar);
0: jdbc:phoenix:> !tables
+------------+--------------+-------------------------+---------------+----------+------------+----------------------------+-------------+
| TABLE_CAT | TABLE_SCHEM | TABLE_NAME | TABLE_TYPE | REMARKS | TYPE_NAME | SELF_REFERENCING_COL_NAME | REF_GENERAT |
+------------+--------------+-------------------------+---------------+----------+------------+----------------------------+-------------+
| | SYSTEM | CATALOG | SYSTEM TABLE | | | | |
| | SYSTEM | FUNCTION | SYSTEM TABLE | | | | |
| | SYSTEM | SEQUENCE | SYSTEM TABLE | | | | |
| | SYSTEM | STATS | SYSTEM TABLE | | | | |
| | | USERTABLE | TABLE | | | | |
+------------+--------------+-------------------------+---------------+----------+------------+----------------------------+-------------+

0: jdbc:phoenix:> upsert into usertable(id, firstname, lastname) values('1', 'Hello', 'world');
...
0: jdbc:phoenix:> upsert into usertable(id, firstname, lastname) values('5', 'Hello', 'phoenix');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
base(main):019:0> scan 'USERTABLE'
ROW COLUMN+CELL
1 column=0:FIRSTNAME, timestamp=1508397473229, value=Hello
1 column=0:LASTNAME, timestamp=1508397473229, value=world
1 column=0:_0, timestamp=1508397473229, value=x
2 column=0:FIRSTNAME, timestamp=1508397529852, value=Hello
2 column=0:LASTNAME, timestamp=1508397529852, value=world
2 column=0:_0, timestamp=1508397529852, value=x
3 column=0:FIRSTNAME, timestamp=1508397535836, value=Hello
3 column=0:LASTNAME, timestamp=1508397535836, value=world
3 column=0:_0, timestamp=1508397535836, value=x
4 column=0:FIRSTNAME, timestamp=1508397643594, value=Hello
4 column=0:LASTNAME, timestamp=1508397643594, value=phoenix
4 column=0:_0, timestamp=1508397643594, value=x
5 column=0:FIRSTNAME, timestamp=1508397648938, value=Hello
5 column=0:LASTNAME, timestamp=1508397648938, value=phoenix
5 column=0:_0, timestamp=1508397648938, value=x
5 row(s) in 0.3220 seconds

创建索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
0: jdbc:phoenix:> create index idx_name on usertable (lastname) include(firstname);
0: jdbc:phoenix:> 0: jdbc:phoenix:> !tables
+------------+--------------+-------------------------+---------------+----------+------------+----------------------------+-------------+
| TABLE_CAT | TABLE_SCHEM | TABLE_NAME | TABLE_TYPE | REMARKS | TYPE_NAME | SELF_REFERENCING_COL_NAME | REF_GENERAT |
+------------+--------------+-------------------------+---------------+----------+------------+----------------------------+-------------+
| | | IDX_NAME | INDEX | | | | |
| | SYSTEM | CATALOG | SYSTEM TABLE | | | | |
| | SYSTEM | FUNCTION | SYSTEM TABLE | | | | |
| | SYSTEM | SEQUENCE | SYSTEM TABLE | | | | |
| | SYSTEM | STATS | SYSTEM TABLE | | | | |
| | | USERTABLE | TABLE | | | | |
+------------+--------------+-------------------------+---------------+----------+------------+----------------------------+-------------+
0: jdbc:phoenix:> !index usertable
+------------+--------------+-------------+-------------+------------------+-------------+-------+-------------------+--------------+----+
| TABLE_CAT | TABLE_SCHEM | TABLE_NAME | NON_UNIQUE | INDEX_QUALIFIER | INDEX_NAME | TYPE | ORDINAL_POSITION | COLUMN_NAME | AS |
+------------+--------------+-------------+-------------+------------------+-------------+-------+-------------------+--------------+----+
| | | USERTABLE | true | | IDX_NAME | 3 | 1 | 0:LASTNAME | A |
| | | USERTABLE | true | | IDX_NAME | 3 | 2 | :ID | A |
| | | USERTABLE | true | | IDX_NAME | 3 | 3 | 0:FIRSTNAME | |
+------------+--------------+-------------+-------------+------------------+-------------+-------+-------------------+--------------+----+
1
2
3
4
5
6
7
8
9
10
11
12
13
hbase(main):020:0> scan 'IDX_NAME'
ROW COLUMN+CELL
phoenix\x004 column=0:0:FIRSTNAME, timestamp=1508397686228, value=Hello
phoenix\x004 column=0:_0, timestamp=1508397686228, value=x
phoenix\x005 column=0:0:FIRSTNAME, timestamp=1508397686228, value=Hello
phoenix\x005 column=0:_0, timestamp=1508397686228, value=x
world\x001 column=0:0:FIRSTNAME, timestamp=1508397686228, value=Hello
world\x001 column=0:_0, timestamp=1508397686228, value=x
world\x002 column=0:0:FIRSTNAME, timestamp=1508397686228, value=Hello
world\x002 column=0:_0, timestamp=1508397686228, value=x
world\x003 column=0:0:FIRSTNAME, timestamp=1508397686228, value=Hello
world\x003 column=0:_0, timestamp=1508397686228, value=x
5 row(s) in 0.3180 seconds

使用索引

正常的select … where … 是不会用到索引表的,要想用到索引表,必须查询出的字段也是索引字段。(此处的结论需要在后续的性能测试中进行验证)

1
2
3
4
5
6
7
0: jdbc:phoenix:> select firstname from usertable where lastname = 'phoenix';
+------------+
| FIRSTNAME |
+------------+
| Hello |
| Hello |
+------------+

性能测试

Todo…

Todo…

Local Index

Todo…

参考&鸣谢

一个磁盘可以划分成多个分区,每个分区必须先用格式化工具(例如某种mkfs命令)格式化成某种格式的文件系统,然后才能存储文件,格式化的过程会在磁盘上写一些管理存储布局的信息。

格式

文件系统中存储的最小单位是块(Block),一个块究竟多大是在格式化时确定的,例如mke2fs的-b选项可以设定块大小为1024、2048或4096字节。而上图中启动块(Boot Block)的大小是确定的,就是1KB,启动块是由PC标准规定的,用来存储磁盘分区信息和启动信息,任何文件系统都不能使用启动块。启动块之后才是ext2文件系统的开始,ext2文件系统将整个分区划成若干个同样大小的块组(Block Group)

fs-ext2layout.png

做一个EXT2看看

做一个EXT2的文件系统,然后将其挂载,看看其文件格式。

1
2
3
4
5
6
$ dd if=/dev/zero of=./img.1m count=256 bs=4K
...
$ mke2fs fs
...
$ sudo mount -o loop ./img.1m /mnt
$ sudo umount /mnt

使用od查看img.1m镜像文件

1
od -tx1 -Ax ./img.1m

Super Block

描述整个分区的文件系统信息,例如块大小、文件系统版本号、上次mount的时间等等。超级块在每个块组的开头都有一份拷贝。

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
50
51
52
53
54
struct ext2_super_block {
__le32 s_inodes_count; /* Inodes count */
__le32 s_blocks_count; /* Blocks count */
__le32 s_r_blocks_count; /* Reserved blocks count */
__le32 s_free_blocks_count; /* Free blocks count */
__le32 s_free_inodes_count; /* Free inodes count */
__le32 s_first_data_block; /* First Data Block */
__le32 s_log_block_size; /* Block size */
__le32 s_log_frag_size; /* Fragment size */
__le32 s_blocks_per_group; /* # Blocks per group */
__le32 s_frags_per_group; /* # Fragments per group */
__le32 s_inodes_per_group; /* # Inodes per group */
__le32 s_mtime; /* Mount time */
__le32 s_wtime; /* Write time */
__le16 s_mnt_count; /* Mount count */
__le16 s_max_mnt_count; /* Maximal mount count */
__le16 s_magic; /* Magic signature */
__le16 s_state; /* File system state */
__le16 s_errors; /* Behaviour when detecting errors */
__le16 s_minor_rev_level; /* minor revision level */
__le32 s_lastcheck; /* time of last check */
__le32 s_checkinterval; /* max. time between checks */
__le32 s_creator_os; /* OS */
__le32 s_rev_level; /* Revision level */
__le16 s_def_resuid; /* Default uid for reserved blocks */
__le16 s_def_resgid; /* Default gid for reserved blocks */
__le32 s_first_ino; /* First non-reserved inode */
__le16 s_inode_size; /* size of inode structure */
__le16 s_block_group_nr; /* block group # of this superblock */
__le32 s_feature_compat; /* compatible feature set */
__le32 s_feature_incompat; /* incompatible feature set */
__le32 s_feature_ro_compat; /* readonly-compatible feature set */
__u8 s_uuid[16]; /* 128-bit uuid for volume */
char s_volume_name[16]; /* volume name */
char s_last_mounted[64]; /* directory where last mounted */
__le32 s_algorithm_usage_bitmap; /* For compression */
__u8 s_prealloc_blocks; /* Nr of blocks to try to preallocate*/
__u8 s_prealloc_dir_blocks; /* Nr to preallocate for dirs */
__u16 s_padding1;
/*
* Journaling support valid if EXT3_FEATURE_COMPAT_HAS_JOURNAL set.
*/
__u8 s_journal_uuid[16]; /* uuid of journal superblock */
__u32 s_journal_inum; /* inode number of journal file */
__u32 s_journal_dev; /* device number of journal file */
__u32 s_last_orphan; /* start of list of inodes to delete */
__u32 s_hash_seed[4]; /* HTREE hash seed */
__u8 s_def_hash_version; /* Default hash version to use */
__u8 s_reserved_char_pad;
__u16 s_reserved_word_pad;
__le32 s_default_mount_opts;
__le32 s_first_meta_bg; /* First metablock block group */
__u32 s_reserved[190]; /* Padding to the end of the block */
};

fs.sb.png

从000000开始的1KB是启动块,由于这不是一个真正的磁盘分区,启动块的内容全部为零。从000400到0007ff的1KB是超级块,可以对照着dumpe2fs分析。

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
$ dumpe2fs ./img.1m                                                                                                                 1 
dumpe2fs 1.42.9 (28-Dec-2013)
Filesystem volume name: <none>
Last mounted on: <not available>
Filesystem UUID: 2b8250d9-29fa-459e-9e2d-8b9c887b860b
Filesystem magic number: 0xEF53
Filesystem revision #: 1 (dynamic)
Filesystem features: ext_attr resize_inode dir_index filetype sparse_super
Filesystem flags: signed_directory_hash
Default mount options: user_xattr acl
Filesystem state: not clean
Errors behavior: Continue
Filesystem OS type: Linux
Inode count: 128
Block count: 1024
Reserved block count: 51
Free blocks: 986
Free inodes: 117
First block: 1
Block size: 1024
Fragment size: 1024
Reserved GDT blocks: 3
Blocks per group: 8192
Fragments per group: 8192
Inodes per group: 128
Inode blocks per group: 16
Filesystem created: Wed Oct 25 09:56:05 2017
Last mount time: Wed Oct 25 09:56:48 2017
Last write time: Wed Oct 25 09:56:48 2017
Mount count: 1
Maximum mount count: -1
Last checked: Wed Oct 25 09:56:05 2017
Check interval: 0 (<none>)
Reserved blocks uid: 0 (user root)
Reserved blocks gid: 0 (group root)
First inode: 11
Inode size: 128
Default directory hash: half_md4
Directory Hash Seed: 2feeee23-e3de-4126-a60d-1d05a11b0ab3
...

块大小是1024字节,1MB的分区共有1024个块,第0个块是启动块,启动块之后才算ext2文件系统的开始,因此Group 0占据第1个到第1023个块,共1023个块。块位图占一个块,共有1024×8=8192个bit,足够表示这1023个块了,因此只要一个块组就够了。默认是每8KB分配一个inode,因此1MB的分区对应128个inode,这些数据都和dumpe2fs的输出吻合。

GDT(Group Descriptor Table)

由很多块组描述符组成,整个分区分成多少个块组就对应有多少个块组描述符。每个块组描述符(Group Descriptor)存储一个块组的描述信息,例如在这个块组中从哪里开始是inode表,从哪里开始是数据块,空闲的inode和数据块还有多少个等等。和超级块类似,块组描述符表在每个块组的开头也都有一份拷贝,这些信息是非常重要的,一旦超级块意外损坏就会丢失整个分区的数据,一旦块组描述符意外损坏就会丢失整个块组的数据,因此它们都有多份拷贝。通常内核只用到第0个块组中的拷贝,当执行e2fsck检查文件系统一致性时,第0个块组中的超级块和块组描述符表就会拷贝到其它块组,这样当第0个块组的开头意外损坏时就可以用其它拷贝来恢复,从而减少损失。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
* Structure of a blocks group descriptor
*/
struct ext2_group_desc
{
__le32 bg_block_bitmap; /* Blocks bitmap block */
__le32 bg_inode_bitmap; /* Inodes bitmap block */
__le32 bg_inode_table; /* Inodes table block */
__le16 bg_free_blocks_count; /* Free blocks count */
__le16 bg_free_inodes_count; /* Free inodes count */
__le16 bg_used_dirs_count; /* Directories count */
__le16 bg_pad;
__le32 bg_reserved[3];
};

fs.gd.png

从000800开始是块组描述符表,这个文件系统较小,只有一个块组描述符,对照着dumpe2fs的输出信息分析

1
2
3
4
5
6
7
8
9
10
11
$ dumpe2fs ./img.1m                                                                                                                 1 
...
Group 0: (Blocks 1-1023)
主 superblock at 1, Group descriptors at 2-2
保留的GDT块位于 3-5
Block bitmap at 6 (+5), Inode bitmap at 7 (+6)
Inode表位于 8-23 (+7)
986 free blocks, 117 free inodes, 2 directories
可用块数: 38-1023
可用inode数: 12-128k
...

整个文件系统是1MB,每个块是1KB,应该有1024个块,除去启动块还有1023个块,分别编号为1-1023,它们全都属于Group 0。其中,Block 1是超级块,接下来的块组描述符指出,块位图是Block 6,因此中间的Block 2-5是块组描述符表,其中Block 3-5保留未用。块组描述符还指出,inode位图是Block 7,inode表是从Block 8开始的,那么inode表到哪个块结束呢?由于超级块中指出每个块组有128个inode,每个inode的大小是128字节,因此共占16个块,inode表的范围是Block 8-23。从Block 24开始就是数据块了。块组描述符中指出,空闲的数据块有986个,由于文件系统是新创建的,空闲块是连续的Block 38-1023,用掉了前面的Block 24-37

Block Bitmap

一个块组中的块是这样利用的:数据块(Data Block)存储所有文件的数据,比如某个分区的块大小是1024字节,某个文件是2049字节,那么就需要三个数据块来存,即使第三个块只存了一个字节也需要占用一个整块;超级块、块组描述符表、块位图、inode位图、inode表这几部分存储该块组的描述信息。那么如何知道哪些块已经用来存储文件数据或其它描述信息,哪些块仍然空闲可用呢?块位图就是用来描述整个块组中哪些块已用哪些块空闲的,它本身占一个块,其中的每个bit代表本块组中的一个块,这个bit为1表示该块已用,这个bit为0表示该块空闲可用。
为什么用df命令统计整个磁盘的已用空间非常快呢?因为只需要查看每个块组的块位图即可,而不需要搜遍整个分区。相反,用du命令查看一个较大目录的已用空间就非常慢,因为不可避免地要搜遍整个目录的所有文件。
与此相联系的另一个问题是:在格式化一个分区时究竟会划出多少个块组呢?主要的限制在于块位图本身必须只占一个块。用mke2fs格式化时默认块大小是1024字节,可以用-b参数指定块大小,现在设块大小指定为b字节,那么一个块可以有8b个bit,这样大小的一个块位图就可以表示8b个块的占用情况,因此一个块组最多可以有8b个块,如果整个分区有s个块,那么就可以有s/(8b)个块组。格式化时可以用-g参数指定一个块组有多少个块,但是通常不需要手动指定,mke2fs工具会计算出最优的数值。

fs_blk_bmp.png

从块位图中可以看出,前37位(前4个字节加最后一个字节的低5位)都是1,,就表示Block 1-37已用。在块位图中,Block 38-1023对应的位都是0(一直到001870那一行最后一个字节的低7位),接下来的位已经超出了文件系统的空间,不管是0还是1都没有意义。可见,块位图每个字节中的位应该按从低位到高位的顺序来看。以后随着文件系统的使用和添加删除文件,块位图中的1就变得不连续了。

Inode Bitmap

和块位图类似,本身占一个块,其中每个bit表示一个inode是否空闲可用。

fs_inode_bmp.png

块组描述符指出,空闲的inode有117个,由于文件系统是新创建的,空闲的inode也是连续的,inode编号从1到128,空闲的inode编号从12到128。从inode位图可以看出,前11位都是1,表示前11个inode已用。以后随着文件系统的使用和添加删除文件,inode位图中的1就变得不连续了。001c00这一行的128位就表示了所有inode,因此下面的行不管是0还是1都没有意义。已用的11个inode中,前10个inode是被ext2文件系统保留的,其中第2个inode是根目录,第11个inode是lost+found目录,块组描述符也指出该组有两个目录,就是根目录和lost+found。

Inode Table

一个文件除了数据需要存储之外,一些描述信息也需要存储,例如文件类型(常规、目录、符号链接等),权限,文件大小,创建/修改/访问时间等,也就是ls -l命令看到的那些信息,这些信息存在inode中而不是数据块中。每个文件都有一个inode,一个块组中的所有inode组成了inode表。
inode表占多少个块在格式化时就要决定并写入块组描述符中,mke2fs格式化工具的默认策略是一个块组有多少个8KB就分配多少个inode。由于数据块占了整个块组的绝大部分,也可以近似认为数据块有多少个8KB就分配多少个inode,换句话说,如果平均每个文件的大小是8KB,当分区存满的时候inode表会得到比较充分的利用,数据块也不浪费。如果这个分区存的都是很大的文件(比如电影),则数据块用完的时候inode会有一些浪费,如果这个分区存的都是很小的文件(比如源代码),则有可能数据块还没用完inode就已经用完了,数据块可能有很大的浪费。如果用户在格式化时能够对这个分区以后要存储的文件大小做一个预测,也可以用mke2fs的-i参数手动指定每多少个字节分配一个inode。

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
50
51
52
53
54
55
56
/*
* Structure of an inode on the disk
*/
struct ext2_inode {
__le16 i_mode; /* File mode */
__le16 i_uid; /* Low 16 bits of Owner Uid */
__le32 i_size; /* Size in bytes */
__le32 i_atime; /* Access time */
__le32 i_ctime; /* Creation time */
__le32 i_mtime; /* Modification time */
__le32 i_dtime; /* Deletion Time */
__le16 i_gid; /* Low 16 bits of Group Id */
__le16 i_links_count; /* Links count */
__le32 i_blocks; /* Blocks count */
__le32 i_flags; /* File flags */
union {
struct {
__le32 l_i_reserved1;
} linux1;
struct {
__le32 h_i_translator;
} hurd1;
struct {
__le32 m_i_reserved1;
} masix1;
} osd1; /* OS dependent 1 */
__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
__le32 i_generation; /* File version (for NFS) */
__le32 i_file_acl; /* File ACL */
__le32 i_dir_acl; /* Directory ACL */
__le32 i_faddr; /* Fragment address */
union {
struct {
__u8 l_i_frag; /* Fragment number */
__u8 l_i_fsize; /* Fragment size */
__u16 i_pad1;
__le16 l_i_uid_high; /* these 2 fields */
__le16 l_i_gid_high; /* were reserved2[0] */
__u32 l_i_reserved2;
} linux2;
struct {
__u8 h_i_frag; /* Fragment number */
__u8 h_i_fsize; /* Fragment size */
__le16 h_i_mode_high;
__le16 h_i_uid_high;
__le16 h_i_gid_high;
__le32 h_i_author;
} hurd2;
struct {
__u8 m_i_frag; /* Fragment number */
__u8 m_i_fsize; /* Fragment size */
__u16 m_pad1;
__u32 m_i_reserved2[2];
} masix2;
} osd2; /* OS dependent 2 */
};

根目录inode信息:
fs.rootinode.png

st_mode以八进制表示,包含了文件类型和文件权限,最高位的4表示文件类型为目录(各种文件类型的编码详见stat(2)),低位的755表示权限。Size是1024,说明根目录现在只有一个数据块。Links为3表示根目录有三个硬链接,分别是根目录下的.和..,以及lost+found子目录下的..。注意,虽然我们通常用/表示根目录,但是并没有名为/的硬链接,事实上,/是路径分隔符,不能在文件名中出现。这里的Blockcount是以512字节为一个块来数的,并非格式化文件系统时所指定的块大小,磁盘的最小读写单位称为扇区(Sector),通常是512字节,所以Blockcount是磁盘的物理块数量,而非分区的逻辑块数量。根目录数据块的位置由上图中的Blocks[0]指出,也就是第24个块,它在文件系统中的位置是24×0x400=0x6000

探索文件系统还有一个很有用的工具debugfs,它提供一个命令行界面,可以对文件系统做各种操作,例如查看信息、恢复数据、修正文件系统中的错误。使用debugfs ./img.1m打开文件系统,使用stat /命令查看根目录inode信息

1
2
3
4
5
6
7
8
9
10
11
12
Inode: 2   Type: directory    Mode:  0755   Flags: 0x0
Generation: 0 Version: 0x00000000
User: 0 Group: 0 Size: 1024
File ACL: 0 Directory ACL: 0
Links: 3 Blockcount: 2
Fragment: Address: 0 Number: 0 Size: 0
ctime: 0x59efef35 -- Wed Oct 25 09:56:05 2017
atime: 0x59efef63 -- Wed Oct 25 09:56:51 2017
mtime: 0x59efef35 -- Wed Oct 25 09:56:05 2017
BLOCKS:
(0):24
TOTAL: 1

数据块寻址

通过__le32 i_block[EXT2_N_BLOCKS]来完成数据块的寻址。

1
2
3
4
5
6
7
8
/*
* Constants relative to the data blocks
*/
#define EXT2_NDIR_BLOCKS 12
#define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS
#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)
#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)
#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)
  • EXT2_NDIR_BLOCKS
    i_block前12项属于直接寻址,直接存储数据块的”block id”。所以总共可记录 12 笔记录,总额大小为12*1k=12k
  • EXT2_IND_BLOCK
    i_block第13项为一级间接寻址,每笔 block 号码的记录会花去 4bytes,因此 1K 的大小能够记录 256 笔记录,因此一个间接可以记录的文件大小为(1k/4)*1K=256k
  • EXT2_DIND_BLOCK
    i_block第14项为二级间接寻址,第一层 block 会指定 256 个第二层,每个第二层可以指定 256 个号码,因此总额大小为2562561k
  • EXT2_TIND_BLOCK
    i_block第15项为三级间接寻址,第一层 block 会指定 256 个第二层,每个第二层可以指定 256 个第三层,每个第三层可以指定 256 个号码,因此总额大小为256256256*1k

*** 总容量为直接寻址+一级间接+二级间接+三级间接=16.06GB ***

fs.datablockaddr.png

Blocks

  • 对于常规文件,文件的数据存储在数据块中。
  • 对于目录,该目录下的所有文件名和目录名存储在数据块中,注意文件名保存在它所在目录的数据块中,除文件名之外,ls -l命令看到的其它信息都保存在该文件的inode中。注意这个概念:目录也是一种文件,是一种特殊类型的文件。
  • 对于符号链接,如果目标路径名较短则直接保存在inode中以便更快地查找,如果目标路径名较长则分配一个数据块来保存。
  • 设备文件、FIFO和socket等特殊文件没有数据块,设备文件的主设备号和次设备号保存在inode中。

根目录的数据结构

1
2
3
4
5
6
7
struct ext2_dir_entry_2 {
__le32 inode; /* Inode number */
__le16 rec_len; /* Directory entry length */
__u8 name_len; /* Name length */
__u8 file_type;
char name[]; /* File name, up to EXT2_NAME_LEN */
};

根目录的数据块
fs.datablock.png

根据上文中根目录inode信息找到根目录数据块006000地址,目录的数据块由许多不定长的记录组成,每条记录描述该目录下的一个文件,在上图中用框表示。第一条记录描述inode号为2的文件,也就是根目录本身,该记录的总长度为12字节,其中文件名的长度为1字节,文件类型为2(见下表,注意此处的文件类型编码和st_mode不一致),文件名是.。第二条记录也是描述inode号为2的文件(根目录),该记录总长度为12字节,其中文件名的长度为2字节,文件类型为2,文件名字符串是..。第三条记录一直延续到该数据块的末尾,描述inode号为11的文件(lost+found目录),该记录的总长度为1000字节(和前面两条记录加起来是1024字节),文件类型为2,文件名字符串是lost+found,后面全是0字节。如果要在根目录下创建新的文件,可以把第三条记录截短,在原来的0字节处创建新的记录。如果该目录下的文件名太多,一个数据块不够用,则会分配新的数据块,块编号会填充到inode的Blocks[1]字段。

编码 文件类型(file type)
0 Unknown
1 Regular File
2 Directory
3 Character device
4 Block device
5 Named pipe
6 Socket
7 Symbolic link

参考&鸣谢

Exception语法

try-except

1
2
3
4
5
6
7
8
9
10
try:
<语句>
except <异常名字>:
<语句>
except <异常名字>, <异常数据变量>:
<语句>
except (<异常名字>,<异常名字>,...)[, <异常数据变量>]:
<语句>
else:
<语句>

try-finally

1
2
3
4
5
6
try:
<语句>
finally:
# 无论是否发生异常都将执行最后的代码
# 退出try时总会执行
<语句>

raise

1
raise [Exception [, args [, traceback]]]

Exception是异常的类型(例如,NameError)参数是一个异常参数值。该参数是可选的,如果不提供,异常的参数是”None”。最后一个参数是可选的(在实践中很少使用),如果存在,是跟踪异常对象。

标准异常

异常名称 描述
BaseException 所有异常的基类
SystemExit 解释器请求退出
KeyboardInterrupt 用户中断执行(通常是输入^C)
Exception 常规错误的基类
StopIteration 迭代器没有更多的值
GeneratorExit 生成器(generator)发生异常来通知退出
StandardError 所有的内建标准异常的基类
ArithmeticError 所有数值计算错误的基类
FloatingPointError 浮点计算错误
OverflowError 数值运算超出最大限制
ZeroDivisionError 除(或取模)零 (所有数据类型)
AssertionError 断言语句失败
AttributeError 对象没有这个属性
EOFError 没有内建输入,到达EOF 标记
EnvironmentError 操作系统错误的基类
IOError 输入/输出操作失败
OSError 操作系统错误
WindowsError 系统调用失败
ImportError 导入模块/对象失败
LookupError 无效数据查询的基类
IndexError 序列中没有此索引(index)
KeyError 映射中没有这个键
MemoryError 内存溢出错误(对于Python 解释器不是致命的)
NameError 未声明/初始化对象 (没有属性)
UnboundLocalError 访问未初始化的本地变量
ReferenceError 弱引用(Weak reference)试图访问已经垃圾回收了的对象
RuntimeError 一般的运行时错误
NotImplementedError 尚未实现的方法
SyntaxError Python 语法错误
IndentationError 缩进错误
TabError Tab 和空格混用
SystemError 一般的解释器系统错误
TypeError 对类型无效的操作
ValueError 传入无效的参数
UnicodeError Unicode 相关的错误
UnicodeDecodeError Unicode 解码时的错误
UnicodeEncodeError Unicode 编码时错误
UnicodeTranslateError Unicode 转换时错误
Warning 警告的基类
DeprecationWarning 关于被弃用的特征的警告
FutureWarning 关于构造将来语义会有改变的警告
OverflowWarning 旧的关于自动提升为长整型(long)的警告
PendingDeprecationWarning 关于特性将会被废弃的警告
RuntimeWarning 可疑的运行时行为(runtime behavior)的警告
SyntaxWarning 可疑的语法的警告
UserWarning 用户代码生成的警告

参考&鸣谢

安装

python不能直接访问hbase,必须通过thrift插件才能访问hbase,下面是对hbasethriftpython插件的安装

1
2
pip install thrift
pip install hbase-thrift

访问hbase

hbase_client.py

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
#!/usr/bin/python
#encoding=utf-8

from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol

from hbase import Hbase
from hbase.ttypes import *

class HbaseClient(object):
def __init__(self, host='', port=9090):
self.sock = TSocket.TSocket(host, port)
self.transport = TTransport.TBufferedTransport(self.sock)
self.proto = TBinaryProtocol.TBinaryProtocol(self.transport)
self.client = Hbase.Client(self.proto)

def get_tables(self):
self.transport.open()
tables = self.client.getTableNames()
self.transport.close()
return tables

def get_column_desp(self, table_name):
self.transport.open()
column_desp = self.client.getColumnDescriptors(table_name)
self.transport.close()
return column_desp

def get_hbase_client(self):
return self.client

def open(self):
self.transport.open()
def close(self):
self.transport.close()

test.py

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
import hbase_client as hc

con = hc.HbaseClient(host='x.x.x.x')
client = con.get_hbase_client()

con.open()
tbls = client.getTableNames()
print tbls
mytbls = []
for tbl in tbls:
if not (tbl == 'YJDD:myApp' or tbl == 'YJDD:myAppDetail'):
continue
print "ColumnDescriptors:"
tbl_desp = client.getColumnDescriptors(tbl)
print tbl_desp
print "TableRegions:"
tbl_reg = client.getTableRegions(tbl)
print tbl_reg
mytbls.append(tbl)

print "============== scannerGet ============"
for tbl in mytbls:
sid = client.scannerOpen(tbl, '', ['user','app'])
row_result = client.scannerGet(sid)
print row_result
while row_result:
row_result = client.scannerGet(sid)
if row_result:
print row_result
print "--------------------------"

client.scannerClose(sid)

print "=============== scannerGetList ============"
for tbl in mytbls:
sid = client.scannerOpen(tbl, '', ['user','app'])
row_result = client.scannerGetList(sid, 20)
print row_result
client.scannerClose(sid)
print "--------------------------"
con.close()

参考&鸣谢

背景

计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理。最早的计算机在设计时采用8个比特(bit)作为一个字节(byte),所以,一个字节能表示的最大的整数就是255(二进制11111111=十进制255),如果要表示更大的整数,就必须用更多的字节。比如两个字节可以表示的最大整数是65535,4个字节可以表示的最大整数是4294967295。

ASCII

规定了128个字符的编码(准确地说ASCII码是一个编码字符集),比如空格“SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。后128个称为扩展ASCII码,目前许多基于x86的系统都支持使用扩展ASCII码。256个ASCII码中的后128个扩展码可定制用来表示特殊字符和非英语字符,GB2312就是利用这后面的128个扩展字符来表示汉字,[161,254]共94个字符来组成双字节来表示简体汉字字符表。

Unicode

光是英语字符ASCII编码字符集是够了,但是如果算上世界上其他的语言的字符,ASCII码显然不够了,于是Unicode编码字符集应运而生。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。UTF-8、UTF-16、UTF-32都是将所有Unicode用到的数字转换为程序数据的编码方案。全世界的字符加起来也用不了所有的码位,Unicode 5.0版本中,才用了238605个码位。

UTF8

新问题的出现:如果统一成Unicode编码,乱码问题从此消失了。但是,如果你写的文本基本上全部是英文的话,用Unicode编码比ASCII编码需要多一倍的存储空间,在存储和传输上就十分不划算。因此,又出现了把Unicode编码转化为“可变长编码”的UTF-8编码。UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间。ASCII编码实际上可以被看成是UTF-8编码的一部分,所以,大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。Unicode是字符集,UTF-8、UTF-16等是编码格式,定义“字符对应的数字”如何在以二进制的方式存储。

GB2312

规定: 一个小于127的字符的意义与原来相同, 但两个大于127的字符连在一起时, 就表示一个汉字, 前面的一个字节(他称之为高字节)从0xA1用到 0xF7, 后面一个字节(低字节)从0xA1到0xFE, 这样我们就可以组合出大约7000多个简体汉字了. 在这些编码里, 我们还把数学符号,罗马希腊的 字母,日文的假名们都编进去了, 连在 ASCII 里本来就有的数字,标点,字母都统统重新编了两个字节长的编码, 这就是常说的”全角”字符, 而原来在127号以下的那些就叫”半角”字符了。

GBK

GBK 包括了 GB2312 的所有内容, 同时又增加了近20000个新的汉字(包括繁体字)和符号。

转换

unicode兼容ascii,GBK兼容GB2312,转换也就是unicode与GBK之间的事情了。字符串在Python内部的表示是unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码,即先将其他编码的字符串解码(decode)成unicode,再从unicode编码(encode)成另一种编码。

  • decode的作用是将其他编码的字符串转换成unicode编码,如str1.decode(‘gb2312’),表示将gb2312编码的字符串转换成unicode编码
  • encode的作用是将unicode编码转换成其他编码的字符串,如str2.encode(‘gb2312’),表示将unicode编码的字符串转换成gb2312编码
1
2
3
4
5
6
7
8
9
10
11
12
13
#encoding=utf8

s = '中文'
print type(s)
print s

us = s.decode('utf8')
print type(us)
print s

gs = us.encode('gb2312')
print type(gs)
print gs

参考&鸣谢

Python语法概况

grammar

关键字

and , exec , not , assert , finally , or , break , for , pass , class , from , print , countinue , global , raise , def , if , return , del , import , try , elif , in , while , else , is , with , except , lambda , yield

  • exec
    用来执行储存在字符串或文件中的Python语句。eg:
    1
    2
    >>> exec 'print "Hello World"'
    Hello World
    注意例子中exec语句的用法和eval_r(), execfile()是不一样的. exec是一个语句(就象print或while), 而eval_r()和execfile()则是内建函数。
  • lambda
    在python中使用lambda来创建匿名函数,lambda会创建一个函数对象,但不会把这个函数对象赋给一个标识符,而def则会把函数对象赋值给一个变量。lambda它只是一个表达式,而def则是一个语句。eg:
    1
    2
    m = lambda x,y,z: (x-y)*z
    print m(3,1,2)
    lambda一般只用来定义简单的函数。
  • yield
    它和return差不多的用法,只是最后是返回了一个生成器,当你调用yield所在的那个函数的时候,那个函数并没有运行,只会返回一个生成器的对象,第一次在for中调用生成器的的对象,它将会运行你函数中的代码从最开始一直到到碰到了yield的关键字,然后它会返回循环中的第一个值。然后每一次其他的调用将会运行你在这个函数中所写的循环多一次,并且返回下一个值,知道没有值可以返回了。eg:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    >>> def createGenerator():
    ... mylist = range(3)
    ... for i in mylist:
    ... yield i*i
    ...
    >>> mygenerator = createGenerator() # create a generator
    >>> print(mygenerator) # mygenerator is an object!
    <generator object createGenerator at 0xb7555c34>
    >>> for i in mygenerator:
    ... print(i)
    0
    1
    4

Python VS C

Pytho C 说明
字母、数字、下划线组成,不能以数字开头,区分大小写 字母、数字、下划线组成,不能以数字开头,区分大小写 标识符
单下划线开头的代表不能直接访问的类属性,通过类提供的接口访问
双下划线开头的代表类的私有成员
双下划线开头和结尾的代表特殊方法专用的标识(eg:init())
“" 不需要,以”;”结尾 行继符
单引号(‘),双引号(“),三引号(‘’’) 双引号(“) 字符串
# // 注释
: {} 代码块
if if 条件
elif else if
else else
a = b = 1 or a,b=1,”Hi” int a=1 , b=2 多变量赋值
Numbers, String, List, Tuple, Dictionary char, int , float, double, void, point* 基础数据类型
list[item1, item2, … itemN] 列表,允许更新
tuple(item1, item2, …, itemN) 元组,不允许更新操作
dict{key1:value1, key2:value2, …, keyN:valueN} 字典,允许更新
** 幂,a**b a的b次幂
// mod 整除
“!=” , “<>” “!=” 不等
“and” “&&” 逻辑运算
“or” 双竖线
“not” “!”
“in” , “not in” 判断元素在不在元组或列表中
“is” , “not is” 判断两个标识符是不是引用自一个对象
“while”, “for” “while”, “for”, “do..while” 循环, python中可与”else”一起执行条件为false的情况
“break”, “continue”, “pass” “break”, “continue”, “goto” 循环控制

Python编译

选项 描述
-d 在解析时显示调试信息
-O 生成优化代码 ( .pyo 文件 )
-S 启动时不引入查找Python路径的位置
-c cmd 执行 Python 脚本,并将运行结果作为 cmd 字符串
file 在给定的python文件执行python脚本

参考&鸣谢

vfs-arch

版本

引用代码版本:linux-3.10.104

功能

  • 提供一个通用接口
  • 提供各种cache以提高文件系统性能

vfs

VFS所处理的系统调用

系统调用名 说明
mount umount umount2 安装/卸载文件系统
sysfs 获取文件系统信息
ustat statfs fstatfs statfs64 fstatfs64 获取文件系统统计信息
chroot pivot_root 更改根目录
chdir fchdir getcwd 对当前目录进行操作
mkdir rmdir 创建、删除目录
getdents getdents64 readdir link unlink rename lookup_dcookie 对目录项进行操作
readlink symlink 对软连接进行操作
chown fchown lchown chown16 fchown16 lchown16 更改文件所有者
chmod fchmod utime 更改文件属性
stat fstat lstat access oldstat oldfstat oldlstat 读取文件状态
stat64 lstat64 fstat64
open close creat umask 打开、关闭、创建文件
dup dup2 fcntl fcntl64 对文件描述符进行操作
select poll 等待一组文件描述符上发生的事件
truncate ftruncate truncate64 ftruncate64 更改文件长度
lseek 更改文件指针
read write readv writev sendfile sendfile64 readahead 进行文件I/O操作
io_setup io_submit io_getevents io_cancel io_destroy 异步I/O
pread64 pwrite64 搜索并访问文件
mmap mmap2 munmap madvise mincore remap_file_pages 处理文件内存映射
fdatasync fsync sync msync 同步文件数据
flock 处理文件锁
setxattr lsetxattr fsetxattr getxattr lgetxattr 处理文件扩展属性
fgetxattr listxattr llistxattr flistxattr removexattr
lremovexattr fremovexattr

VFS数据结构

  • 超级块对象和inode对象分别对应有物理数据,在磁盘上有静态信息。
  • 目录项对象和文件对象描述的是一种关系,前者描述的文件与文件名的关系,后者描述的是进程与文件的关系,所以没有对应物理数据
  • 进程每打开一个文件,就会有一个file结构与之对应。同一个进程可以多次打开同一个文件而得到多个不同的file结构,file结构描述被打开文件的属性,如文件的当前偏移量等信息
  • 两个不同的file结构可以对应同一个dentry结构。进程多次打开同一个文件时,对应的只有一个dentry结构
  • 在存储介质中,每个文件对应唯一的inode结点,但是每个文件又可以有多个文件名。
  • Inode中不存储文件的名字,它只存储节点号;而dentry则保存有名字和与其对应的节点号,所以就可以通过不同的dentry访问同一个inode

vfs_3.10.104.png

Super Block

  • 超级块用来描述特定文件系统的信息。它存放在磁盘特定的扇区中 ,它在使用的时候将信息存在于内存中
  • 当内核对一个文件系统进行初始化和注册时在内存为其分配一个超级块,这就是VFS超级块。即,VFS超级块是各种具体文件系统在安装时建立的,并在这些文件系统卸载时被自动删除

super_block_1.jpg

关键成员:

  • s_list
    所有的超级块形成一个双联表,s_list.prev和s_list.next分别指向与当前超级块相邻的前一个元素和后一个元素
  • s_fs_info
    字段指向具体文件系统的超级块。
    例如:超级块对象指的是Ext2文件系统,该字段就指向ext2_sb_info数据结构
  • alloc_super()
    超级块对象是通过函数alloc_super()创建并初始化的(由sget()调用,在fs/super.c文件中)。在文件系统安装时,内核会调用该函数以便从磁盘读取文件系统超级块,并且将其信息填充到内存中的超级块对象中

include/linux/fs.h

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
struct super_block {
struct list_head s_list; /* 指向超级块链表的指针 */
dev_t s_dev; /* 设备标示符 */
unsigned char s_blocksize_bits; /* 以位为单位的块大小 */
unsigned long s_blocksize; /* 以字节为单位的块大小 */
loff_t s_maxbytes; /* 文件的最长长度 */
struct file_system_type *s_type; /* 文件系统类型 */
const struct super_operations *s_op; /* 超级块方法 */
const struct dquot_operations *dq_op; /* 磁盘限额处理方法*/
const struct quotactl_ops *s_qcop; /* 磁盘限额管理方法 */
const struct export_operations *s_export_op;/* 网络文件系统使用的输出操作 */
unsigned long s_flags; /* 安装标志 */
unsigned long s_magic;
struct dentry *s_root; /* 文件系统根目录和目录项对象 */
struct rw_semaphore s_umount; /* 卸载文件系统所用的信号量 */
int s_count; /* 引用计数 */
atomic_t s_active; /* 次级引用计数 */
#ifdef CONFIG_SECURITY
void *s_security; /* 指向超级块安全数据结构的指针 */
#endif
const struct xattr_handler **s_xattr; /* 指向超级块扩展属性结构的指针*/
struct list_head s_inodes; /* all inodes */
struct hlist_bl_head s_anon; /* 用于处理远程网络文件系统的匿名目录项的链表 */
struct list_head s_mounts; /* list of mounts; _not_ for fs use */
/* s_dentry_lru, s_nr_dentry_unused protected by dcache.c lru locks */
struct list_head s_dentry_lru; /* unused dentry lru */
int s_nr_dentry_unused; /* # of dentry on lru */
/* s_inode_lru_lock protects s_inode_lru and s_nr_inodes_unused */
spinlock_t s_inode_lru_lock ____cacheline_aligned_in_smp;
struct list_head s_inode_lru; /* unused inode lru */
int s_nr_inodes_unused; /* # of inodes on lru */
struct block_device *s_bdev; /* 指向块设备驱动程序描述符的指针 */
struct backing_dev_info *s_bdi;
struct mtd_info *s_mtd;
struct hlist_node s_instances; /* 用于给定文件系统类型的超级块对象链表的指针 */
struct quota_info s_dquot; /* 磁盘限额的描述符 */
struct sb_writers s_writers;
char s_id[32]; /* Informational name */
u8 s_uuid[16]; /* UUID */
void *s_fs_info; /* 指向特定文件系统的超级块信息的指针 */
unsigned int s_max_links;
fmode_t s_mode;

/* Granularity of c/m/atime in ns. Cannot be worse than a second */
u32 s_time_gran;

/* The next field is for VFS *only*. No filesystems have any business
* even looking at it. You had been warned. */
struct mutex s_vfs_rename_mutex; /* Kludge */

/* Filesystem subtype. If non-empty the filesystem type field
* in /proc/mounts will be "type.subtype" */
char *s_subtype;

/* Saved mount options for lazy filesystems using
* generic_show_options() */
char __rcu *s_options;
const struct dentry_operations *s_d_op; /* default d_op for dentries */

/* Saved pool identifier for cleancache (-1 means none) */
int cleancache_poolid;
struct shrinker s_shrink; /* per-sb shrinker handle */

/* Number of inodes with nlink == 0 but still referenced */
atomic_long_t s_remove_count;

/* Being remounted read-only */
int s_readonly_remount;
};

super_operation

include/linux/fs.h

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
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
/* 创建和初始化一个索引节点对象 */
void (*destroy_inode)(struct inode *);
/* 释放给定的索引节点 */
void (*dirty_inode) (struct inode *, int flags);
/* VFS在索引节点被修改时会调用这个函数 */
int (*write_inode) (struct inode *, struct writeback_control *wbc);
/* 将指定的inode写回磁盘 */
int (*drop_inode) (struct inode *);
/* 最后一个指向索引节点的引用被删除后,VFS会调用这个函数 */
void (*evict_inode) (struct inode *);
void (*put_super) (struct super_block *);
/* 卸载文件系统时由VFS调用,用来释放超级块 */
int (*sync_fs)(struct super_block *sb, int wait);
/* 使文件系统中的数据与磁盘上的数据同步 */
int (*freeze_fs) (struct super_block *);
int (*unfreeze_fs) (struct super_block *);
int (*statfs) (struct dentry *, struct kstatfs *);
/* VFS调用该函数获取文件系统状态 */
int (*remount_fs) (struct super_block *, int *, char *);
/* 定新的安装选项重新安装文件系统时,VFS会调用该函数 */
void (*umount_begin) (struct super_block *);
/* VFS调用该函数中断安装操作 */

int (*show_options)(struct seq_file *, struct dentry *);
/* 用来显示特定文件系统的选项 */
int (*show_devname)(struct seq_file *, struct dentry *);
int (*show_path)(struct seq_file *, struct dentry *);
int (*show_stats)(struct seq_file *, struct dentry *);
#ifdef CONFIG_QUOTA
ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
/* 限额系统使用该方法从文件中读取数据,该文件详细说明了所在文件系统的限制 */
ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
/* 限额系统使用该方法将数据写入文件中,该文件详细说明了所在文件系统的限制 */
#endif
int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
int (*nr_cached_objects)(struct super_block *);
void (*free_cached_objects)(struct super_block *, int);
};

Inode

inode_1.jpg

索引节点,索引节点与文件一一对应,并且随文件存在而存在。内存中索引节点由一个inode结构表示。

include/linux/fs.h

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
struct inode {
umode_t i_mode; /* 文件类型与访问权限 */
unsigned short i_opflags;
kuid_t i_uid; /* inode所属文件的拥有者的id,通过ls -n可查看拥有者id */
kgid_t i_gid; /* inode所属文件所在组的id,通过ls -n可查看组id */
unsigned int i_flags; /* 文件系统的安装标志 */

#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif

const struct inode_operations *i_op; /* 索引节点的操作 */
struct super_block *i_sb; /* 指向超级块对象的指针 */
struct address_space *i_mapping; /* address space */

#ifdef CONFIG_SECURITY
void *i_security; /* 指向索引节点安全结构的指针 */
#endif

/* Stat data, not accessed from path walking */
unsigned long i_ino; /* 索引节点号 */
/* Filesystems may only read i_nlink directly. They shall use the
* following functions for modification:
* (set|clear|inc|drop)_nlink
* inode_(inc|dec)_link_count */
union {
const unsigned int i_nlink; /* 硬链接数目,当该inode描述一个目录时,这个值至少为2,代表.和..的数目 */
unsigned int __i_nlink;
};
dev_t i_rdev; /* 如果该inode描述的是一个设备文件,此值为设备号 */
loff_t i_size; /* 文件的字节数 */
struct timespec i_atime; /* 上次访问文件的时间 */
struct timespec i_mtime; /* 上次写文件的时间,这里的修改只文件内容被修改 */
struct timespec i_ctime; /* 上次修改索引节点的时间,这里的修改除了指文件内容被修改外,更强调的是文件的属性被修改 */
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
unsigned short i_bytes; /* 文件中最后一个块的字节数 */
unsigned int i_blkbits; /* 块的位数 */
blkcnt_t i_blocks; /* 文件使用块的个数,通过ls -s可以查看该某个文件的块使用数目 */

#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount; /* SMP系统为i_size字段获取一致值时使用的顺序计数器 */
#endif

/* Misc */
unsigned long i_state; /* 索引节点的状态标志 */
struct mutex i_mutex;

unsigned long dirtied_when; /* jiffies of first dirtying */

struct hlist_node i_hash; /* 用于散列链表的指针 */
/* 为了提高查找inode的效率,每一个inode都会有一个hash值。该字段指向hash值相同的inode所形成的双链表该字段包含prev和next两个指针,分别指向上述链表的前一个元素和后一个元素 */
struct list_head i_wb_list; /* backing dev IO list */
struct list_head i_lru; /* inode LRU list 所有使用的索引结点形成的双联表 */
struct list_head i_sb_list; /* 用于超级块的索引节点链表的指针 */
union {
struct hlist_head i_dentry; /* 所有引用索引节点的目录项对象链表的头 */
struct rcu_head i_rcu;
};
u64 i_version; /* 版本号(每次使用后自动递增) */
atomic_t i_count; /* 引用计数器 */
atomic_t i_dio_count;
atomic_t i_writecount; /* 用于写进程的引用计数器 */
const struct file_operations *i_fop; /* 缺省文件操作 former ->i_op->default_file_ops */
struct file_lock *i_flock; /* 指向文件锁链表的指针 */
struct address_space i_data; /* address space */
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS]; /* 索引节点磁盘限额 */
#endif
struct list_head i_devices; /* 用于具体的字符或块设备索引节点链表的指针 */
union {
struct pipe_inode_info *i_pipe; /* 如果文件是一个管道则使用它 */
struct block_device *i_bdev; /* 指向块设备驱动程序的指针 */
struct cdev *i_cdev; /* 指向字符设备驱动程序的指针 */
};

__u32 i_generation; /* 索引节点版本号(由某些文件系统使用) */

#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_head i_fsnotify_marks;
#endif

#ifdef CONFIG_IMA
atomic_t i_readcount; /* struct files open RO */
#endif
void *i_private; /* fs or device private pointer */
};

i_state 成员

  • I_DIRTY_SYNC
    已被改变但不需要同步
  • I_DIRTY_DATASYNC
    数据部分已被改变
  • I_DIRTY_PAGES
    有脏的数据页
  • I_NEW
    索引节点对象已经分配,但还没有用从磁盘索引节点读取来的数据填充
  • I_WILL_FREE
    即将被释放
  • I_FREEING
    索引节点对象正在被释放
  • I_CLEAR
    索引节点对象的内容不再有意义
  • I_SYNC
    正在同步过程中
  • I_REFERENCED
    在LRU list中标记inode为最近被引用过
  • I_DIO_WAKEUP
    Never Set
  • I_DIRTY
    I_DIRTY_SYNC | I_DIRTY_DATASYNC | I_DIRTY_PAGES

inode_operations

include/linux/fs.h

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
struct inode_operations {
struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
void * (*follow_link) (struct dentry *, struct nameidata *);
int (*permission) (struct inode *, int);
struct posix_acl * (*get_acl)(struct inode *, int);

int (*readlink) (struct dentry *, char __user *,int);
void (*put_link) (struct dentry *, struct nameidata *, void *);

int (*create) (struct inode *,struct dentry *, umode_t, bool);
/* 如果该inode描述一个目录文件,那么当在该目录下创建或打开一个文件时,内核必须为这个文件创建一个inode。VFS通过调用该inode的i_op->create()函数来完成上述新inode的创建。
该函数的第一个参数为该目录的 inode,第二个参数为要打开新文件的dentry,第三个参数是对该文件的访问权限。如果该inode描述的是一个普通文件,那么该inode永远都不会调用这个create函数 */

int (*link) (struct dentry *,struct inode *,struct dentry *);
/* 用于在指定目录下创建一个硬链接。这个link函数最终会被系统调用link()调用。
该函数的第一个参数是原始文件的dentry,第二个参数即为上述指定目录的inode,第三个参数是链接文件的dentry。 */

int (*unlink) (struct inode *,struct dentry *);
/* 在某个目录下删除dentry指定的索引节点对象。这个unlink函数最终会被系统调用unlink()调用。 第一个参数即为上述硬链接所在目录的inode,第二个参数为要删除文件的dentry */

int (*symlink) (struct inode *,struct dentry *,const char *);
/* 在某个目录下新建软连接(符号链接),第一个参数是原始文件所在目录的inode,第二个参数是原始文件的dentry,第三个参数是符号链接的名字(const char *) */

int (*mkdir) (struct inode *,struct dentry *,umode_t);
/* 在指定的目录下创建一个子目录,当前目录的inode会调用i_op->mkdir()。该函数会被系统调用mkdir()调用。
第一个参数即为指定目录的inode,第二个参数为子目录的dentry,第三个参数为子目录权限;
(目录与子目录是通过目录inode中的dentry链相连的,而子目录的dentry又指向子目录自身的inode) */

int (*rmdir) (struct inode *,struct dentry *);
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
/* 在指定的目录下创建一个特殊文件,比如管道、设备文件或套接字等 */

int (*rename) (struct inode *, struct dentry *, struct inode *, struct dentry *);
int (*setattr) (struct dentry *, struct iattr *);
int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);
ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
ssize_t (*listxattr) (struct dentry *, char *, size_t);
int (*removexattr) (struct dentry *, const char *);
int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
u64 len);
int (*update_time)(struct inode *, struct timespec *, int);
int (*atomic_open)(struct inode *, struct dentry *,
struct file *, unsigned open_flag,
umode_t create_mode, int *opened);
} ____cacheline_aligned;

File

  • 文件对象描述进程怎样与一个打开文件进行交互。
  • 几个进程可以同时访问一个文件,因此文件指针必须放在文件对象而不是索引节点对象中

include/linux/fs.h

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
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path; /* 文件对象通过f_path.dentry指针指向相关的目录项对象 */
/* 目录项会指向相关的索引节点,索引节点会记录文件是否是脏的 */
#define f_dentry f_path.dentry
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;

/*
* Protects f_ep_links, f_flags, f_pos vs i_size in lseek SEEK_CUR.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
atomic_long_t f_count; /* 记录使用文件对象的进程数 */
unsigned int f_flags;
fmode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;

u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;

#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
unsigned long f_mnt_write_state;
#endif
};

file_operations

include/linux/fs.h

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
struct file_operations {
struct module *owner; /* 用于指定拥有这个文件操作结构体的模块,通常取THIS_MODULE */
loff_t (*llseek) (struct file *, loff_t, int);
/* 用于设置文件的偏移量。第一个参数指明要操作的文件,第二个参数为偏移量,第三个参数为开始偏移的位置(可取SEEK_SET,SEEK_CUR和SEEK_END之一)*/

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
/* 从文件中读数据。第一个参数为源文件,第二个参数为目的字符串,第三个参数指明欲读数据的总字节数,第四个参数指明从源文件的某个偏移量处开始读数据。由系统调用read()调用 */

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
/* 往文件里写数据。第一个参数为目的文件,第二个参数源字符串,第三个参数指明欲写数据的总字节数,第四个参数指明从目的文件的某个偏移量出开始写数据。由系统调用write()调用 */

ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
/* 启动一个异步I/O操作,引入它是为了支持io_submit()系统调用 */

int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
/* 检查是否在一个文件上有操作发生,如果没有则睡眠,知道该文件上有操作发生 */

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
/* 将指定文件映射到指定的地址空间上。由系统调用mmap()调用 */

int (*open) (struct inode *, struct file *);
/* 打开指定文件,并且将这个文件和指定的索引结点关联起来。由系统调用open()调用 */

int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
/* 释放以打开的文件,当打开文件的引用计数(f_count)为0时,该函数被调用 */

int (*fsync) (struct file *, loff_t, loff_t, int datasync);
/* 文件在缓冲的数据写回磁盘 */

int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);
int (*show_fdinfo)(struct seq_file *m, struct file *f);
};

Dentry

VFS把每一个目录看作由若干个目录和文件组成的一个普通文件。

*** 本来inode中应该包括“目录节点”的名称,但由于硬链接的存在,导致一个物理文件可能有多个文件名,因此把和“目录节点”名称相关的部分从 inode 中分开,放在一个专门的 dentry 结构(目录项)中 ***
描述一个文件和一个名字的对应关系,或者说dentry就是一个“文件名”

dentry_1.jpg

  • 在内存中, 每个文件都至少有一个dentry(目录项)和inode(索引节点)结构
  • dentry记录着文件名,上级目录等信息,正是它形成了我们所看到的树状结构
  • 有关该文件的组织和管理的信息主要存放inode里面,它记录着文件在存储介质上的位置与分布

dentry_2.png

include/linux/fs.h

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
struct dentry {
/* RCU lookup touched fields */
unsigned int d_flags; /* protected by d_lock */
seqcount_t d_seq; /* per dentry seqlock */
struct hlist_bl_node d_hash; /* lookup hash list */
struct dentry *d_parent; /* parent directory */
struct qstr d_name;
struct inode *d_inode; /* Where the name belongs to - NULL is
* negative */
unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */

/* Ref lookup also touches following */
unsigned int d_count; /* protected by d_lock */

spinlock_t d_lock; /* per dentry lock */
const struct dentry_operations *d_op;
struct super_block *d_sb; /* The root of the dentry tree */
unsigned long d_time; /* used by d_revalidate */
void *d_fsdata; /* fs-specific data */

struct list_head d_lru; /* LRU list */
struct list_head d_child; /* child of parent list */
/* 如果当前目录项是一个目录,那么该目录项通过这个字段加入到父目录的d_subdirs链表当中。这个字段中的next和prev指针分别指向父目录中的另外两个子目录 */
struct list_head d_subdirs; /* our children */
/* 如果当前目录项是一个目录,那么该目录下所有的子目录(一级子目录)形成一个链表。该字段是这个链表的表头 */
/*
* d_alias and d_rcu can share memory
*/
union {
struct hlist_node d_alias; /* inode alias list */
/* 索引节点中的i_dentry指向了它目录项,目录项中的d_alias,d_inode又指会了索引节点对象
一个inode可能对应多个目录项,所有的目录项形成一个链表。inode结构中的i_dentry即为这个链表的头结点。当前目录项以这个字段处于i_dentry链表中。该字段中的prev和next指针分别指向与该目录项同inode的其他两个(如果有的话)目录项 */
struct rcu_head d_rcu;
} d_u;
};

dentry的四种状态

  • 空闲状态(Free)
    该状态的目录对象不包括有效的信息,没有被vfs使用。
  • 未使用状态(unused)
    该状态的目录对象还没有被内核使用。引用计数器d_count为0,d_inode字段仍然指向关联的索引节点,目录对象包含有效的信息。为了在必要时回收内存,它的内容可能被丢弃。
  • 正在使用状态(in use)
    该状态的目录对象正在被内核使用。引用计数器d_count大于0,d_inode字段指向关联的索引节点,目录对象包含有效的信息,不能被丢弃。
  • 负状态(negative)
    与目录对象关联的索引节点不存在,相应的磁盘索引节点已被删除。d_inode被置为NULL,该对象仍被保存在目录项高速缓存中,以便同一文件目录名的查找能够快速完成。

dentry_oprations

include/linux/dcache.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct dentry_operations {
int (*d_revalidate)(struct dentry *, unsigned int);
/* 该函数判断目录对象是否有效。VFS准备从dcache中使用一个目录项时,会调用该函数 */

int (*d_weak_revalidate)(struct dentry *, unsigned int);
int (*d_hash)(const struct dentry *, const struct inode *, struct qstr *);
/* 该目录生成散列值,当目录项要加入到散列表时,VFS要调用此函数 */

int (*d_compare)(const struct dentry *, const struct inode *, const struct dentry *, const struct inode *, unsigned int, const char *, const struct qstr *);
int (*d_delete)(const struct dentry *);
/* 当d_count=0时,VFS调用次函数。使用该函数要加dcache_lock锁 */

void (*d_release)(struct dentry *);
/* 当该目录对象将要被释放时,VFS调用该函数 */

void (*d_prune)(struct dentry *);

void (*d_iput)(struct dentry *, struct inode *);
/* 当一个目录项丢失了其索引节点时,VFS就掉用该函数 */

char *(*d_dname)(struct dentry *, char *, int);
struct vfsmount *(*d_automount)(struct path *);
int (*d_manage)(struct dentry *, bool);
} ____cacheline_aligned;

Dentry定位文件

首先,通过dir对应的dentry0找到inode0节点,有了inode节点就可以读取目录中的信息。其中包含了该目录包含的下一级目录与文件文件列表,包括name与inode号。

1
2
$  ls -i
975248 subdir0 975247 subdir1 975251 file0

然后,根据通过根据subdir0对应的inode号重建inode2,并通过文件数据(目录也是文件)与inode2重建subdir0的dentry节点:dentry1。

1
2
$  ls -i
975311 file1 975312 file2

接着,根据file1对应的inode号重建inode4,并通过文件数据与inode4重建file1的dentry节点。最后,就可以通过inode4节点访问文件了。

dentry_3.png

Dentry Cache

由于从硬盘读入一个目录项并构造相应的目录项对象需要花费大量的时间,也为了最大限度提高目处理这些录项对象的效率,VFS采用了dentry cache的设计。当有用户用ls命令查看某一个目录或用open命令打开一个文件时,VFS会为这里用的每个目录项与文件建立dentry项与inode,即“按需创建”。然后维护一个LRU(Least Recently Used)列表,当Linux认为VFS占用太多资源时,VFS会释放掉长时间没有被使用的dentry项与inode项。

这里的建立于释放是从内存占用的角度看。从Linux角度看,dentry与inode是VFS中固有的东西。所不同的只是VFS是否把dentry与inode读到了内存中。对于Ext2/3文件系统,构建dentry与inode的过程非常简单,但对于其他文件系统,则会慢得多。

Process & File

file_1.png

task_struct (include/linux/sched.h)

1
2
3
4
5
6
7
8
9
10
11
12
struct task_struct {
...
/* CPU-specific state of this task */
struct thread_struct thread;
/* filesystem information */
struct fs_struct *fs;
/* open file information */
struct files_struct *files;
/* namespaces */
struct nsproxy *nsproxy;
...
};

进程控制块task_struct(include/linux/sched.h)中有两个变量与文件有关:fs(struct fs_struct)与files(struct files_struct)。

fs_struct (include/linux/fs_struct.h)

1
2
3
4
5
6
7
8
struct fs_struct {
int users;
spinlock_t lock;
seqcount_t seq;
int umask;
int in_exec;
struct path root, pwd;
};

path (include/liinux/path.h)

1
2
3
4
struct path {
struct vfsmount *mnt;
struct dentry *dentry;
};

fs中存储着rootpwd两个指向dentry项的指针。用户定路径时,绝对路径会通过root进行定位;相对路径会通过pwd进行定位。进程的root不一定是文件系统的根目录。如ftp进程的根目录不是文件系统的根目录,这样才能保证用户只能访问ftp目录下的内容。
files是一个file object列表,其中每一个节点对应着一个被打开了的文件。

files_struct (include/linux/fdtable.h)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct files_struct {
/*
* read mostly part
*/
atomic_t count;
struct fdtable __rcu *fdt;
struct fdtable fdtab;
/*
* written part on a separate cache line in SMP
*/
spinlock_t file_lock ____cacheline_aligned_in_smp;
int next_fd;
unsigned long close_on_exec_init[1];
unsigned long open_fds_init[1];
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};

当进程定位到文件时,会构造一个file object,并通过f_inode关联到inode节点。文件关闭时(close),进程会释放对应对应file object。

fdtable (include/linux/fdtable.h)

1
2
3
4
5
6
7
struct fdtable {
unsigned int max_fds;
struct file __rcu **fd; /* current fd array */
unsigned long *close_on_exec;
unsigned long *open_fds;
struct rcu_head rcu;
};

file_2.jpg

fd数组第一个元素[0]是进程的标准输入文件;第二个元素[1]是进程的标准输出文件;第三个元素[2]是进程的标准错误文件。

文件系统类型

特殊文件系统

文件系统 安装点 说明
bdev 块设备
binfmt_misc 任意 其他可执行格式
devpts /dev/pts 伪终端支持
eventpollfs 由事件轮询机制使用
futexfs 由futex(快速用户空间加锁)机制使用
pipefs 管道
proc /proc 对内核数据结构的常规访问点
rootfs 为启动阶段提供一个空的根目录
shm IPC共享线性区
mqueue 任意 实现POSIX消息队列时使用
sockfs 套接字
sysfs /sys 对系统数据的常规访问点
tmpfs 任意 临时文件(若不被交换出去,则常驻内存中)
usbfs /proc/bus/usb USB设备

内核给每个安装的特殊文件系统分配一个虚拟的块设备,让其主设备号为0,次设备号有任意值(每个特殊的文件系统有不同的值)

文件系统类型注册

文件系统的源码要么包含在内核映像中,要么作为一个模块被动态装入。每个注册的文件系统都用一个类型为file_system_type的对象来表示。
file_system_type (include/linux/fs.h)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct file_system_type {
const char *name; /* 文件系统名称 */
int fs_flags; /* 文件系统类型标志 */
#define FS_REQUIRES_DEV 1 /* 文件系统必须位于物理磁盘设备上 */
#define FS_BINARY_MOUNTDATA 2 /* 文件系统使用的二进制安装数据 */
#define FS_HAS_SUBTYPE 4
#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
#define FS_USERNS_DEV_MOUNT 16 /* A userns mount does not imply MNT_NODEV */
#define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */
struct dentry *(*mount) (struct file_system_type *, int, const char *, void *);
void (*kill_sb) (struct super_block *); /* 删除超级块的方法 */
struct module *owner; /* 指向实现文件系统的模块指针 */
struct file_system_type * next; /* 指向文件系统类型链表下一个元素的指针 */
struct hlist_head fs_supers; /* 具有相同文件系统类型的超级块对象链表的头*/

struct lock_class_key s_lock_key;
struct lock_class_key s_umount_key;
struct lock_class_key s_vfs_rename_key;
struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];

struct lock_class_key i_lock_key;
struct lock_class_key i_mutex_key;
struct lock_class_key i_mutex_dir_key;
};

所有文件系统类型的对象都插入道一个单向链表中,由变量file_systems指向链表的第一个元素,file_systems->next指向链表的下一个元素。

int register_filesystem(struct file_system_type * fs)[fs/filesystems.c]
注册一个新的文件系统,传入要注册文件系统的结构体,在file_systems列表中查找受否与传入fs->name同名的文件系统,如果存在则返回找到的fs,若不存在将传入文件系统fs加入file_systems列表

对比一下ramfs_fs_typeroot_fs_type

1
2
3
4
5
6
7
8
9
10
11
static struct file_system_type ramfs_fs_type = {
.name = "ramfs",
.mount = ramfs_mount,
.kill_sb = ramfs_kill_sb,
.fs_flags = FS_USERNS_MOUNT,
};
static struct file_system_type rootfs_fs_type = {
.name = "rootfs",
.mount = rootfs_mount,
.kill_sb = kill_litter_super,
};

发现fs_flags = FS_USERNS_MOUNT,这个flag似乎想告诉我们,ramfs是挂载到用户命名空间的,言外之意rootfs不是挂载到用户空间的,那便是内核空间喽。

文件系统处理

  • 每个文件系统都有自己的根目录
  • 已安装文件系统的根目录隐藏了父文件系统的安装点目录原来的内容

命名空间

通常进程共享一个命名空间,位于系统的根文件系统且被init进程使用的已安装文件系统树。如果clone()系统调用以CLONE_NEWNS标志创建一个新进程,那么新进程将获取一个新的命名空间,新进程再创建的新新进程将继承新命名空间。

  • fork
    fork创造的子进程是父进程的完整副本,复制了父亲进程的资源,包括内存的内容task_struct内容
  • vfork
    vfork创建的子进程与父进程共享数据段,而且由vfork()创建的子进程将先于父进程运行
  • clone
    有选择性的继承父进程的资源,可以选择像vfork一样和父进程共享一个虚存空间,可以不和父进程共享,甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系。灵活性更强。

KeyPoint

启动流程

  1. start_kernel[init/main.c]
  2. vfs_caches_init(totalram_pages)[fs/dcache.c]
  3. dcache_init()[fs/dcache.c]
    初始化dentry_cachedentry_hashtable
  4. inode_init()[fs/inode.c]
    初始化inode_cachepinode_hashtable
  5. files_init()[fs/file_table.c]
    初始化file_cachepfiles_stat.max_files
  6. mnt_init()[fs/namespace.c]
    初始化mnt_cache,mount_hashtable,mountpoint_hashtable
    1. sysfs_init()[fs/sysfs/mount.c]
      初始化sysfs_dir_cachep
      sysfs_inode_init()[s/sysfs/inode.c] -> bdi_init(&sysfs_backing_dev_info)
      register_filesystem(&sysfs_fs_type)
      sysfs_mnt = kern_mount(&sysfs_fs_type)
    2. init_root()[fs/ramfs/inode.c]
      bdi_init(&ramfs_backing_dev_info)
      register_filesystem(&rootfs_fs_type)
    3. init_mount_tree()[fs/namespace.c]
      挂载rootfs到/ mnt = vfs_kern_mount(type, 0, "rootfs", NULL)
      创建namespace ns = create_mnt_ns(mnt)
      设置init_task(PID=0)命名空间 init_task.nsproxy->mnt_ns = ns
      设置当前进程的pwdroot set_fs_pwd(current->fs, &root); set_fs_root(current->fs, &root);
  7. bdev_cache_init()[fs/block_dev.c]
    初始化bdev_cachep
    1. mount bdev
  8. chrdev_init()[fs/char_dev.c]
    初始化cdev_map

Mount文件系统

mount_1.png

mount时,linux先找到磁盘分区的super block,然后通过解析磁盘的inode table与file data,构建出自己的dentry列表与inode列表。需要注意的是,VFS实际上是按照Ext的方式进行构建的,所以两者非常相似(毕竟Ext是Linux的原生文件系统)。比如inode节点,Ext与VFS中都把文件管理结构称为inode,但实际上它们是不一样的。Ext的inode节点在磁盘上;VFS的inode节点在内存里。Ext-inode中的一些成员变量其实是没有用的,如引用计数等。保留它们的目的是为了与vfs-node保持一致。这样在用ext-inode节点构造vfs-inode节点时,就不需要一个一个赋值,只需一次内存拷贝即可。

参考&鸣谢