Linux用于I/O的数据结构及fcntl函数详解

Linux内核用于IO的数据结构

内核使用三种数据结构表示打开的文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响.

  • 进程表项 每个进程在记录表中都有一个记录项,记录项中包含一张打开的文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:
    • 文件描述符标志(close_on_exec,close_on_exec是一个进程所有文件描述符的标记位图,每个比特位代表一个打开的文件描述符,用于确定在系统调用execve()时需要关闭的文件句柄)。
    • 指向一个文件表项的指针
  • 文件表项 内核为所有打开文件维护一张文件表(不同进程打开相同文件将有两条记录),每个文件表项中包括:
    • 文件状态标志(read,write,append,async,nonblock等)
    • 当前文件偏移量
    • 指向该文件v(i)节点表项的指针
  • 节点表项。每个打开的文件都有一个v-node结构,v-node中包含了文件类型和对此文件进行各种操作函数的指针。对于大多数文件,V-node中还包含了文件inode节点信息,这些信息是打开文件时从磁盘上读入内存的,所以文件所有信息都是随时可用的。
    • v节点的信息
    • 当前文件的长度
    • i节点的信息

close_on_exec是一个进程所有文件描述符(文件句柄)的位图标志,每个bit代表一个打开的文件描述符,用于
确定在系统调用execve()时是否需要关闭文件句柄。
当一个进程fork出一个子进程时,通常会在子进程中调用execve()函数
加载执行另一个新程序。此时子进程将完全被新程序替换掉,并在子进程中执行新程序。若一个文件描述符在close_on_exec中对应的
bit被设置,那么在执行execve()时该文件描述符将被关闭,否则该文件描述符将始终处于打开状态。
当打开一个文件的时候,默认情况下文件句柄在子进程中也处于打开状态。
注意文件描述符和文件描述符标志的区别,文件描述符是文件进程打开文件时的文件句柄,文件描述符标志为close_on_exec。

下图显示了一个进程打开两个不同文件时三张表对应的关系:

如果两个独立的进程同时打开同一个文件,三张表之间的对应关系如下:


从上面可以看出,不同进程打开相同的文件时每个进程将获得各自的文件表项,这是因为不同的进程都有各自的文件偏移量。
当我们对文件进行操作的时候,上面三种表项之间的变化关系如下:

  • 当对文件进行写操作时(write),在文件表项中的文件偏移量将增加写入的字节数。如果此时文件偏移量超过了文件长度,更新文件长度为当前的文件偏移量
  • 当用O_APPEND标志打开一个文件,则相应的标志也被设置到文件表项的文件标志状态中。每次对这种具有追加标志的文件进行写操作时,首先将当前文件偏移量设置为文件文件长度,这就使得每次增加的内容都会写到文件末尾。
  • 若使用lseek定位到文件末尾,则文件表项中偏移量被设置为文件长度
  • lseek函数只修改文件表项中的偏移量,不进行任何IO操作。

注意问题:

  • 可能有多个文件描述符指向同一个文件表项,如在fork的时候就有可能发生
  • 注意文件描述符和文件状态标志在作用范围方面的区别。前者只用于一个进程的描述符,而后者则应用于指向该给定文件表项的任何进程中的所有描述符。

函数fcntl功能及用法

函数原型:

1
2
3
#include <fcntl.h>
int fcntl(int fd, int cmd, .../*int args or lock args*/);

函数功能:
fcntl的作用是改变已经打开的文件属性。

参数说明:

  • fd 为file descriptor,即文件打开之后的文件描述符
  • cmd为命令,即需要对fd操作的命令,一般为几个宏定义中的其中一个
  • args 参数,此参数为执行cmd命令所需要的参数

cmd参数命令及功能:

fcntl的功能可以分为5种:

  • 复制一个已有的文件描述符
    • cmd = F_DUPFD,此功能*返回一个文件描述符,新的描述符的值为大于或等于args的可用的(尚未打开)文件描述符的最小值,新描述符与fd共用一个文件表项。但是新的文件描述符有它自己的一套文件描述符标志.
    • cmd = F_DUPFD_CLOEXEC.与上述功能一致,唯一不同的是使用此命令会设置CLOSE_ON_EXEC,
      即当执行execve的时候,文件描述符将被关闭。
  • 获取或设置文件描述符标志
    • cmd = F_GETFD 返回与fd关联的close_on_exec标志,第三个参数被忽略。
    • cnd = F_SETFD 将文件描述符标志close_on_exec设置为第三个参数
  • 获取或设置文件状态标志
    • cmd = F_GETFL 获取fd对应的文件的状态标志(存储于文件表项)
    • cmd = F_SETFL 设置fd对应文件的状态标志
  • 获取或设置异步IO所有权
    • cmd = F_GETOWN 获取当前接受SIGIO和SIGURG信号的进程IO或者进程组ID。
    • cmd = F_SETOWN 设置接受SIGIO和SIGURG信号的进程ID或进程组ID。返回值为正则为进程,返回值为负数即为进程组。
  • 获取或记录锁(cmd=F_GETLK、F_SETLK、F_SETLKW),此处不详解

文件状态标志说明

文件状态标志存储与文件表项中,它用于说明进程对当前文件的可操作权限。文件的
操作权限说明如下图表所示,权限设置可使用|或者&进行设置.

文件状态标志 功能说明
O_RDONLY 只读权限
O_WRONLY 只写权限
O_RDWR 读写权限
O_EXEC 可执行权限
O_SEARCH 只搜索打开权限
O_APPEND 追加写
O_NONBLOCK 非阻塞模式
O_SYNC 等待写完成(数据和属性)
O_DSYNC 等待写完成(仅数据)
O_RSYNC 同步读写
O_FSYNC 等待写完成
O_ASYNC 异步IO

注意O_RDONLY、O_WRONLY、O_RDWR、O_EXEC、O_SEARCH这个五个标志并不各占
一位,一个文件的访问方式只能取这五个里面的一个。因此检查当前文件的是这
五个标志中的哪一个需要使用屏蔽字O_ACCMODE取得当问方式位,在于这五个标志
进行对比。

