0%

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节点时,就不需要一个一个赋值,只需一次内存拷贝即可。

参考&鸣谢

ssl_tls

  • SSL
    Secure Socket Layer,安全套接字层,为Netscape所研发,用以保障在Internet上数据传输之安全,利用数据加密(Encryption)技术,可确保数据在网络上之传输过程中不会被截取。当前版本为3.0。它已被广泛地用于Web浏览器与服务器之间的身份认证和加密数据传输。
    SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。SSL协议可分为两层: SSL记录协议(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。 SSL握手协议(SSL Handshake Protocol):它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等
  • TLS
    Transport Layer Security,传输层安全协议,用于两个应用程序之间提供保密性和数据完整性。
    TLS 1.0是IETF(Internet Engineering Task Force,Internet工程任务组)制定的一种新的协议,它建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本,可以理解为SSL 3.1,它是写入了 RFC 的。该协议由两层组成: TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)。较低的层为 TLS 记录协议,位于某个可靠的传输协议(例如 TCP)上面。

TLS握手

TLS协议分为两部分:Handshake Protocol和Record Protocol。其中Handshake Protocol用来协商密钥,协议的大部分内容就是通信双方如何利用它来安全的协商出一份密钥。 Record Protocol则定义了传输的格式。

由于非对称加密的速度比较慢,所以它一般用于密钥交换,双方通过公钥算法协商出一份密钥,然后通过对称加密来通信,当然,为了保证数据的完整性,在加密前要先经过HMAC的处理。

handshake

TLS缺省只进行server端的认证,客户端的认证是可选的([ ]中是可选项)。只进行server端的认证叫单向认证,server端客户端同时认证的叫双向认证。

单向认证流程

single-handshake

  1. 客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息。
  2. 服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书
  3. 客户端使用服务端返回的信息验证服务器的合法性,包括:
    证书是否过期
    发型服务器证书的CA是否可靠
    返回的公钥是否能正确解开返回证书中的数字签名
    服务器证书上的域名是否和服务器的实际域名相匹配
    验证通过后,将继续进行通信,否则,终止通信
  4. 客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择
  5. 服务器端在客户端提供的加密方案中选择加密程度最高的加密方式。
  6. 服务器将选择好的加密方案通过明文方式返回给客户端
  7. 客户端接收到服务端返回的加密方式后,使用该加密方式生成产生随机码,用作通信过程中对称加密的密钥,使用服务端返回的公钥进行加密,将加密后的随机码发送至服务器
  8. 服务器收到客户端返回的加密信息后,使用自己的私钥进行解密,获取对称加密密钥。
  9. 在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全。

双向认证流程

double-handshake

  1. 客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息。
  2. 服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书
  3. 客户端使用服务端返回的信息验证服务器的合法性,包括:
    证书是否过期
    发型服务器证书的CA是否可靠
    返回的公钥是否能正确解开返回证书中的数字签名
    服务器证书上的域名是否和服务器的实际域名相匹配
    验证通过后,将继续进行通信,否则,终止通信
  4. 服务端要求客户端发送客户端的证书,客户端会将自己的证书发送至服务端
  5. 验证客户端的证书,通过验证后,会获得客户端的公钥
  6. 客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择
  7. 服务器端在客户端提供的加密方案中选择加密程度最高的加密方式
  8. 将加密方案通过使用之前获取到的公钥进行加密,返回给客户端
  9. 客户端收到服务端返回的加密方案密文后,使用自己的私钥进行解密,获取具体加密方式,而后,产生该加密方式的随机码,用作加密过程中的密钥,使用之前从服务端证书中获取到的公钥进行加密后,发送给服务端
  10. 服务端收到客户端发送的消息后,使用自己的私钥进行解密,获取对称加密的密钥,在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全。

参考&鸣谢

我对这些问题的理解,一直是模模糊糊的,很多细节搞不清楚。网上看到一篇文章《数字签名是什么?》,发现思路一下子就理清了。为了加深记忆,我把文字和图片都复制过来。

keys_1

keys_2

keys_3

keys_4

鲍勃收信后,用私钥解密,就看到了信件内容。这里要强调的是,只要鲍勃的私钥不泄露,这封信就是安全的,即使落在别人手里,也无法解密。

sign_1

鲍勃给苏珊回信,决定采用”数字签名”。他写完后先用Hash函数,生成信件的摘要(digest)。

sign_2

sign_3

sign_4

sign_5

苏珊再对信件本身使用Hash函数,将得到的结果,与上一步得到的摘要进行对比。如果两者一致,就证明这封信未被修改过。

cert_1

复杂的情况出现了。道格想欺骗苏珊,他偷偷使用了苏珊的电脑,用自己的公钥换走了鲍勃的公钥。此时,苏珊实际拥有的是道格的公钥,但是还以为这是鲍勃的公钥。因此,道格就可以冒充鲍勃,用自己的私钥做成”数字签名”,写信给苏珊,让苏珊用假的鲍勃公钥进行解密。

cert_2