代码示例说明

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
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
int test;
//返回结果:test = 10,返回大于等于第三个参数可用的fd(10)。
test = fcntl(STDIN_FILENO, F_DUPFD, 10);
printf("result of fcntl(STDIN_FILENO, F_DUPFD, 10) is:%d\n", test);
//返回结果:test=11,与上一个函数功能相同
//不同之处在于其设置了close_on_exec,当执行exec时关闭响应的文件描述符
//注意有一些版本的系统上没有定义这个宏
//test = fcntl(STDIN_FILENO, F_DUPFD_CLOEXEC, 10);
//返回结果 test = 0. 标准输入的文件描述符标志(即关闭标志)为0,
//意思是执行exec时关联的文件描述符不关闭
test = fcntl(STDIN_FILENO, F_GETFD);
printf("the result of fcntl(STDIN_FILENO, F_GETFD) is:%d\n", test);
//将文件描述符标志设置为0
fcntl(STDIN_FILENO, F_SETFD, 0);
//返回结果 test = 32270
test = fcntl(STDIN_FILENO, F_GETFL);
printf("the result of fcntl(STDIN_FILENO, F_GETFL) is:%d\n", test);
//给STDIN_FILENO对应的IO添加非阻塞权限
test |= O_NONBLOCK;
fcntl(STDIN_FILENO, F_SETFL, test);
test = fcntl(STDIN_FILENO, F_GETFL);
printf("the result of fcntl(STDIN_FILENO, F_GETFL) is:%d\n", test);
}

STDIN_FILENO,STDOUT_FILENO以及STDERR_FILENO

STDIN_FILENO等是系统API接口库中的宏定义,它是一个int类型的值,是打开文件的句柄,
对应的主要函数有open,read,write和close等。
STDIN_FILENO的含义是标准输入(键盘)的文件描述符,STDOUT_FILENO是标准输出流的文件描述符,STDERR_FILENO
是标准错误流的文件描述符。

STDIN_FILENO与stdin的区别

  • 数据类型不同 stdin的数据类型为FILE*,STDIN_NO的数据类型为int
  • 可用的函数不同 stdin主要用的函数有fread,fwrite,fclose,STDIN_FILENO可用的函数为write,read和close
  • stdin属于标准IO,高级的输入输出函数,在stdio.h中定义;STDIN_FILENO是文件描述符,一般定义为0,1,2,属于没有buffer的IO,直接调用系统调用,定义在unistd.h中
  • 层次不同,stdin属于标注库处理的输入流,其声明为FILE*型,对应的函数前面都有f开头;而STDIN_FILENO属于系统API接口,对用的函数是一些系统级的调用

Comment and share

在C语言中,当向一个函数传递指针的时候,它的长度信息往往会被截断(如果是数组名),传入函数的只是一个指针,而无法标示数组的长度,在函数中我们也无法获取它的长度信息,除非显示的传递一个长度参数。但是使用free函数的过程中,我们只是向free中传递了一个指针,并没有标示它的大小,那free是如何知道将要被free掉的内存的大小呢?下面将详细解答这一问题。

malloc函数的实现是以块分配内存,在被分配的块中包括两部分。第一部分中存储含有报头的元数据,它其中包含有分配块的大小信息,是一个常量;第二部分中存储实际用户数据。而使用malloc分配内存返回的是第二部分用户数据的地址。而块的两个部分在内存中的存储取决有编译器的实现,一般有两种情况,第一种是最常见的,即元数据和用户数据是连续的,存储在连续空间位置。第二种是两部分分开存储。

对于第一种情况,malloc分配内存的空间图如下:

1
2
3
4
5
6
7
8
____ The allocated block ____
/ \
+--------+--------------------+
| Header | Your data area ... |
+--------+--------------------+
^
|
+-- The address you are given

对于上述情况,malloc中内存转换实现方式为:

1
2
3
4
5
6
/* The corresponding word size */
#define SIZE_SZ (sizeof(INTERNAL_SIZE_T))
...
/* conversion from malloc headers to user pointers, and back */
#define chunk2mem(p) ((void*)((char*)(p) + 2*SIZE_SZ))
#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ))

如上源码我们可以看出来,chunk2mem将原始指针转换为user_data的指针,mem2chunk做了相反的转换。这个时候free的实现为:

1
2
3
4
void free(void* mem) {
p = mem2chunk(mem);
// Now that you know how large is chunk "p", go ahead and free the chunk.
}

同时,我们通过实验可以推测,标准库对void*的operator=做了重载。

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
int main() {
int *a = (int*)malloc(sizeof(int));
int *b = (int*)malloc(sizeof(int));
short *c = (short*)malloc(sizeof(short));
std::cout << a << std::endl
<< b << std::endl
<< c << std::endl;
std::cout << b - a << std::endl;
std::cout << reinterpret_cast<size_t>(b) - reinterpret_cast<size_t>(a) << std::endl;
}

输出结果为:

1
2
3
4
5
0x10b5c20
0x10b5c40
0x10b5c60
8
32

参考:

https://www.quora.com/How-does-free-function-in-C-knows-how-much-memory-to-be-released-deallocate

http://stackoverflow.com/questions/1518711/how-does-free-know-how-much-to-free

Comment and share

why assigment operator can not be frined

this is a problem in my work and I have find th solution on stackoverflow,so recorder here

problem description

When I refactor my object, I have a problem which need to change the return value
of and function std::string to a struct data, but I don’t want to change my code
where the function be used, so I want to overload the assignment operator which
will assign a struct to string.The code is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <string>
class data_struct{
friend std::string operator = (std::string& s, data_struct& d);
private:
bool success{false};
std::string message{""};
public:
data_struct(bool t_success, std::string t_message):
success(t_success), message(t_message) {}
}
std::string operator = (std::string& s, data_struct& d) {
s = d.s;
return s;
}
int main() {
data_struct d(false,"haha");
std::string s = d;
}

when I compile this file , this is an error follows:

1
2
3
4
5
6
7
8
9
main.cpp:3:64: error: ‘std::__cxx11::string operator=(std::__cxx11::string&, data_struct&)’ must be a nonstatic member function
friend std::string operator = (std::string& s, data_struct& d);
^
main.cpp:12:13: error: expected initializer before ‘operator’
std::string operator = (std::string& s, data_struct& d) {
^
main.cpp: In function ‘int main()’:
main.cpp:19:19: error: conversion from ‘data_struct’ to non-scalar type ‘std::__cxx11::string {aka std::__cxx11::basic_string<char>}’ requested
std::string s = d;

why does this happen?

Firstly, it should be noted that this has nothing to do with the operator being implemented as a friend specifically. It is really about implementing the copy-assignment as a member function or as a non-member (standalone) function. Whether that standalone function is going to be a friend or not is completely irrelevant: it might be, it might not be, depending on what it wants to access inside the class.

Now, the answer to this question is given in D&E book (The Design and Evolution of C++). The reason for this is that the compiler always declares/defines a member copy-assignment operator for the class (if you don’t declare your own member copy-assignment operator).

If the language also allowed declaring copy-assignment operator as a standalone (non-member) function, you could end up with the following

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Class definition
class SomeClass {
// No copy-assignment operator declared here
// so the compiler declares its own implicitly
...
};
SomeClass a, b;
void foo() {
a = b;
// The code here will use the compiler-declared copy-assignment for `SomeClass`
// because it doesn't know anything about any other copy-assignment operators
}
// Your standalone assignment operator
SomeClass& operator =(SomeClass& lhs, const SomeClass& rhs);
void bar() {
a = b;
// The code here will use your standalone copy-assigment for `SomeClass`
// and not the compiler-declared one
}

As seen in the above example, the semantics of the copy-assignment would change in the middle of the translation unit - before the declaration of your standalone operator the compiler’s version is used. After the declaration your version is used. The behavior of the program will change depending on where you put the declaration of your standalone copy-assignment operator.

This was considered unacceptably dangerous (and it is), so C++ doesn’t allow copy-assignment operator to be declared as a standalone function.

It is true that in your particular example, which happens to use a friend function specifically, the operator is declared very early, inside the class definition (since that’s how friends are declared). So, in your case the compiler will, of course, know about the existence of your operator right away. However, from the point of view of C++ language the general issue is not related to friend functions in any way. From the point of view of C++ language it is about member functions vs. non-member functions, and non-member overloading of copy-assignment is just prohibited entirely for the reasons described above.

Solution

due to the solution above is not proper.So I overWrite the orignal function, and
invoke different version in their needed place.

Comment and share

工厂类模式家族之简单工厂模式

在面向对象编程的领域,设计模式给我们提供了适合特定场景的软件设计思想,不过大多设计模式都可以通过一般的设计进行替代,但我们为什么还有费工夫去遵循设计模式呢?这就要说到设计模式的精髓了,我们通过对软件系统进行良好的设计,不但可以提高代码的可重用性,增强系统的可扩展性,给客户提供良好的接口,还可以减少编码过程中因代码组织太乱扩展过程中需要修改旧代码而带来的一连串的错误,降低维护成本。软件设计过程我们应该尽量追求符合软件设计的开闭原则。

开闭原则:在面向对象编程领域中,开闭原则规定“软件中的对象(类,模块,函数)应该对于扩展是开放的,但是对于修改是封闭的”。该特性在产品化的环境中是特别有价值的,在这种环境中我们认为一旦类完成,我们可以对它进行扩展改变其行为,但是不允许修改类。也就是说一个类的实现只应该因错误而修改。--by wiki

工厂模式家族包括三种重要的模式,分别是简单工厂模式,工厂模式以及抽象工厂模式。他们都属于类的创建型模式。创建型模式包括两种,分别是类的创建型模式对象的创建型模式。类的创建型模式通常使用继承关系,将类的创建交由其具体的子类完成,这样就向外界隐藏了如何得到具体类的实现细节,以及这个类的实例是如何被创建或者组织在一起的。;对象创建型模式通常把一个类的创建委托给另一个对象完成,可以根据语境动态地决定生成哪个具体类的实例。

本节我们来讲解工厂模式中的简单工厂模式。主要包括以下几个部分:

  • 简单工厂模式的实质
  • 简单工厂模式的适用性
  • 简单工厂模式的结构
  • 简单工厂模式的参与者
  • 简单工厂模式各成分之间的交互
  • 实际应用
  • 简单工厂模式的优缺点
  • 简单工厂模式实例代码

简单工厂模式的实质

简单工厂模式又称为静态工厂模式。 它的实质是根据客户传递的信息,工厂类通过该信息制造出相应的产品的实例返回给客户。这样我们就做到了客户只是产品的消费者,而真正的创建者是工厂类。 在简单工厂模式中,待被创建的产品通常继承自同一个类。而这个类中包含了具体产品的所有的公共成员和方法。

简单工厂模式的适用性

简单工厂模式将对象的创建和对象本身的业务分离开来,降低了系统的耦合度,当维护期间需要对客户代码或者产品代码进行修改的时候,修改其中之一不会影响另一个。

简单工厂模式的结构

简单工厂模式的参与者

简单工厂模式中一般有以下几个部分:

  • 工厂类:简单工厂模式的核心,它的作用是根据客户提供的信息创建相应的具体产品
  • 抽象产品:所有具体产品的父类,其中主要包含所有具体产品共有的方法或对象
  • 具体产品:工厂类创建的具体实例。

简单工厂模式各成分之间的交互

  • 客户首先创建factory类(一般为单例模式),
  • factory类创建成功后,客户调用其createProduct方法,并传入相关信息,
  • 具体产品实例被创建并返回给client,开始进行使用concreteProduct

简单工厂模式的实际应用

在实际开发中使用简单工厂模式中,我们可以进行变通的使用。

在实际情况种可能会出现比较复杂的抽象产品和和具体产品之间的关系,这个时候我们依然也可以使用抽象工厂模式:

简单工厂模式的优缺点

简单工厂模式的优点: 通过在中间添加一个工厂类,降低产品类和客户代码之间的耦合度;客户在获取产品的时候无需记住所有产品的构造方法,只需要通过同意的工厂类接口进行创建产品,大大提高了效率准确率。

简单工厂模式的缺点:所有的产品实例化的逻辑都在工厂类的一个创建方法中,当需要添加新产品的时候,不得不进行修改factory类,这样就违背了设计的开闭原则(对扩展开放,对修改封闭);另外当产品类别过多的时候,会出现这个函数冗杂的问题,增加维护成本。最重要的是这个工厂类是所有产品的入口,当它不能工作的时候,所有的产品将陷入瘫痪状态。

简单工厂的实例

下面通过形状shape,circle以及rectangle来实现一个简单的简单工厂模式(如有问题,欢迎指正):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//factory.hpp
#ifndef FACTORY_HPP_
#define FACTORY_HPP_
#include "circle.hpp"
#include "rectangle.hpp"
#include <memory>
#include <string>
class Factory {
private:
Factory() {}
Factory(const Factory &) = delete;
Factory &operator=(const Factory &) = delete;
public:
static std::shared_ptr<Factory> m_factory;
static std::shared_ptr<Factory> getInstance();
static std::shared_ptr<Shape> getShape(const std::string &flag);
};
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//factory.cpp
#include "factory.hpp"
std::shared_ptr<Factory> Factory::m_factory = nullptr;
std::shared_ptr<Factory> Factory::getInstance() {
if (m_factory == nullptr) {
m_factory = std::unique_ptr<Factory>(new Factory());
}
return m_factory;
}
std::shared_ptr<Shape> Factory::getShape(const std::string &flag) {
if (0 == flag.compare("circle")) {
return std::shared_ptr<Shape>(new Circle());
} else if (0 == flag.compare("rectangle")) {
return std::shared_ptr<Shape>(new Rectangle());
}
}
1
2
3
4
5
6
7
8
9
10
//shape.hpp
#ifndef SHAPE_HPP_
#define SHAPE_HPP_
#include <iostream>
class Shape {
public:
virtual void draw() = 0;
virtual double getArea() = 0;
};
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//circle.hpp
#ifndef CIRCLE_HPP
#define CIRCLE_HPP
#include "shape.hpp"
class Circle : public Shape {
public:
Circle(double t_radius = 0) : m_radius(t_radius) {}
virtual void draw() {
std::cout << "this is a circle, the radius is : " << m_radius << std::endl;
};
virtual double getArea() { return m_radius * m_radius * 3.14; }
private:
double m_radius;
};
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//rectangle.hpp
#ifndef RECTANGLE_HPP_
#define RECTANGLE_HPP_
#include "shape.hpp"
class Rectangle : public Shape {
public:
Rectangle(int a = 0, int b = 0) : m_height(a), m_width(b) {}
virtual void draw() {
std::cout << "this is a rectangle\n height: " << m_height
<< "\nwidth:" << m_width << std::endl;
}
virtual double getArea() { return m_height * m_width; }
private:
int m_height;
int m_width;
};
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
//main.cpp
#include "circle.hpp"
#include "factory.hpp"
#include "rectangle.hpp"
int main() {
std::shared_ptr<Factory> t = Factory::getInstance();
std::shared_ptr<Shape> c = t->getShape("circle");
c->draw();
std::cout << "the area is: " << c->getArea() << std::endl;
std::shared_ptr<Shape> r = t->getShape("rectangle");
r->draw();
std::cout << "the area is :" << r->getArea() << std::endl;
}

Comment and share

GRASP设计原则(职责分配原则)

GRASP(General responsibility assignment software Principle)设计原则是设计模式的基础,在GOF的23中设计模式中处处可以体现其中的一个或多个设计原则,所以在掌握设计模式之前需要对GRASP原则有一定的了解,本节我在这里总结一下grasp原则。

本文共分为以下几个内容:

  • GRASP的九个原则
  • GRASP原则详解
  • GRASP原则在23中设计模式中的体现

GRASP的九个原则

  • 信息专家原则(information)
  • 创造者原则(creator)
  • 低耦合原则(low coupling)
  • 高内聚原则(high cohesion)
  • 控制器原则(controller)
  • 多态原则(polymorphism)
  • 纯虚构(pure Fabrication)
  • 中介原则(indirect)
  • 受保护变量原则(protected Variations)

Grasp原则详解

信息专家原则(information expert)

信息专家模式的本质指的是我们应该将职责委托给哪一个对象,这个职责可以是一个方法,也可以是一个算法或者其他内容。它是面向过程设计过程中最基本的原则。

委托原则:我们在设计对象的时候,如果某个对象拥有完成某个职责所需要的所有信息,那么这个职责就分配给这个对象实现。这个时候,这个类就是相对于这个职责的信息专家。

示例:我们在设计购物网站的时候,为避免重复,一种商品只能在购物车中出现一次,如果多次出现,则需要将其数量增加。这个时候我们在将物品放入购物车的时候,要首先判断当前物品是否在购物车中,判断两个物品是否为同一个物品的方法这个职责应该委托给谁呢?显而易见,商品类中有唯一标识,所以这个职责由商品类实现,而不是购物车。

创造者原则(creator)

creator原则的本质是创建类对象职责应该委托给那个对象,也就是谁应该负责产生某个类的实例。

解决方案: 如果符合下面的一个或者多个条件,则可以将创建A的实例的职责分配给B;

  • B包含A
  • B聚合A
  • B拥有初始化A的数据并在创建A的实例时将数据传递给A
  • B记录A的实例
  • B频发使用A

满足上述一种或者多种情况的时候,我们应该奖创建A的实例的职责分配给B。

合理的creator原则带来的优点:如果职责分配合理,设计就能降低耦合,提高设计的清晰度,封装性和重用性。

示例:例如订单和商品的关系是聚合关系,这个时候我们将在订单中创建商品。

低耦合(Low coupling)

耦合是评价一个系统中各个元素之间连接或者依赖关系强弱的尺度。低耦合的原则是我们在设计系统的时候尽量降低系统中各个元素之间的耦合度,这样对于系统的理解和维护都有很大的益处。

耦合性高的系统会带来的坏处:

  • 一个类的修改导致其他类产生较大的影响;
  • 系统难以维护和理解;
  • 系统的重用性差,在重用一个高耦合类的时候,不得不重用它所依赖的所有类。

两个类具有以下特性中的其中一个,我们就说这两个类是耦合的:

  • A具有一个类型为B的属性;
  • A调用B的方法
  • A的方法包含对B的引用(参数或者返回值的方式)
  • A是B的直接或者间接的子类
  • A是接口B的一种实现

低耦合系统的设计方法

  • 在类的划分上,尽量创建松耦合的类,类之间的耦合性越低,越有利于复用,修改一个类不会影响其他类。
  • 在类的设计上,尽量降低类中成员和方法的访问权限。
  • 在类的设计上,尽量将类设计为不变类
  • 在类的引用上,将一个对象对另一个对象的引用降低到最小

高内聚(high cohesion)

内聚是评价一个对象的职责被关联的尺度或者强弱,也可以说是功能性内聚的职责。也就是功能性紧密的相关职责应该放在同一个类中,并共同完成有限的功能。这样做更加有利于对类的理解和重用,也可以降低类的维护成本。

往往低内聚的系统设计会导致类的混乱,当对功能进行扩展或者改进的时候带来不必要的麻烦,低内聚的类也不利于重用,因为他们的职责如此之混乱。

为了达到高内聚,我们需要对类的职责进行分解,使分解出来的类具有独立的职责,满足单一职责原则。将一些需要在多个类中使用到的方法封装到一个类中,其他的类只负责他们需要负责的相关功能,这样我们可以提高类的内聚程度。

控制器原则(controller)

控制器模式的实质是将一些系统事件的接受和处理委托给一个的对象controller,这个对象可以是一个类,系统或者子系统,它不与UI进行交互,它只负责系统信息的接收和处理。

一般情况下,控制器是一个系统,这个系统中包括多个处理器,分别对应处理不同的事务。通常情况下,一个控制器应当把要完成的功能委托给其他对象,而它只负责任务的协调控制和分配。

控制器原则与MVC模式相对应,MVC模式是一种比设计模式更高的架构模式。

多态原则(polymorphism)

多态原则与面向对象设计原则中的多态概念类似,这里不再详细赘述。

纯虚构(pure Fabrication)

纯虚构原则与我们所说的纯虚函数类似。

  纯虚构的作用是用来解决高内聚和低耦合之间的矛盾的。高内聚低耦合是我们系统设计的终极目标,高内聚意味着我们要将类拆分成多个功能集中的类,但是拆分的多个类之间需要进行协作才能正常工作,这样又增加了类之间的耦合性。

  纯虚构原则是用来解决上述问题的方案。它要求将一部分类的职责转移到纯虚构类中,在理想的情况下,分配给这种虚构类的职责是为了达到高内聚低耦合的效果。在实际的操作过程中,纯虚构类的实现又很多种方式,例如将数据库中操作的方法从数据库实体中分离出来,形成专门的数据访问类;通过对类的分解来实现类的重用,新增加的数据访问类对应于数据的持久化存储,这是软件开发过程中为了方便虚构出来的一个概念。一般情况下,纯虚构模式通常基于功能进行划分的。

中介模式(indirect)

中介模式的目的是为了避免两个对象之间产生直接耦合,降低对象之间的耦合度。

解决方案是建立中间对象来协调两个对象之间的交互,避免耦合性过高。

受保护模式(protected variations)

受保护模式的实质与OCP(开放闭合原则)类似,我们首先找到系统中不稳定的变化点,使用统一的接口封装起来,如果未来发生变化的时候,可以通过扩展接口来扩展新的功能,而不需要改变旧的代码。这样达到易于扩展的目的。

Comment and share

创建型设计模式之build模式

最近在读《设计模式-可复用面向对象软件设计的基础》一书,在阅读的过程中我会结合书中的相关知识和实例以及在网络上的博客对相关的模式的理解进行总结,并在此基础上加入自己的一些理解,总结模式中需要注意的一些点,记录在此博客,以供大家交流分享,同时防止自己对内容遗忘,如有不正确指出,欢迎批评指正。

本节的主要内容是设计模式中的创建型模式之一:builder模式

要用好builder模式,必须对其机制了解透彻,将该模式用在合适的软件中才能显出它真正的威力,第五部分中实例将为您展示它的真正威力。

本文内容分为以下几个方面:

  • builder模式的意图
  • builder模式的适用性
  • builder模式的结构(通用UML类图)及详解
  • builder模式中的参与者
  • builder模式中各个成分之间的交互
  • builder模式比较好的一个实例
  • builder模式使用过程中注意的点
  • KFC套餐实例代码

builder模式的精髓都隐藏于builder模式的意图和实用性中,让你真正理解这两个方面,可以说你已经掌握了builder模式(以下意图和模式都摘自《设计模式》书中)

builder模式的意图

builder模式的意图是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

builder模式的适用性

  • 创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
  • 当构造过程必须允许被构造的对象有不同的表示时。

提前记录一下:装配的工作是由导向器(director)完成的,复杂对象的创建是由具体的建造器完成(concreteBuilder),这里不明白没问题,等你看完全文再过来看这里你就理解了。

当且仅当上面两个条件均满足的情况下我们要使用builder模式(完全理解这两条不一件重要的事情,但是不要着急,当你读完这篇文章的时候就会拨开它的庐山真面目了)

builder模式的结构(UML类图)及详解

builder模式的UML类图关系如下:

注意的问题:

  • 在builder中一般不声明纯虚函数,而是把它们定义为空方法,这使客户只重定义他们感兴趣的操作。
  • 在director中,注意我们不是把所有的buildPart都去执行一次,而是根据需要的客户的需求,进行定制的去buildPart部分构建,可以构建一次,也可以构建多次。
  • 一般将m_product声明为protected成员,因为这样既保证了封装性,又能使得concreteBuilder能够正常操作product;

builder模式种的参与者

从上面的类图中我们也可以看出,在builder模式中的参与者有以下四种,以及他们的职责分别是:(此处为个人理解,与书中内容可能不一致,如果有问题欢迎指正)

  • Director: 负责装配product的各个部件,使用Builder的类方法进行实现。它的作用是隔离了客户与product的具体生产过程;并负责控制product的生产过程。
  • Builder: 为创建一个product对象的各个部件指定抽象接口。一般情况下默认builderPart的操作默认为空。
  • concreteBuilder: 具体实现每一个部件的具体的复杂生产过程,如buildPartA()的具体实现,并提供一个检索产品的接口。
  • Product: 这个争议不大,即为被构造的复杂对象。此类对象会有不同的表示。

builder模式各个成分之间的交互方式

先上一张时序图:

从上面的时序图可以分析出,各个成分之间的交互方式如下:

  • 客户首先创建一个concreteBuilder对象,然后创建一个Director,
  • 创建结束之后,使用concreteBuilder修饰对象Director,
  • 然后调用Director的construct()方法,进行构造Product,
  • 最后客户通过concreteBuilder的getResult()方法取回生成的Product

builder模式中一个较好的实例

两个实例来自网络对两本书籍实例的纠正,改编

上面这篇文章深刻的分析了builder模式的本质以及诸多误区的分析,收益颇多,在此感谢博主分享。

在本例中,设备(Equipment)是一个复杂对象,由一个machine和一个或多个输入端口(InputPort)或者输出端口(outputPort)组成;其中输入或输出端口可能有不同的类型(ordinary和super)。现在要你设计一个生成不同型号的产品,要求产品可能包含一进一出(普通或super),一进两出(普通或super)。

  在设计中,我们首先定义一个LCDFactory对象充当director,一个设备生成器(EQPBuilder),相当于Builder。

​ 首先ordinary和super是port的内部实现方式不同,所以我们需要定义两个具体类,即 ordinaryEQPBuilder和superEQPBuilder。

​ 在EQPbuilder中,我们将定义四个函数,分别是:buildMachine(), addInputPort(), addOutputPort()和getEQP()。两个concrete类继承EQPBuilder。

​ 当我们需要获取不同数目的port的设备,这属于组装方面的范畴,所以我们将在LCDFactory中的createEQP中做。其UML类图如下:

builder模式在使用的过程中需要注意的问题

这里所说的注意的问题可能上面已经提到了,但是这里还是要着重强调一下,因为我们稍不注意,就可能将模式滥用,导致系统设计的失败。

  • 在builder模式中Builder一定不要定义纯虚函数成员函数,而是定义为空方法的虚函数,这样就可以使得客户可以只定义他们感兴趣的操作。
  • Director负责装配产品,concreteBuilder负责实现复杂产品部件的具体实现
  • 在Director中调用Builder的buildPart函数的时候,并不是每个函数都被调用,而是根据要生成的product对其选择性调用,可能调用零次,也可能调用多次。
  • Builder中的m_product声明为protected,即保证对象的封装性,又能让具体类方便的使用。(这是我在写代码的时候的解决方法,如果有更好的将m_product声明为private的解决方法欢迎交流2824759538@qq.com)

KFC套餐实例代码

这是本人使用KFC服务员生成套餐时的一种模拟,其实实际情况中并不需要使用builder模式,但是这里为了练手强行写成了builder模式,希望大家不要吐槽。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//KFCWaiter.hpp 注意此处water相当于Director的作用
#ifndef KFCWAITER_HPP_
#define KFCWAITER_HPP_
#include "MealBuilder.hpp"
class KFCWaiter {
public:
void setMealBuilder(std::shared_ptr<MealBuilder> t_builder);
void construct();
private:
std::shared_ptr<MealBuilder> m_builder;
};
#endif
1
2
3
4
5
6
7
8
9
//KFCWaiter.cpp
#include "KFCWaiter.hpp"
void KFCWaiter::setMealBuilder(std::shared_ptr<MealBuilder> t_builder) {
m_builder = t_builder;
}
void KFCWaiter::construct() {
m_builder->buildFood();
m_builder->buildDrink();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Meal.hpp Meal相当于Product
#ifndef MEAL_HPP
#define MEAL_HPP
#include <iostream>
#include <string>
class Meal {
public:
Meal(std::string t_food = "hanbao", std::string t_drink = "kele");
~Meal();
std::string getFood() const;
std::string getDrink() const;
void getMeal() const;
void buildFood(const std::string &t_food);
void buildDrink(const std::string &t_drink);
private:
std::string m_food;
std::string m_drink;
};
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Meal.cpp
#include "Meal.hpp"
Meal::Meal(std::string t_food, std::string t_drink)
: m_food(t_food), m_drink(t_drink) {}
Meal::~Meal() {}
std::string Meal::getFood() const { return m_food; }
std::string Meal::getDrink() const { return m_drink; }
void Meal::getMeal() const {
std::cout << "套餐为:" << std::endl
<< "食物:" << m_food << std::endl
<< "饮料:" << m_drink << std::endl;
}
void Meal::buildFood(const std::string &t_food) { m_food = t_food; }
void Meal::buildDrink(const std::string &t_drink) { m_drink = t_drink; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//MealBuilder.hpp
#ifndef BUILDER_HPP
#define BUILDER_HPP
#include "Meal.hpp"
#include <memory>
#include <string>
class MealBuilder {
public:
MealBuilder();
virtual void buildFood() = 0;
virtual void buildDrink() = 0;
std::shared_ptr<Meal> getResult();
virtual ~MealBuilder(){};
protected:
std::shared_ptr<Meal> m_meal;
};
#endif
1
2
3
4
//MealBuilder.cpp
#include "MealBuilder.hpp"
MealBuilder::MealBuilder() : m_meal(std::shared_ptr<Meal>(new Meal())) {}
std::shared_ptr<Meal> MealBuilder::getResult() { return m_meal; }
1
2
3
4
5
6
7
8
9
10
//MealBuilderA.hpp
#ifndef MEALBUILDERA_HPP_
#define MEALBUILDERA_HPP_
#include "MealBuilder.hpp"
class MealBuilderA : public MealBuilder {
public:
virtual void buildFood();
virtual void buildDrink();
};
#endif
1
2
3
4
5
//MealBuilderA.cpp
#include "MealBuilderA.hpp"
void MealBuilderA::buildFood() { m_meal->buildFood("套餐A食物"); }
void MealBuilderA::buildDrink() { m_meal->buildDrink("套餐A饮料"); }
1
2
3
4
5
6
7
8
9
10
//MealBuilderB.hpp
#ifndef MEALBUILDERB_HPP_
#define MEALBUILDERB_HPP_
#include "MealBuilder.hpp"
class MealBuilderB : public MealBuilder {
public:
virtual void buildFood();
virtual void buildDrink();
};
#endif
1
2
3
4
5
//MealBuilderB.cpp
#include "MealBuilderB.hpp"
void MealBuilderB::buildFood() { m_meal->buildFood("套餐B食物"); }
void MealBuilderB::buildDrink() { m_meal->buildDrink("套餐B饮料"); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//main.cpp
#include "KFCWaiter.hpp"
#include "MealBuilderA.hpp"
#include "MealBuilderB.hpp"
int main() {
std::shared_ptr<MealBuilder> Aptr =
std::shared_ptr<MealBuilder>(new MealBuilderA());
KFCWaiter k;
k.setMealBuilder(Aptr);
k.construct();
std::shared_ptr<Meal> current_meal = Aptr->getResult();
current_meal->getMeal();
}

Comment and share

UML时序图详解

  这几天在阅读《设计模式》一书,书中总结了前人在面向对象软件设计过程中针对特定的问题提出的简洁而优雅的解决方案。在阅读的过程中,我一边理解书中给出的实例,一边结合自己写过的项目或者用过的框架与当前模式进行对比,发现之前的项目有一些还是有很大的改进空间。在阅读本书的过程中,书中很多例子都是通过UML类图和UML时序图进行说明,所以,理解这些例子的前提是对UML类图以及UML时序图有一定的了解,UML类图在前面的博客(https://langzi989.github.io/2017/01/05/UML%E5%9B%BE%E8%A1%A8%E7%A4%BA%E5%B8%B8%E8%A7%81%E7%9A%84%E7%B1%BB%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/)中已经讲解,现在我们主要来看一下UML时序图中的重要概念。

  UML类关系以及时序图在大三上《系统分析与设计》课的时候学过,但是那个时候不知道这东西有什么用,所以没有认真学和理解。随着实战经验的增多,越来越能体会到这些在软件设计的过程中重中之重,不管是描述你的设计思想还是与团队中其他人进行交流。所以还是建议大家在学到这一部分的时候对这门课认真对待。

定义

时序图是一种按照时间顺序显示对象之间的交互的图。时序图中显示的是参与交互的对象和对象之间消息传递的顺序。

基本元素

时序图中包含的主要的元素有:

  • 角色(Actor)
  • 对象(Object)
  • 生命线(life line)
  • 控制焦点(focus of control)
  • 消息(message)
  • 自关联消息(self-associated message)
  • 组合片段(combined fragment)

基本元素详解

角色(actor)

角色指的是系统角色,这个角色可以是人,其他系统或者子系统

图形表示:

对象(object)

对象包括三种:

  • 指定类名和对象名的对象objectName: className
  • 指定类名但没有对象名的对象(匿名对象) :className
  • 指定对象名但不指定类名的对象 objectName

图形表示:
第一类:

第二类:

第三类:

生命线(life line)

生命线在时序图中使用向下的虚线表示,它指的是其所对应的对象的存活时间。从上到下表示时间的推后。

图像表示:

控制焦点(focus of control)

控制焦点在时序图中以方块的形式出现,它是一种表示时间段的方式,对象在这一时间段中执行相应的操作。

图像表示:

消息(message)

消息有三种类型

  • 同步消息:发送者发送请求后被阻塞,直到接受者返回消息或者做完其他处理之后发送者才能继续执行之后的操作
  • 异步消息:发送者发送消息给接受者之后不等待接受者返回响应而继续执行下面的操作。
  • 返回消息:接受者返回消息给发送者

消息的图形表示:
同步消息:

异步消息:

返回消息:

自关联消息(self-associated message)

自关联消息一般指的是当前类调用自己的函数的情况

组合片段(combined fragment)

组合片段中包括四个部分:

  • Alternative fragment(alt表示):与if…then…else…对应

  • option fragment(opt表示):与switch对应

  • parallel fragment(par表示):表示同时发生

  • Loop Fragment(loop表示):与循环对应

    图形表示:

    alt:

Comment and share

UML图表示常见的类之间的关系

类之间的关系常见的主要包括以下几种:

  • 继承
  • 实现
  • 依赖
  • 关联
  • 聚合
  • 组合

继承(inheritance)

继承的概念大家都比较熟悉,他主要指的是派生类继承父类或者接口之间的继承,派生类继承了父类中原有的功能,并在此基础上添加了新的功能。例如public继承实质上是一种is-a的关系,(子类is a 基类)

图形表示:

实现(implement)

实现指的是一个类实现一个接口,一个类可以实现一个或者多个接口。实现是类和接口之间常见的一种关系。在java中通常通过implements实现。

图形表示:

依赖

依赖指的是一个类A使用另外一个类B,这种该使用关系是偶然性的,临时的,非常弱的一种关系。但是类B的变化会影响到类A;比如人要使用船过河中,这个时候人和船就是一种依赖关系。具体表现在代码中就是B是类A中的某个方法的参数。

图形表示:

关联

关联是指俩个类之间的或者类和接口之间的一种强依赖关系。而这种强依赖关系不是偶然性的,而是长期的,而且双方的关系是平等的,关联可以是单向的,也可以是双向的。具体表现在代码中是(1):B以类属性的方式出现在类A中(2)A引用类型为B的全局变量

图形表示:

聚合

聚合是关联关系的一种特例,它表现的是一种has-a的关系,即整体与部分的关系,此时整体与部分可以是分离的,他们可以具有各自的声明周期,部分可以属于多个整体对象,也可以被多个整体对象共享;如计算机与CPU的关系聚合中部分和整体都有各自的生命周期,并且互相影响

图形表示:

组合

组合也是关联的一种特例,它体现的是一种contain-a的关系,这种关系比聚合强;这个时候整体和部分是不可分离的,整体的声明周期与部分的声明周期相同,当整体的周期结束时,部分的周期也随之结束。如人和大脑的关系

图形表示:

依赖性强弱:

组合>聚合>关联>依赖

Comment and share

docker防止fork炸弹

在开发过程中,我们使用了docker作为容器来进行编程语言代码的评测,以用于防止有害代码破坏宿主物理机器,但是在昨天发现docker还是会受fork炸弹的影响将宿主物理机器挂掉。针对这个问题,我在docker官网找到了相关的解决方法,记录分享并以备之后再次遇到此类问题可以很快找到解决方案。
当前解决方案只适用于docker1.1版本及之后(通过docker.io进行安装,sudo apt install docker.io)。
解决方案的思想就是:在启动docker的时候给其加上启动选项–pids-limit来限制cgroup中的fork的进程的最大数,我这里限制的30,可以根据自己的需求去限制。

1
docker run -it --pids-limit 30 Ubuntu:14.04

上述方法即可防止fork炸弹。
以下内容转载自http://blog.csdn.net/thinkhy/article/details/50995720

Docker容器与安全

Docker能否大规模用于生产环境,尤其是公有云环境,就在于Docker是否能提供安全的环境。本文将总结《Docker容器与容器云》一书3.9节『Docker与容器安全』的主要内容,包括Docker现有安全机制、存在的安全问题以及Docker安全增强三个方面。

1. Docker的安全机制

1.1 Docker daemon安全

  • Docker向外界服务提供了四种通信方式,默认是以Unix域套接字的方式来与客户端通信,这种方式较TCP形式更为安全。
  • Docker也提供了TLS传输层安全协议,通过–tlsverify(安全传输校验),–tlscacert(信任证书)、–tlskey(服务器或者客户端秘钥)、–tlscert(证书位置)来配置。

    1.2 镜像安全

  • Docker registry镜像库访问控制

    • Docker daemon第一次启动时,通过公网(Amazon CDN)载入official.json包,饮食公共image和用户image的目录以及数字签名信息。
    • official.json在Docker daemon启动时加载到MemoryGraph,MemoryGraph用于存储公钥以及命名之间的授权映射,默认授权节点对授权空间有读写权限。
  • 镜像校验和

    • 镜像校验和用来保证镜像的完整性,以预防可能出现的镜像破环。
    • 目前Docker对于镜像校验和和验证失败不采取任何措施,仅输出警告信息

      1.3 内核安全

  • 内核为容器提供了两种技术cgroup和namespace,分别对容器进行资源限制和资源隔离。
    • 容器本质是进程,cgroup用来限制容器的资源使用量,避免单个容器耗尽系统资源。
  • namespace用来隔离容器与宿主机,以及不同的容器。
    • Docker目前仅完整支持uts、ipc、pid、network、mount这5种ns,user ns尚未完全支持。
    • 系统资源未进行隔离,如/proc,/sys、SELinux、time、syslog、/dev设备信息等均未进行隔离。

      1.4 容器之间的网络安全

  • Docker可通过iptabls设定规则实现禁止或允许容器之间的通信。

    1.5 Docker容器Capability限制

  • 容器的行为通过Linux超级用户分组限制,具体包括了CHOWN、DAC_OVERRIDe、FSETID、FOWNER、MKNOD、NET_RAW、SETGID、SETUID、SETFCAP、SETPCAP、NET_BIND_SERVICE、SYS_CHROOT、KILL和AUDIT_WRITE。
  • Docker进程的Capability可通过docker run命令的参数进行配置

    2. Docker安全问题

    2.1 磁盘资源限制问题

  • Docker容器通过镜像层叠的方式来构建容器内的文件系统,本质上还是在宿主机文件系统的目录(/var/lib/docker)下存储文件。
  • 极有可能出现一个容器将宿主机上所有的磁盘空间耗尽,导致其它容器无法存储文件,所以有必要对容器的磁盘使用量进行限制。

    2.2 容器逃逸问题

  • Docker使用操作系统进行虚拟化,共享内核、内存、CPU以及磁盘,易造成容器逃逸问题。
  • Docker1.0之后采用白名单来限制容器的能力,会给出默认的容器Capability清单,禁止容器拥有清单之外的Capability。

    2.3 容器DoS攻击与流量限制问题

  • 公有云基于虚拟化技术实现,攻击数据包可能不需要通过物理网卡就可以攻击同一个宿主机下的其他容器,传统Dos预防措施无法适用容器之间的攻击。
  • 的Docker容器连接在网桥上,通过veth pari技术创建网卡,其一端在容器内命名为eth0,另一张网上驻留在宿主机环境之中。
  • 同一宿主机下所有容器共用一张物理网卡,如果一个容器抢占大部分带宽,会影响其它容器使用。

    2.4 超级权限问题

  • docker run时加入–privileged参数能使容器获得所有的超级用户权限能力,并将所有的宿主机的所有设备挂载到容器内。

    3. Docker安全的解决方案

    3.1 SELinux

  • SELinux三种控制方式
    • Type Enforcement: 主要的访问控制机制。
    • Role-Based Access Control(RBAC):基于SELinux用户的权限控制手段。
    • Multi-Level Security(MLS): 多级分类安全,指定level标签。
  • 为什么要在Docker中使用SELinux
    • SELinux将所有进程和文件打上标签,而容器以进程方式运行,所以控制进程如何访问资源,也就是限制容器如何去访问资源。
    • SELinux策略是全局的,它不是针对具体用户设定,而是强制整个系统遵循。
    • 减少提权攻击风险。

      3.2 user namespace

  • 容器的超级用户权限通过ns映射到宿主机是一个普通用户。
  • 容器被恶意程序攻击,所做的也就是这个普通用户的权限,而非宿主机的超级权限。

    3.3 磁盘限额

  • Docker仅对Device Mapper文件系统的限额提供了–storage-opt参数进行限制。
  • cgroup没有对磁盘进行限制,Linux磁盘限额技术主要基于用户和文件系统。
  • 可能的解决方案
    • 所有用户共有宿主机的一块磁盘,限制用户在磁盘上的使用量来限定容器的磁盘使用量。
    • 选择支持目录限额的文件系统,如XFS。
    • Docker定期检查每一个容器磁盘使用量,会对性能造成影响。
    • 创建虚拟文件系统,些文件系统仅供某一个容器使用。

      3.4 容器流量限制

  • Docker没对容器的网络带宽做限制。
  • 可以采用Traffic Controller容器对容器网卡流量进行限制,一定程序上减少容器Dos攻击危害。

    3.5 GRSecurity内核安全增强工具

  • Docker容器共享宿主机的内存,在内存安全上存在不少问题,需要针对内存破坏做防御。
  • GRSecurity是一个对内核的安全扩展,通过智能访问控制来阻止内存破坏,预防0day漏洞。

    3.6 fork炸弹

  • fork炸弹以极快速度创建大量进程,以此消耗系统资源,使系统无法运行新程序,现有进程运行速度放缓。
  • 容器本身在内核层面隔离性不足,fork bomb会给容器带来灾难性影响。
  • fork bomb受到社区关注(Issue 6479),但目前还没有完美解决方案。
  • Docker无法使用ulimit来限制forkbomb问题,因为一个宿主机用户可能同时启动多个容器,无法对每个容器做进程数的限制。
  1. 总结
    Docker自身已经提供了不少安全机制,但Docker目前仍然只适于运行可信应用程序(内部使用),如果需要运行任意代码,安全很难得到保证。在日常应用中,还可以通过SELinux、GRSecurity、seccomp等工具来增强容器安全。

Comment and share

Linux time命令详解

参考:http://blog.he96.com/2011/01/linux-timewhat-do-real-user-and-sys.html

real,user,sys

我们常用linux中的time命令计算某个程序的运行耗时,用户态CPU耗时,系统态COU耗时。
例如:

1
2
3
4
time foo
real 0m0.020s
user 0m0.020s
sys 0m0.000s

现在我们探讨一下上述三个时间分别代表的含义:

  • real: 表示a.out程序的实际的运行耗时,也就是在a.out运行开始时刻你看了一下手表,a.out运行结束时刻看了一眼手表,两次时间的差值就是real的值。例如使用time sleep 2的时候real值为2
  • user: a.out运行在用户态CPU的时间
  • sys: a.out运行在核心态CPU的时间

用户态和核心态

然后我们讲一下用户态和核心态:

  • 核心态(kernal mode): 在内核态,代码拥有完全的,不受任何限制的访问底层硬件的能力。可以执行任意的CPU指令,访问任意的内存地址。内核态通常情况下,都是为哪些最底层的,由操作系统提供的,可信可靠耳朵代码来运行的。内核态崩溃将是灾难性的,它会影响到整个系统。
  • 用户态(User mode):在用户态,代码不具备直接访问底层硬件或者内存的能力,而必须借助操作系统提供的可靠的,底层的API来访问硬件或者内存。由于这种隔离带来的保护作用,用户态代码崩溃,操作系统可以正常恢复。我们大多数代码是运行在用户态。

区分内核态和用户态的作用:隔离保护,使得系统更稳定。
使用这三个时间我们可以计算当前程序CPU的使用率:
CPU Usage = (user + sys) / real_time

常见误区

  • realtime = user + sys
  • realtime > user + sys
    由于有一些程序需要等待I/O等导致realtime与user+sys不相等
    当在多核CPU的情况下第二种情况不成立

Comment and share

魏传柳(2824759538@qq.com)

author.bio


Tencent


ShenZhen,China