后来,苏珊感觉不对劲,发现自己无法确定公钥是否真的属于鲍勃。她想到了一个办法,要求鲍勃去找”证书中心”(certificate authority,简称CA),为公钥做认证。证书中心用自己的私钥,对鲍勃的公钥和一些相关信息一起加密,生成”数字证书”(Digital Certificate)。

cert_3

鲍勃拿到数字证书以后,就可以放心了。以后再给苏珊写信,只要在签名的同时,再附上数字证书就行了。

cert_4

苏珊收信后,用CA的公钥解开数字证书,就可以拿到鲍勃真实的公钥了,然后就能证明”数字签名”是否真的是鲍勃签的。

参考&鸣谢

tcpdump

TcpDump可以将网络中传送的数据包完全截获下来提供分析。它支持针对网络层、协议、主机、网络或端口的过滤,并提供and、or、not等逻辑语句来帮助你去掉无用的信息。

用法

tcpdump <参数s> <包过滤表达式>

  • 参数
    详细请见man tcpdump
  • 包过滤表达式
    详细请见man pcap-filter

常用参数

  • -A 以ASCII格式打印出所有分组,并将链路层的头最小化。
  • -c 在收到指定的数量的分组后,tcpdump就会停止。
  • -C 在将一个原始分组写入文件之前,检查文件当前的大小是否超过了参数file_size 中指定的大小。如果超过了指定大小,则关闭当前文件,然后在打开一个新的文件。参数 file_size 的单位是兆字节(是1,000,000字节,而不是1,048,576字节)。
  • -d 将匹配信息包的代码以人们能够理解的汇编格式给出。
  • -dd 将匹配信息包的代码以C语言程序段的格式给出。
  • -ddd 将匹配信息包的代码以十进制的形式给出。
  • -D 打印出系统中所有可以用tcpdump截包的网络接口。
  • -e 在输出行打印出数据链路层的头部信息。
  • -E 用spi@ipaddr algo:secret解密那些以addr作为地址,并且包含了安全参数索引值spi的IPsec ESP分组。
  • -f 将外部的Internet地址以数字的形式打印出来。
  • -F 从指定的文件中读取表达式,忽略命令行中给出的表达式。
  • -i 指定监听的网络接口。
  • -l 使标准输出变为缓冲行形式,可以把数据导出到文件。
  • -L 列出网络接口的已知数据链路。
  • -m 从文件module中导入SMI MIB模块定义。该参数可以被使用多次,以导入多个MIB模块。
  • -M 如果tcp报文中存在TCP-MD5选项,则需要用secret作为共享的验证码用于验证TCP-MD5选选项摘要(详情可参考RFC 2385)。
  • -b 在数据-链路层上选择协议,包括ip、arp、rarp、ipx都是这一层的。
  • -n 不把网络地址转换成名字。
  • -nn 不进行端口名称的转换。
  • -N 不输出主机名中的域名部分。例如,‘nic.ddn.mil‘只输出’nic‘。
  • -t 在输出的每一行不打印时间戳。
  • -O 不运行分组分组匹配(packet-matching)代码优化程序。
  • -P 不将网络接口设置成混杂模式。
  • -q 快速输出。只输出较少的协议信息。
  • -r 从指定的文件中读取包(这些包一般通过-w选项产生)。
  • -S 将tcp的序列号以绝对值形式输出,而不是相对值。
  • -s 从每个分组中读取最开始的snaplen个字节,而不是默认的68个字节,一般设置为0,即65535字节。(抓包长度)
  • -T 将监听到的包直接解释为指定的类型的报文,常见的类型有rpc远程过程调用)和snmp(简单网络管理协议;)。
  • -t 不在每一行中输出时间戳。
  • -tt 在每一行中输出非格式化的时间戳。
  • -ttt 输出本行和前面一行之间的时间差。
  • -tttt 在每一行中输出由date处理的默认格式的时间戳。
  • -u 输出未解码的NFS句柄。
  • -v 输出一个稍微详细的信息,例如在ip包中可以包括ttl和服务类型的信息。
  • -vv 输出详细的报文信息。
  • -w 直接将分组写入文件中,而不是不分析并打印出来。

包过滤表达式

tcpdump利用它作为过滤报文的条件,如果一个报文满足表 达式的条件,则这个报文将会被捕获。如果没有给出任何条件,则网络上所有的信息包 将会被截获。

表达式类型关键字:

  • type
    主要包括host,net,port,例如 host 210.27.48.2, 指明 210.27.48.2是一台主机,net 202.0.0.0指明202.0.0.0是一个网络地址,port 23 指明端口号是23。如果没有指定类型,缺省的类型是host。
  • dir
    主要包括src,dst,dst or src,dst and src, 这些关键字指明了传输的方向。举例说明,src 210.27.48.2 ,指明ip包中源地址是 210.27.48.2 , dst net 202.0.0.0 指明目的网络地址是202.0.0.0。如果没有指明 方向关键字,则缺省是src or dst关键字。
  • proto
    主要包括fddi,ip,arp,rarp,tcp,udp等类型。Fddi指明是在FDDI (分布式光纤数据接口网络)上的特定的网络协议,实际上它是”ether”的别名,fddi和ether 具有类似的源地址和目的地址,所以可以将fddi协议包当作ether的包进行处理和分析。 其他的几个关键字就是指明了监听的包的协议内容。如果没有指定任何协议,则tcpdump 将会 监听所有协议的信息包。

除了这三种类型的关键字之外,其他重要的关键字如下:gateway, broadcast,less, greater, 还有三种逻辑运算,取非运算是not !,与运算是and&&,或运算是or,这些关键字可以组合起来构成强大的组合条件来满足人们的需要。

示例

查看HTTP请求响应头以及数据

sudo tcpdump -A -s 0 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'

proto [ expr : size ]
Proto 是 ether, fddi, tr, ip, arp, rarp, tcp, udp, icmp or ip6中的一个,它为索引操作指明了协议层。注意,tcp,udp和其他较高层的协议类型只能应用于IPv4,而不能用于IPv6(这个问题可能在将来能得到解决)。被指定的协议层的字节偏移量由expr给出。Size是可选的,它指明了数据域中,我们所感兴趣的字节数。它可以是1,2,或4,默认为1。运算符的长度,由关键字len给出,指明了数据包的长度。
例如,ether[0] & 1 != 0会捕捉所有的多播数据流。表达式ip[0] & 0xf != 5能捕捉所有带可选域的IP数据包。表达式ip[6:2] & 0x1fff = 0仅捕捉未分段的数据报和段偏移量是0的数据报。这个检查隐含在tcp和udp的下标操作中。例如,tcp[0]通常指第一个字节的TCP首部,而不是指第一个字节的分段。
有些偏移量和域值可以以名字来表示,而不是数值。以下协议首部域的偏移量是正确的:icmptype (ICMP 类型域), icmpcode (ICMP 代码域), and tcpflags (TCP 标志域)。
ICMP 类型域有以下这些: icmp-echoreply, icmp-unreach, icmp-sourcequench, icmp-redirect, icmp-echo, icmp-routeradvert, icmp-routersolicit, icmp-timxceed, icmp-paramprob, icmp-tstamp, icmp-tstampreply, icmp-ireq, icmp-ireqreply, icmp-maskreq, icmp-maskreply.
TCP 标志域有以下这些: tcp-fin, tcp-syn, tcp-rst, tcp-push, tcp-push, tcp-ack, tcp-urg.

参考&鸣谢

** 纯转载 **

当我们用HBase 存储实时数据的时候, 如果要做一些数据分析方面的操作, 就比较困难了, 要写MapReduce Job。 Hive 主要是用来做数据分析的数据仓库,支持标准SQL 查询, 做数据分析很是方便,于是便很自然地想到用Hive来载入HBase的数据做分析, 但是很奇怪地是, 上网查了一下, 只看到以下两种情况:

  1. 如何用Hive 往HBase里面插入大量的数据。
  2. Hive 与HBase集成, 直接从Hive里面连HBase的数据库进行查询。参考链接: https://cwiki.apache.org/confluence/display/Hive/HBaseIntegration

选项1是我们需求的逆操作, 直接忽略, 选项2, 虽然没有做专门的Benchmark, 但总感觉直接对HBase进行查询操作不怎么靠谱, 如果我们要频繁做很多类型的数据分析, 那HBase的压力一定会倍增。
难道没有把HBase里面的数据直接导入到Hive当中的工具或者方法吗?
找了一会, 似乎没找到, 那么只好自己想一个解决方案了。

思路:
利用选项2, 先打通Hive对HBase指定表的全表访问, 再建立一个新的空表, 把查询出来的数据全部导入到新表当中, 以后的所有数据分析操作在新表中完成。
说干就干, 让我们试一个简单的例子。

  • 首先在HBase里面建一个表, 名为 student, 包含 id 和 name 两个column
    1
    2
    hbase shell
    create 'student', 'id', 'name'
  • 向表中插入两行数据
    1
    2
    3
    4
    put 'student', 'row1', 'id:val', '1'
    put 'student', 'row1', 'name:val', 'Tony'
    put 'student', 'row2', 'id:val', '2'
    put 'student', 'row2', 'name:val', 'Mike'
    注意:在插入数据的时候一定要指定column (如id:val, name:val) 直接使用column family (如 id, name) 去存数据会导致后面Hive 建表的时候有问题。
  • 扫描此表, 确定数据已经插入
    1
    2
    3
    4
    5
    6
    scan 'student'
    ROW COLUMN+CELL
    row1 column=id:val, timestamp=1384939342989, value=1
    row1 column=name:val, timestamp=1384939365511, value=Tony
    row2 column=id:val, timestamp=1384939351444, value=2
    row2 column=name:val, timestamp=1384939379245, value=Mike
  • 从Hive建立可以访问HBase的外部表
    1
    2
    3
    4
    CREATE EXTERNAL TABLE student(key string, id int, name string) 
    STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
    WITH SERDEPROPERTIES ("hbase.columns.mapping" = "id:val,name:val")
    TBLPROPERTIES("hbase.table.name" = "student");
  • Hive中建立一个新的空表
    1
    2
    3
    4
    hive> select * from student;
    OK
    row1 1 Tony
    row2 2 Mike
    但是此时这个表实际上是一个虚拟表, 实际的数据还在HBase中。 下面需要在Hive中另建一个结构一样的空表, 再把数据导出来。
  • Hive中建立一个新的空表
    1
    2
    3
    4
    5
    6
    7
    CREATE TABLE new_student (
    key string,
    id INT,
    name STRING)
    ROW FORMAT DELIMITED
    FIELDS TERMINATED BY '\t'
    STORED AS TEXTFILE;
  • 将数据从HBase中导入到新的Hive表中
    1
    2
    3
    4
    5
    hive> INSERT OVERWRITE TABLE new_student SELECT * FROM student;
    hive> select * from new_student;
    OK
    row1 1 Tony
    row2 2 Mike
    至此大功告成!
    以后所有复杂的数据查询和数据分析都可以在new_student表中完成。

参考&鸣谢

xhyve_logo

FreeBSD 下的虚拟技术 bhyve (The BSD Hypervisor) 是去年1月份正式发布的,包含在了 FreeBSD 10.0 发行版中。今天要玩的这个 xhyve 是基于 bhyve 的 Mac OS X 移植版本,也就是说我们想在 Mac 上运行 Linux 的话除了 VirtualBox, VMware Fusion 外,现在有了第三种选择。

xhyve is a lightweight virtualization solution for OS X that is capable of running Linux. It is a port of FreeBSD’s bhyve, a KVM+QEMU alternative written by Peter Grehan and Neel Natu.

特点:

  • super lightweight, only 230 KB in size
  • completely standalone, no dependencies
  • the only BSD-licensed virtualizer on OS X
  • does not require a kernel extension (bhyve’s kernel code was ported to user mode code calling into Hypervisor.framework)
  • multi-CPU support
  • networking support
  • can run off-the-shelf Linux distributions (and could be extended to run other operating systems)

xhyve may make a good solution for running Docker on your Mac, for instance.

install

search xhyver:

1
2
3
4
5
6
7
8
9
$ brew info xhyve
xhyve: stable 0.2.0 (bottled), HEAD
xhyve, lightweight macOS virtualization solution based on FreeBSD's bhyve
https://github.com/mist64/xhyve
/usr/local/Cellar/xhyve/HEAD-1f1dbe3 (11 files, 11.2MB) *
Built from source on 2017-08-24 at 12:12:21
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/xhyve.rb
==> Requirements
Required: macOS >= 10.10 ✔

install:

1
$ brew install xhyve

Ubuntu 16.04 VM

下载ubuntu-16.04.1-server-amd64.iso,装载该iso,然后将其中的vmlinuzinitrd.gz复制出来,以供xhyve使用。

在mac系统下直接装载ubuntu的iso会出错

1
2
$ hdiutil attach ./ubuntu-16.04.1-server-amd64.iso
hdiutil: attach failed - 无可装载的文件系统

所以需要制作一个新的iso,新的iso文件前预留2KB的空间

1
2
3
4
5
6
$ dd if=/dev/zero bs=2k count=1 of=./tmp.iso
$ dd if=./ubuntu-16.04.1-server-amd64.iso bs=2k skip=1 >> ./tmp.iso
$ hdiutil attach ./tmp.iso

$ cp /Volumes/Ubuntu-Server\ 16/install/vmlinuz .
$ cp /Volumes/Ubuntu-Server\ 16/install/initrd.gz .

创建一个磁盘映像文件hdd.img,当作虚拟机的虚拟硬盘

1
$ dd if=/dev/zero of=hdd.img bs=1g count=8

编写VM创建脚本mk_xhyve.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash

KERNEL="vmlinuz"
INITRD="initrd.gz"
CMDLINE="earlyprintk=serial console=ttyS0 acpi=off"

MEM="-m 1G"
#SMP="-c 2"
NET="-s 2:0,virtio-net"
IMG_CD="-s 3,ahci-cd,./ubuntu-16.04.1-server-amd64.iso"
IMG_HDD="-s 4,virtio-blk,./hdd.img"
PCI_DEV="-s 0:0,hostbridge -s 31,lpc"
LPC_DEV="-l com1,stdio"

xhyve $MEM $SMP $PCI_DEV $LPC_DEV $NET $IMG_CD $IMG_HDD -f kexec,$KERNEL,$INITRD,"$CMDLINE"

运行VM创建脚本sudo sh ./mk_xhyve.sh创建ubuntu虚拟机

xhyve_vm_run

按正常系统安装方法安装。。。
待成功安装完成后选择<Go Back> => Execute a shell,进入iso的shell界面,然后需要将已经安装好的hdd.img也就是系统中的/dev/vda中的boot目录copy出来,因为要用里面的vmlinuz-4.4.0-31-genericinitrd.img-4.4.0-31-generic

进入shell后先查看一下ip地址

1
2
3
4
5
6
7
8
9
BusyBox v1.22.1 (Ubuntu 1:1.22.0-15ubuntu1) built-in shell (ash)
Enter 'help' for a list of built-in commands.

~ # ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop qlen 1000
link/ether 82:62:9e:40:cf:32 brd ff:ff:ff:ff:ff:ff
~ #

发现没有获取到ip地址,此时shell用的是busybox,是没有dhclient的,不过busybox提供udhcpc -i <interface>
获取到ip地址后,vm可以通过tar c ./boot | nc -l -p 1234将boot目录发送给宿主机,宿主机用nc <vm ip> 1234 | tar x接受boot目录。

获得到boot目录后取出其中的vmlinuz-4.4.0-31-genericinitrd.img-4.4.0-31-generic,然后修改VM创建脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash

KERNEL="vmlinuz-4.4.0-31-generic"
INITRD="initrd.img-4.4.0-31-generic"
CMDLINE="earlyprintk=serial console=ttyS0 acpi=off root=/dev/vda1"

MEM="-m 1G"
#SMP="-c 2"
NET="-s 2:0,virtio-net"
IMG_CD="-s 3,ahci-cd,./ubuntu-16.04.1-server-amd64.iso"
IMG_HDD="-s 4,virtio-blk,./hdd.img"
PCI_DEV="-s 0:0,hostbridge -s 31,lpc"
LPC_DEV="-l com1,stdio"

xhyve $MEM $SMP $PCI_DEV $LPC_DEV $NET $IMG_CD $IMG_HDD -f kexec,$KERNEL,$INITRD,"$CMDLINE"

并执行sudo sh ./mk_xhyve.sh

xhyve_vm_run2

ubuntu 16.04 正常启动了,over!

参考&鸣谢

zeppelin_logo

Apache Zeppelin是一个让交互式数据分析变得可行的基于网页的开源框架。Zeppelin提供了数据分析、数据可视化等功能。Zeppelin 是一个提供交互数据分析且基于Web的笔记本。方便你做出可数据驱动的、可交互且可协作的精美文档,并且支持多种语言,包括 Scala(使用 Apache Spark)、Python(Apache Spark)、SparkSQL、 Hive、 Markdown、Shell等等。

环境

采用docker环境部署zeppelin

OS:CentOS 7.3.1611
JAVA:OpenJDK 1.8
zeppelin: 0.7.2

dockerfile:

1
2
3
4
5
6
7
8
FROM centos:7.3.1611

MAINTAINER zhoub

RUN yum update -y
RUN yum install -y java-1.8.0-openjdk.x86_64
RUN echo "JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.el7_3.x86_64/jre" | tee -a /etc/bashrc
RUN echo "export JAVA_HOME" | tee -a /etc/bashrc

下载安装

zeppelin官网或github上下载,推荐下载最新版。解压tar包,然后在准备好的环境中运行zeppelin/bin/zeppelin-daemon.sh start

docker:

1
2
3
4
5
6
7
#!/bin/sh

cmd="/root/run/entry.sh"
image="centos7.3:java1.8"
net="hadoop_net"

docker run -d --rm -w /root/ -v ${PWD}/run:/root/run -v ${PWD}/zeppelin:/root/zeppelin -v ${PWD}/hbase:/root/hbase -p 8080 --network ${net} -h zeppelin --name zeppelin ${image} ${cmd}

entry.sh:

1
2
3
4
5
6
7
8
#!/bin/sh

/root/zeppelin/bin/zeppelin-daemon.sh start

while true
do
sleep 10s
done

架构

zeppelin_arch
Zeppelin具有客户端/服务器架构,客户端一般就是指浏览器。服务器接收客户端的请求,并将请求通过Thrift协议发送给翻译器组。翻译器组物理表现为JVM进程,负责实际处理客户端的请求并与服务器进行通信。

zeppelin_interpreter_arch
翻译器是一个插件式的体系结构,允许任何语言/后端数据处理程序以插件的形式添加到Zeppelin中。当前的Zeppelin已经支持很多翻译器。插件式架构允许用户在Zeppelin中使用自己熟悉的特定程序语言或数据处理方式。例如,通过使用%spark翻译器,可以在Zeppelin中使用Scala语言代码。

配置

zeppelin的配置主要指interpreter的设置,然后note通过配置好的interpreter进行解释执行。

JDBC-hive配置

zeppelin想要通过jdbc连接hive需要对hive、hdfs、zeppelin三者进行配置

hive

需要在hive节点上启动hiveserver2服务nohup /shared-disk/apache-hive-2.1.1-bin/bin/hive --service hiveserver2&

zeepelin

拷贝连接hive-jdbc需要用到的jar包

将包hive-jdbc-1.1.0+cdh5.9.1+795-1.cdh5.9.1.p0.4.el7.noarch.rpm解开后的jar文件拷贝到zeppelin/lib/interpreter/目录下
hive-jdbc-1.1.0+cdh5.9.1+795-1.cdh5.9.1.p0.4.el7.noarch.rpm包内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
total 51448
-rw-r--r-- 1 root root 62050 Aug 25 10:18 commons-logging-1.1.3.jar
-rw-r--r-- 1 root root 19386631 Aug 25 10:18 hive-exec-1.1.0-cdh5.9.1.jar
lrwxrwxrwx 1 root root 28 Aug 25 10:18 hive-exec.jar -> hive-exec-1.1.0-cdh5.9.1.jar
-rw-r--r-- 1 root root 96598 Aug 25 10:19 hive-jdbc-1.1.0-cdh5.9.1.jar
-rw-r--r-- 1 root root 23635048 Aug 25 10:19 hive-jdbc-1.1.0-cdh5.9.1-standalone.jar
lrwxrwxrwx 1 root root 28 Aug 25 10:19 hive-jdbc.jar -> hive-jdbc-1.1.0-cdh5.9.1.jar
lrwxrwxrwx 1 root root 39 Aug 25 10:19 hive-jdbc-standalone.jar -> hive-jdbc-1.1.0-cdh5.9.1-standalone.jar
-rw-r--r-- 1 root root 5558969 Aug 25 10:19 hive-metastore-1.1.0-cdh5.9.1.jar
lrwxrwxrwx 1 root root 33 Aug 25 10:19 hive-metastore.jar -> hive-metastore-1.1.0-cdh5.9.1.jar
-rw-r--r-- 1 root root 827980 Aug 25 10:19 hive-serde-1.1.0-cdh5.9.1.jar
lrwxrwxrwx 1 root root 29 Aug 25 10:19 hive-serde.jar -> hive-serde-1.1.0-cdh5.9.1.jar
-rw-r--r-- 1 root root 2058121 Aug 25 10:19 hive-service-1.1.0-cdh5.9.1.jar
lrwxrwxrwx 1 root root 31 Aug 25 10:19 hive-service.jar -> hive-service-1.1.0-cdh5.9.1.jar
-rw-r--r-- 1 root root 313702 Aug 25 10:19 libfb303-0.9.3.jar
-rw-r--r-- 1 root root 234201 Aug 25 10:19 libthrift-0.9.3.jar
-rw-r--r-- 1 root root 481535 Aug 25 10:19 log4j-1.2.16.jar

配置jdbc interpreter

  • default.driver: org.apache.hive.jdbc.HiveDriver
  • default.url: jdbc:hive2://172.26.1.177:10000/default
    hiveserver2的默认端口为10000
  • default.user: root
    此处我使用root用户,后文中的hdfs的proxy设置也要用root

zeppelin_hive_jdbc_setting

hdfs

若zeppelin使用jdbc连接hive出错,报如下错误:

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
Could not establish connection to jdbc:hive2://192.168.0.51:10000: Required field 'serverProtocolVersion' is unset! Struct:TOpenSessionResp(status:TStatus(statusCode:ERROR_STATUS, infoMessages:[*org.apache.hive.service.cli.HiveSQLException:Failed to open new session: java.lang.RuntimeException:
java.lang.RuntimeException:
org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.authorize.AuthorizationException): User: hive is not allowed to impersonate hive:13:12,
org.apache.hive.service.cli.session.SessionManager:openSession:SessionManager.java:266,
org.apache.hive.service.cli.CLIService:openSessionWithImpersonation:CLIService.java:202,
org.apache.hive.service.cli.thrift.ThriftCLIService:getSessionHandle:ThriftCLIService.java:402,
org.apache.hive.service.cli.thrift.ThriftCLIService:OpenSession:ThriftCLIService.java:297,
org.apache.hive.service.cli.thrift.TCLIService$Processor$OpenSession:getResult:TCLIService.java:1253,
org.apache.hive.service.cli.thrift.TCLIService$Processor$OpenSession:getResult:TCLIService.java:1238,
org.apache.thrift.ProcessFunction:process:ProcessFunction.java:39,
org.apache.thrift.TBaseProcessor:process:TBaseProcessor.java:39,
org.apache.hive.service.auth.TSetIpAddressProcessor:process:TSetIpAddressProcessor.java:56,
org.apache.thrift.server.TThreadPoolServer$WorkerProcess:run:TThreadPoolServer.java:285,
java.util.concurrent.ThreadPoolExecutor:runWorker:ThreadPoolExecutor.java:1145,
java.util.concurrent.ThreadPoolExecutor$Worker:run:ThreadPoolExecutor.java:615,
java.lang.Thread:run:Thread.java:745,
*java.lang.RuntimeException:java.lang.RuntimeException:
org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.authorize.AuthorizationException): User: hive is not allowed to impersonate hive:21:8,
org.apache.hive.service.cli.session.HiveSessionProxy:invoke:HiveSessionProxy.java:83,
org.apache.hive.service.cli.session.HiveSessionProxy:access$000:HiveSessionProxy.java:36,
org.apache.hive.service.cli.session.HiveSessionProxy$1:run:HiveSessionProxy.java:63,
java.security.AccessController:doPrivileged:AccessController.java:-2,
javax.security.auth.Subject:doAs:Subject.java:415,
org.apache.hadoop.security.UserGroupInformation:doAs:UserGroupInformation.java:1657,
org.apache.hive.service.cli.session.HiveSessionProxy:invoke:HiveSessionProxy.java:59,
com.sun.proxy.$Proxy19:open::-1,
org.apache.hive.service.cli.session.SessionManager:openSession:SessionManager.java:258,
*java.lang.RuntimeException:
org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.authorize.AuthorizationException): User: hive is not allowed to impersonate hive:26:5,
org.apache.hadoop.hive.ql.session.SessionState:start:SessionState.java:494,
org.apache.hive.service.cli.session.HiveSessionImpl:open:HiveSessionImpl.java:137,
sun.reflect.GeneratedMethodAccessor11:invoke::-1,
sun.reflect.DelegatingMethodAccessorImpl:invoke:DelegatingMethodAccessorImpl.java:43,
java.lang.reflect.Method:invoke:Method.java:606,
org.apache.hive.service.cli.session.HiveSessionProxy:invoke:HiveSessionProxy.java:78,
*org.apache.hadoop.ipc.RemoteException:User: hive is not allowed to impersonate hive:45:19,
org.apache.hadoop.ipc.Client:call:Client.java:1427,
org.apache.hadoop.ipc.Client:call:Client.java:1358,
org.apache.hadoop.ipc.ProtobufRpcEngine$Invoker:invoke:ProtobufRpcEngine.java:229,
com.sun.proxy.$Proxy14:getFileInfo::-1,
org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB:getFileInfo:ClientNamenodeProtocolTranslatorPB.java:771,
sun.reflect.GeneratedMethodAccessor7:invoke::-1,
sun.reflect.DelegatingMethodAccessorImpl:invoke:DelegatingMethodAccessorImpl.java:43,
java.lang.reflect.Method:invoke:Method.java:606,
org.apache.hadoop.io.retry.RetryInvocationHandler:invokeMethod:RetryInvocationHandler.java:252,
org.apache.hadoop.io.retry.RetryInvocationHandler:invoke:RetryInvocationHandler.java:104,
com.sun.proxy.$Proxy15:getFileInfo::-1,
org.apache.hadoop.hdfs.DFSClient:getFileInfo:DFSClient.java:2116,
org.apache.hadoop.hdfs.DistributedFileSystem$22:doCall:DistributedFileSystem.java:1315,
org.apache.hadoop.hdfs.DistributedFileSystem$22:doCall:DistributedFileSystem.java:1311,
org.apache.hadoop.fs.FileSystemLinkResolver:resolve:FileSystemLinkResolver.java:81,
org.apache.hadoop.hdfs.DistributedFileSystem:getFileStatus:DistributedFileSystem.java:1311,
org.apache.hadoop.fs.FileSystem:exists:FileSystem.java:1424,
org.apache.hadoop.hive.ql.session.SessionState:createRootHDFSDir:SessionState.java:568,
org.apache.hadoop.hive.ql.session.SessionState:createSessionDirs:SessionState.java:526,
org.apache.hadoop.hive.ql.session.SessionState:start:SessionState.java:480],
errorCode:0, errorMessage:Failed to open new session: java.lang.RuntimeException: java.lang.RuntimeException: org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.authorize.AuthorizationException): User: hive is not allowed to impersonate hive), serverProtocolVersion:null)

需要在hdfs core-site.xml中增加配置

1
2
3
4
5
6
7
8
9
10
11
...
<property>
<name>hadoop.proxyuser.hive.groups</name>
<value>*</value>
</property>

<property>
<name>hadoop.proxyuser.hive.hosts</name>
<value>*</value>
</property>
...

然后重启hdfs namenode

使用

zeppelin_main_ui

参考&鸣谢

ubuntu

Xenial Xerus (好客的非洲地松鼠)

Ubuntu 16.04 的发布代号,简称XX。Canonical和Ubuntu的创始人马克·沙特尔沃思(Mark Shuttleworth) 在他的个人博文中写道:“这是如此的巧合幸运,我们的下个LTS应该为X,因为‘xenial’代表着主人和客人之间融洽的友好关系,而且对于推进Ubuntu OpenStack的LXD和KVM工作起到了非常积极的作用。Xerus则是非洲地松鼠,是我的祖国最为常见的动物。他们在沙漠环境中繁殖,并且以小型团体的方式生活,能够和邻居和谐生活的社会群体。”

glusterfs-antmascot

概述

GlusterFS (Gluster File System) 是一个开源的分布式文件系统,主要由 Z RESEARCH 公司负责开发。GlusterFS 是 Scale-Out 存储解决方案 Gluster 的核心,具有强大的横向扩展能力,通过扩展能够支持数PB存储容量和处理数千客户端。GlusterFS 借助 TCP/IP 或 InfiniBand RDMA 网络将物理分布的存储资源聚集在一起,使用单一全局命名空间来管理数据。GlusterFS 基于可堆叠的用户空间设计,可为各种不同的数据负载提供优异的性能。

glusterfs-frame

GlusterFS 总体架构与组成,它主要由存储服务器(Brick Server)、客户端以及 NFS/Samba 存储网关组成。不难发现,GlusterFS 架构中没有元数据服务器组件,这是其最大的设计这点,对于提升整个系统的性能、可靠性和稳定性都有着决定性的意义。

  • GlusterFS 支持 TCP/IP 和 InfiniBand RDMA 高速网络互联。
  • 客户端可通过原生 GlusterFS 协议访问数据,其他没有运行 GlusterFS 客户端的终端可通过 NFS/CIFS 标准协议通过存储网关访问数据(存储网关提供弹性卷管理和访问代理功能)。
  • 存储服务器主要提供基本的数据存储功能,客户端弥补了没有元数据服务器的问题,承担了更多的功能,包括数据卷管理、I/O 调度、文件定位、数据缓存等功能,利用 FUSE(File system in User Space)模块将 GlusterFS 挂载到本地文件系统之上,实现 POSIX 兼容的方式来访问系统数据。

卷类型

Distribute卷

分布式卷,基于 Hash 算法将文件分布到所有 brick server,只是扩大了磁盘空间,不具备容错能力。由于distribute volume 使用本地文件系统,因此存取效率并没有提高,相反会因为网络通信的原因使用效率有所降低,另外本地存储设备的容量有限制,因此支持超大型文件会有一定难度。

glusterfs-volume-distribute

Stripe卷

条带卷,类似 RAID0,文件分成数据块以 Round Robin 方式分布到 brick server 上,并发粒度是数据块,支持超大文件,大文件的读写性能高。

glusterfs-volume-stripe

Replica卷

复制卷,文件同步复制到多个 brick 上,文件级 RAID1,具有容错能力,写性能下降,读性能提升。Replicated 模式,也称作 AFR(Auto File Replication),相当于 RAID1,即同一文件在多个镜像存储节点上保存多份,每个 replicated 子节点有着相同的目录结构和文件,replica volume 也是在容器存储中较为推崇的一种。

glusterfs-volume-replica

Distribute Stripe卷

分布式条带卷,Brick server 数量是条带数的倍数,兼具 distribute 和 stripe 卷的特点。分布式的条带卷,volume 中 brick 所包含的存储服务器数必须是 stripe 的倍数(>=2倍),兼顾分布式和条带式的功能。每个文件分布在四台共享服务器上,通常用于大文件访问处理,最少需要 4 台服务器才能创建分布条带卷。

glusterfs-volume-distribute-stripe

Distribute Replica卷

分布式复制卷,Brick server 数量是镜像数的倍数,兼具 distribute 和 replica 卷的特点,可以在 2 个或多个节点之间复制数据。分布式的复制卷,volume 中 brick 所包含的存储服务器数必须是 replica 的倍数(>=2倍),兼顾分布式和复制式的功能。

glusterfs-volume-distribute-replica

Stripe Replica卷

条带复制卷,类似 RAID 10,同时具有条带卷和复制卷的特点。

glusterfs-volume-stripe-replica

Distribute Stripe Replica卷

分布式条带复制卷,三种基本卷的复合卷,通常用于类 Map Reduce 应用。

glusterfs-volume-distribute-stripe-replica

环境

使用docker部署GlusterFS,dockerfile如下:

1
2
3
4
5
6
7
8
9
FROM centos:7.3.1611

MAINTAINER zhoub

RUN yum update -y
RUN yum install -y centos-release-gluster310.noarch
RUN yum update -y

RUN yum clean all

创建GlusterFS镜像

1
2
# docker build -t glusterfs:3.10 -f ./dockerfile .
...

创建docker容器脚本mkgfs.sh,创建两个容器,用来组件gluster集群

1
2
3
4
5
6
7
8
9
#!/bin/sh

count=2
image=glusterfs:3.10

for((i=1;i<=$count;i++))
do
docker run -d -e "container=docker" --privileged=true -v /sys/fs/cgroup:/sys/fs/cgroup -h glf-client-$i --name glf_client_$i ${image} /usr/sbin/init
done

部署&使用

部署

安装glusterfs-server

1
2
# yum install -y glusterfs-server
...

准备数据目录

1
# mkdir -p /brks/brk1/gv0

(此处不进行数据盘挂载,直接使用系统硬盘)

启动glusterd服务

1
2
3
4
5
# systemctl enable glusterd
...
# systemctl start glusterd
# systemctl status glusterd
...

在容器glf-client-1上添加glf-client-2

1
gluster peer probe glf-client-2

(此处省去向/etc/hosts增加ip、域名的映射修改)

在容器glf-client-2上添加glf-client-1

1
gluster peer probe glf-client-1

(此处省去向/etc/hosts增加ip、域名的映射修改)

在任意一个容器上创建、启动volume

1
2
# gluster volume create gv0 replica 2 glf-client-1:/brks/brk1/gv0 glf-client-2:/brks/brk1/gv0 force
# gluster volume start gv0

(由于gv0使用的是系统盘,所以在volume创建时,需要指定force参数)
创建、启动完volume后可通过gluster volume infogluster volume status gv0查看状态。

使用

1
mount -t glusterfs glf-client-1:gv0 /mnt/

参考&鸣谢