2021年5月

软件库是重复使用代码的一种简单而合理的方式。

 title=

软件库是一种是一直以来长期存在的、简单合理的复用代码的方式。这篇文章解释了如何从头开始构建库并使得其可用。尽管这两个示例库都以 Linux 为例,但创建、发布和使用这些库的步骤也可以应用于其它类 Unix 系统。

这些示例库使用 C 语言编写,非常适合该任务。Linux 内核大部分由 C 语言和少量汇编语言编写(Windows 和 Linux 的表亲如 macOS 也是如此)。用于输入/输出、网络、字符串处理、数学、安全、数据编码等的标准系统库等主要由 C 语言编写。所以使用 C 语言编写库就是使用 Linux 的原生语言来编写。除此之外,C 语言的性能也在一众高级语言中鹤立鸡群。

还有两个来访问这些库的示例 客户程序 client (一个使用 C,另一个使用 Python)。毫无疑问可以使用 C 语言客户程序来访问 C 语言编写的库,但是 Python 客户程序示例说明了一个由 C 语言编写的库也可以服务于其他编程语言。

静态库和动态库对比

Linux 系统存在两种类型库:

  • 静态库(也被称为归档库):在编译过程中的链接阶段,静态库会被编译进程序(例如 C 或 Rust)中。每个客户程序都有属于自己的一份库的拷贝。静态库有一个显而易见的缺点 —— 当库需要进行一定改动时(例如修复一个 bug),静态库必须重新链接一次。接下来要介绍的动态库避免了这一缺点。
  • 动态库(也被称为共享库):动态库首先会在程序编译中的链接阶段被标记,但是客户程序和库代码在运行之前仍然没有联系,且库代码不会进入到客户程序中。系统的动态加载器会把一个共享库和正在运行的客户程序进行连接,无论该客户程序是由静态编译语言(如 C)编写,还是由动态解释语言(如 Python)编写。因此,动态库不需要麻烦客户程序便可以进行更新。最后,多个客户程序可以共享同一个动态库的单一副本。

通常来说,动态库优于静态库,尽管其复杂性较高而性能较低。下面是两种类型的库如何创建和发布:

  1. 库的源代码会被编译成一个或多个目标模块,目标模块是二进制文件,可以被包含在库中并且链接到可执行的二进制中。
  2. 目标模块会会被打包成一个文件。对于静态库,标准的文件拓展名是 .a 意为“ 归档 archive ”;对于动态库,标准的文件拓展名是 .so 意为“ 共享目标 shared object ”。对于这两个相同功能的示例库,分别发布为 libprimes.a (静态库)和 libshprimes.so (动态库)。两种库的文件名都使用前缀 lib 进行标识。
  3. 库文件被复制到标准目录下,使得客户程序可以轻松地访问到库。无论是静态库还是动态库,典型的位置是 /usr/lib 或者 /usr/local/lib,当然其他位置也是可以的。

构建和发布每种库的具体步骤会在下面详细介绍。首先我将介绍两种库里涉及到的 C 函数。

示例库函数

这两个示例库都是由五个相同的 C 函数构建而成的,其中四个函数可供客户程序使用。第五个函数是其他四个函数的一个工具函数,它显示了 C 语言怎么隐藏信息。每个函数的源代码都很短,可以将这些函数放在单个源文件中,尽管也可以放在多个源文件中(如四个公布的函数都有一个文件)。

这些库函数是基本的处理函数,以多种方式来处理质数。所有的函数接收无符号(即非负)整数值作为参数:

  • is_prime 函数测试其单个参数是否为质数。
  • are_coprimes 函数检查了其两个参数的 最大公约数 greatest common divisor (gcd)是否为 1,即是否为互质数。
  • prime_factors:函数列出其参数的质因数。
  • glodbach:函数接收一个大于等于 4 的偶数,列出其可以分解为两个质数的和。它也许存在多个符合条件的数对。该函数是以 18 世纪数学家 克里斯蒂安·哥德巴赫 Christian Goldbach 命名的,他的猜想是任意一个大于 2 的偶数可以分解为两个质数之和,这依旧是数论里最古老的未被解决的问题。

工具函数 gcd 留在已部署的库文件中,但是在没有包含这个函数的文件无法访问此函数。因此,一个使用库的客户程序无法调用 gcd 函数。仔细观察 C 函数可以明白这一点。

更多关于 C 函数的内容

每个在 C 语言中的函数都有一个存储类,它决定了函数的范围。对于函数,有两种选择。

  • 函数默认的存储类是 extern,它给了函数一个全局域。一个客户程序可以调用在示例库中用 extern 修饰的任意函数。下面是一个带有显式 extern 声明的 are_coprimes 函数定义:
extern unsigned are_coprimes(unsigned n1, unsigned n2) {
  ...
}
  • 存储类 static 将一个函数的的范围限制到函数被定义的文件中。在示例库中,工具函数 gcd 是静态的(static):
static unsigned gcd(unsigned n1, unsigned n2) {
  ...
}

只有在 primes.c 文件中的函数可以调用 gcd,而只有 are_coprimes 函数会调用它。当静态库和动态库被构建和发布后,其他的程序可以调用外部的(extern)函数,如 are_coprimes ,但是不可以调用静态(static)函数 gcd。静态(static)存储类通过将函数范围限制在其他库函数内,进而实现了对库的客户程序隐藏 gcd 函数。

primes.c 文件中除了 gcd 函数外,其他函数并没有指明存储类,默认将会设置为外部的(extern)。然而,在库中显式注明 extern 更加常见。

C 语言区分了函数的 定义 definition 声明 declaration ,这对库来说很重要。接下来让我们开始了解定义。C 语言仅允许命名函数不允许匿名函数,并且每个函数需要定义以下内容:

  • 一个唯一的名字。一个程序不允许存在两个同名的函数。
  • 一个可以为空的参数列表。参数需要指明类型。
  • 一个返回值类型(例如:int 代表 32 位有符号整数),当没有返回值时设置为空类型(void)。
  • 用一对花括号包围起来的函数主体部分。在一个特制的示例中,函数主体部分可以为空。

程序中的每个函数必须要被定义一次。

下面是库函数 are_coprimes 的完整定义:

extern unsigned are_coprimes(unsigned n1, unsigned n2) { /* 定义 */
  return 1 == gcd(n1, n2); /* 最大公约数是否为 1? */
}

函数返回一个布尔值(0 代表假,1 代表真),取决于两个整数参数值的最大公约数是否为 1。工具函数 gcd 计算两个整数参数 n1n2 的最大公约数。

函数声明不同于定义,其不需要主体部分:

extern unsigned are_coprimes(unsigned n1, unsigned n2); /* 声明 */

声明在参数列表后用一个分号代表结束,它没有被花括号包围起来的主体部分。程序中的函数可以被多次声明。

为什么需要声明?在 C 语言中,一个被调用的函数必须对其调用者可见。有多种方式可以提供这样的可见性,具体依赖于编译器如何实现。一个必然可行的方式就是当它们二者位于同一个文件中时,将被调用的函数定义在在它的调用者之前。

void f() {...}     /* f 定义在其被调用前 */
void g() { f(); }  /* ok */

当函数 f 被在调用前声明,此时函数 f 的定义可以移动到函数 g 的下方。

void f();         /* 声明使得函数 f 对调用者可见 */
void g() { f(); } /* ok */
void f() {...}    /* 相较于前一种方式,此方式显得更简洁 */

但是当如果一个被调用的函数和调用它的函数不在同一个文件中时呢?因为前文提到一个函数在一个程序中需要被定义一次,那么如何使得让一个文件中被定义的函数在另一个文件中可见?

这个问题会影响库,无论是静态库还是动态库。例如在这两个质数库中函数被定义在源文件 primes.c 中,每个库中都有该函数的二进制副本,但是这些定义的函数必须要对使用库的 C 程序可见,该 C 程序有其自身的源文件。

函数声明可以帮助提供跨文件的可见性。对于上述的“质数”例子,它有一个名为 primes.h 的头文件,其声明了四个函数使得它们对使用库的 C 程序可见。

/** 头文件 primes.h:函数声明 **/
extern unsigned is_prime(unsigned);
extern void prime_factors(unsigned);
extern unsigned are_coprimes(unsigned, unsigned);
extern void goldbach(unsigned);

这些声明通过为每个函数指定其调用语法来作为接口。

为了客户程序的便利性,头文件 primes.h 应该存储在 C 编译器查找路径下的目录中。典型的位置有 /usr/include/usr/local/include。一个 C 语言客户程序应使用 #include 包含这个头文件,并尽可能将这条语句其程序源代码的首部(头文件将会被导入另一个源文件的“头”部)。C 语言头文件可以被导入其他语言(如 Rust 语言)中的 bindgen,使其它语言的客户程序可以访问 C 语言的库。

总之,一个库函数只可以被定义一次,但可以在任何需要它的地方进行声明,任一使用 C 语言库的程序都需要该声明。头文件可以包含函数声明,但不能包含函数定义。如果头文件包含了函数定义,那么该文件可能会在一个 C 语言程序中被多次包含,从而破坏了一个函数在 C 语言程序中必须被精确定义一次的规则。

库的源代码

下面是两个库的源代码。这部分代码、头文件、以及两个示例客户程序都可以在 我的网页 上找到。

#include <stdio.h>
#include <math.h>

extern unsigned is_prime(unsigned n) {
  if (n <= 3) return n > 1;                   /* 2 和 3 是质数 */
  if (0 == (n % 2) || 0 == (n % 3)) return 0; /* 2 和 3 的倍数不会是质数 */

  /* 检查 n 是否是其他 < n 的值的倍数 */
  unsigned i;
  for (i = 5; (i * i) <= n; i += 6)
    if (0 == (n % i) || 0 == (n % (i + 2))) return 0; /* 不是质数 */

  return 1; /* 一个不是 2 和 3 的质数 */
}

extern void prime_factors(unsigned n) {
  /* 在数字 n 的质因数分解中列出所有 2 */
  while (0 == (n % 2)) {  
    printf("%i ", 2);
    n /= 2;
  }

  /* 数字 2 已经处理完成,下面处理奇数 */
  unsigned i;
  for (i = 3; i <= sqrt(n); i += 2) {
    while (0 == (n % i)) {
      printf("%i ", i);
      n /= i;
    }
  }

  /* 还有其他质因数?*/
  if (n > 2) printf("%i", n);
}

/* 工具函数:计算最大公约数 */
static unsigned gcd(unsigned n1, unsigned n2) {
  while (n1 != 0) {
    unsigned n3 = n1;
    n1 = n2 % n1;
    n2 = n3;
  }
  return n2;
}

extern unsigned are_coprimes(unsigned n1, unsigned n2) {
  return 1 == gcd(n1, n2);
}

extern void goldbach(unsigned n) {
  /* 输入错误 */
  if ((n <= 2) || ((n & 0x01) > 0)) {
    printf("Number must be > 2 and even: %i is not.\n", n);
    return;
  }

  /* 两个简单的例子:4 和 6 */
  if ((4 == n) || (6 == n)) {
    printf("%i = %i + %i\n", n, n / 2, n / 2);
    return;
  }
 
  /* 当 n > 8 时,存在多种可能性 */
  unsigned i;
  for (i = 3; i < (n / 2); i++) {
    if (is_prime(i) && is_prime(n - i)) {
      printf("%i = %i + %i\n", n, i, n - i);
      /* 如果只需要一对,那么用 break 语句替换这句 */
    }
  }
}

库函数

这些函数可以被库利用。两个库可以从相同的源代码中获得,同时头文件 primes.h 是两个库的 C 语言接口。

构建库

静态库和动态库在构建和发布的步骤上有一些细节的不同。静态库需要三个步骤,而动态库需要增加两个步骤即一共五个步骤。额外的步骤表明了动态库的动态方法具有更多的灵活性。让我们先从静态库开始。

库的源文件 primes.c 被编译成一个目标模块。下面是命令,百分号 % 代表系统提示符,两个井字符 # 是我的注释。

% gcc -c primes.c ## 步骤1(静态)

这一步生成目标模块是二进制文件 primes.o-c 标志意味着只编译。

下一步是使用 Linux 的 ar 命令将目标对象归档。

% ar -cvq libprimes.a primes.o ## 步骤2(静态)

-cvq 三个标识分别是“创建”、“详细的”、“快速添加”(以防新文件没有添加到归档中)的简称。回忆一下,前文提到过前缀 lib 是必须的,而库名是任意的。当然,库的文件名必须是唯一的,以避免冲突。

归档已经准备好要被发布:

% sudo cp libprimes.a /usr/local/lib ## 步骤3(静态)

现在静态库对接下来的客户程序是可见的,示例在后面。(包含 sudo 可以确保有访问权限将文件复制进 /usr/local/lib 目录中)

动态库还需要一个或多个对象模块进行打包:

% gcc primes.c -c -fpic ## 步骤1(动态)

增加的选项 -fpic 指示编译器生成与位置无关的代码,这意味着不需要将该二进制模块加载到一个固定的内存位置。在一个拥有多个动态库的系统中这种灵活性是至关重要的。生成的对象模块会略大于静态库生成的对象模块。

下面是从对象模块创建单个库文件的命令:

% gcc -shared -Wl,-soname,libshprimes.so -o libshprimes.so.1 primes.o ## 步骤2(动态)

选项 -shared 表明了该库是一个共享的(动态的)而不是静态的。-Wl 选项引入了一系列编译器选项,第一个便是设置动态库的 -soname,这是必须设置的。soname 首先指定了库的逻辑名字(libshprimes.so),接下来的 -o 选项指明了库的物理文件名字(libshprimes.so.1)。这样做的目的是为了保持逻辑名不变的同时允许物理名随着新版本而发生变化。在本例中,在物理文件名 libshprimes.so.1 中最后的 1 代表是第一个库的版本。尽管逻辑文件名和物理文件名可以是相同的,但是最佳做法是将它们命名为不同的名字。一个客户程序将会通过逻辑名(本例中为 libshprimes.so)来访问库,稍后我会进一步解释。

接下来的一步是通过复制共享库到合适的目录下使得客户程序容易访问,例如 /usr/local/lib 目录:

% sudo cp libshprimes.so.1 /usr/local/lib ## 步骤3(动态)

现在在共享库的逻辑名(libshprimes.so)和它的物理文件名(/usr/local/lib/libshprimes.so.1)之间设置一个符号链接。最简单的方式是将 /usr/local/lib 作为工作目录,在该目录下输入命令:

% sudo ln --symbolic libshprimes.so.1 libshprimes.so ## 步骤4(动态)

逻辑名 libshprimes.so 不应该改变,但是符号链接的目标(libshrimes.so.1)可以根据需要进行更新,新的库实现可以是修复了 bug,提高性能等。

最后一步(一个预防措施)是调用 ldconfig 工具来配置系统的动态加载器。这个配置保证了加载器能够找到新发布的库。

% sudo ldconfig ## 步骤5(动态)

到现在,动态库已为包括下面的两个在内的示例客户程序准备就绪了。

一个使用库的 C 程序

这个示例 C 程序是一个测试程序,它的源代码以两条 #include 指令开始:

#include <stdio.h>  /* 标准输入/输出函数 */
#include <primes.h> /* 我的库函数 */

文件名两边的尖括号表示可以在编译器的搜索路径中找到这些头文件(对于 primes.h 文件来说在 /usr/local/inlcude 目录下)。如果不包含 #include,编译器会抱怨缺少 is_primeprime_factors 等函数的声明,它们在两个库中都有发布。顺便提一句,测试程序的源代码不需要更改即可测试两个库中的每一个库。

相比之下,库的源文件(primes.c)使用 #include 指令打开以下头文件:

#include <stdio.h>
#include <math.h>

math.h 头文件是必须的,因为库函数 prime_factors 会调用数学函数 sqrt,其在标准库 libm.so 中。

作为参考,这是测试库程序的源代码:

#include <stdio.h>
#include <primes.h>

int main() {
  /* 是质数 */
  printf("\nis_prime\n");
  unsigned i, count = 0, n = 1000;
  for (i = 1; i <= n; i++) {
    if (is_prime(i)) {
      count++;
      if (1 == (i % 100)) printf("Sample prime ending in 1: %i\n", i);
    }
  }
  printf("%i primes in range of 1 to a thousand.\n", count);

  /* prime_factors */
  printf("\nprime_factors\n");
  printf("prime factors of 12: ");
  prime_factors(12);
  printf("\n");
 
  printf("prime factors of 13: ");
  prime_factors(13);
  printf("\n");
 
  printf("prime factors of 876,512,779: ");
  prime_factors(876512779);
  printf("\n");

  /* 是合数 */
  printf("\nare_coprime\n");
  printf("Are %i and %i coprime? %s\n",
         21, 22, are_coprimes(21, 22) ? "yes" : "no");
  printf("Are %i and %i coprime? %s\n",
         21, 24, are_coprimes(21, 24) ? "yes" : "no");

  /* 哥德巴赫 */
  printf("\ngoldbach\n");
  goldbach(11);    /* error */
  goldbach(4);     /* small one */
  goldbach(6);     /* another */
  for (i = 100; i <= 150; i += 2) goldbach(i);

  return 0;
}

测试程序

在编译 tester.c 文件到可执行文件时,难处理的部分时链接选项的顺序。回想前文中提到两个示例库都是用 lib 作为前缀开始,并且每一个都有一个常规的拓展后缀:.a 代表静态库 libprimes.a.so 代表动态库 libshprimes.so。在链接规范中,前缀 lib 和拓展名被忽略了。链接标志以 -l (小写 L)开始,并且一条编译命令可能包含多个链接标志。下面是一个完整的测试程序的编译指令,使用动态库作为示例:

% gcc -o tester tester.c -lshprimes -lm

第一个链接标志指定了库 libshprimes.so,第二个链接标志指定了标准数学库 libm.so

链接器是懒惰的,这意味着链接标志的顺序是需要考虑的。例如,调整上述实例中的链接顺序将会产生一个编译时错误:

% gcc -o tester tester.c -lm -lshprimes ## 危险!

链接 libm.so 库的标志先出现,但是这个库中没有函数被测试程序显式调用;因此,链接器不会链接到 math.so 库。调用 sqrt 库函数仅发生在 libshprimes.so 库中包含的 prime_factors 函数。编译测试程序返回的错误是:

primes.c: undefined reference to 'sqrt'

因此,链接标志的顺序应该是通知链接器需要 sqrt 函数:

% gcc -o tester tester.c -lshprimes -lm ## 首先链接 -lshprimes

链接器在 libshprimes.so 库中发现了对库函数 sqrt 的调用,所以接下来对数学库 libm.so做了合适的链接。链接还有一个更复杂的选项,它支持链接的标志顺序。然而在本例中,最简单的方式就是恰当地排列链接标志。

下面是运行测试程序的部分输出结果:

is_prime
Sample prime ending in 1: 101
Sample prime ending in 1: 401
...
168 primes in range of 1 to a thousand.

prime_factors
prime factors of 12: 2 2 3
prime factors of 13: 13
prime factors of 876,512,779: 211 4154089

are_coprime
Are 21 and 22 coprime? yes
Are 21 and 24 coprime? no

goldbach
Number must be &gt; 2 and even: 11 is not.
4 = 2 + 2
6 = 3 + 3
...
32 =  3 + 29
32 = 13 + 19
...
100 =  3 + 97
100 = 11 + 89
...

对于 goldbach 函数,即使一个相当小的偶数值(例如 18)也许存在多个一对质数之和的组合(在这种情况下,5+13 和 7+11)。因此这种多个质数对是使得尝试证明哥德巴赫猜想变得复杂的因素之一。

封装使用库的 Python 程序

与 C 不同,Python 不是一个静态编译语言,这意味着 Python 客户示例程序必须访问动态版本而非静态版本的 primes 库。为了能这样做,Python 中有众多的支持 外部语言接口 foreign function interface (FFI)的模块(标准的或第三方的),它们允许用一种语言编写的程序来调用另一种语言编写的函数。Python 中的 ctypes 是一个标准的、相对简单的允许 Python 代码调用 C 函数的 FFI。

任何 FFI 都面临挑战,因为对接的语言不大可能会具有完全相同的数据类型。例如:primes 库使用 C 语言类型 unsigned int,而 Python 并不具有这种类型;因此 ctypes FFI 将 C 语言中的 unsigned int 类型映射为 Python 中的 int 类型。在 primes 库中发布的四个 extern C 函数中,有两个在具有显式 ctypes 配置的 Python 中会表现得更好。

C 函数 prime_factorsgoldbach 返回 void 而不是返回一个具体类型,但是 ctypes 默认会将 C 语言中的 void 替换为 Python 语言中的 int。当从 Python 代码中调用时,这两个 C 函数会从栈中返回一个随机整数值(因此,该值无任何意义)。然而,可以对 ctypes 进行配置,让这些函数返回 None (Python 中为 null 类型)。下面是对 prime_factors 函数的配置:

primes.prime_factors.restype = None

可以用类似的语句处理 goldbach 函数。

下面的交互示例(在 Python3 中)展示了在 Python 客户程序和 primes 库之间的接口是简单明了的。

>>> from ctypes import cdll

>>> primes = cdll.LoadLibrary("libshprimes.so") ## 逻辑名

>>> primes.is_prime(13)
1
>>> primes.is_prime(12)
0

>>> primes.are_coprimes(8, 24)
0
>>> primes.are_coprimes(8, 25)
1

>>> primes.prime_factors.restype = None
>>> primes.goldbach.restype = None

>>> primes.prime_factors(72)
2 2 2 3 3

>>> primes.goldbach(32)
32 = 3 + 29
32 = 13 + 19

primes 库中的函数只使用一个简单数据类型:unsigned int。如果这个 C 语言库使用复杂的类型如结构体,如果库函数传递和返回指向结构体的指针,那么比 ctypes 更强大的 FFI 更适合作为一个在 Python 语言和 C 语言之间的平滑接口。尽管如此,ctypes 示例展示了一个 Python 客户程序可以使用 C 语言编写的库。值得注意的是,用作科学计算的流行的 Numpy 库是用 C 语言编写的,然后在高级 Python API 中公开。

简单的 primes 库和高级的 Numpy 库强调了 C 语言仍然是编程语言中的通用语言。几乎每一个语言都可以与 C 语言交互,同时通过 C 语言也可以和任何其他语言交互。Python 很容易和 C 语言交互,作为另外一个例子,当 Panama 项目 成为 Java Native Interface(JNI)一个替代品后,Java 语言和 C 语言交互也会变的很容易。


via: https://opensource.com/article/21/2/linux-software-libraries

作者:Marty Kalin 选题:lujun9972 译者:萌新阿岩 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

前两年 ,有位做投资的朋友曾咨询我,“基于 Linux 的操作系统前景如何?”作为从业计算机行业多年的老兵,我沉吟再三,告诉他,没有什么值得投资的方向和企业,如果有,也要做好长期投入的准备。

自然,这是我的一家之言,但是也是我观察已久的感受。在当时的我看来,中国的基础软件行业发展经历了几十年的摸爬滚打,依旧没有走向快车道。不过,如今再次思考这个问题,我觉得,或许有个不太一样的答案了。

中国基础软件发展亟待新力量

肇始于上世纪九十年代的民用计算机和互联网普及热潮,推动了一大批企业和部门投入到计算机行业,这其中就包括以操作系统、数据库等为代表的基础软件。

但在很长时间内,由于当时的市场形势、政策管理等因素,基础软件行业迟迟得不到发展。倒是许多做上层应用开发的企业,得到了不错的发展机遇,这也更加剧了国内计算机产业忽视和避开对基础软件的投入和建设。一方面,我们看到互联网、电商、直播、游戏等取得花团锦簇的发展;但是另一方面,我们也应当看到,如同筑塔于沙,这些都建立在非自主的基础软件的地基之上。

新形势下对基础软件的新需求

作为伴随着中国互联网普及发展一同成长起来的 IT 人,说实话,我经常感慨的是,从业之初真的不敢想象如今的计算机和互联网能发展到这样惊人的地步。现在的计算机、互联网,乃至大数据、AI 等已经深入生活和社会,甚至成为了国计民生的数字基础。

但这些数字基础的背后,是我们缺乏自主创新的基础软件的隐患。基础软件向上决定了应用软件是如何打造的;向下决定了什么样的 ICT 基础设施的架构是最高效的。我们需要在基础软件层面,坚持发展基础软件根技术,并广泛得到市场的认可,应用到千行百业的行业应用中,才能证明中国的基础软件产业是有生命力的、能够蓬勃发展的、能够加速并使能行业数字化建设的。

那么,在如今的新形势下,如何改变这个现状呢?华为公司高级副总裁、中国地区部总裁鲁勇提出,“科技自立自强已经成为「十四五」国家发展的战略支撑,就是要让数字中国构建在扎实的、可靠的底座上,算力创新势在必行。”

基础软件产业迎来新发展的契机

常言道,新问题也是新机会。虽然中国基础软件产业存在一些发展不足,但不经意间,从了解到的一些情况看,我们已然在某些领域取得可喜的发展。从国产 Linux 厂商的几经沉浮到渐成气候,从一水的 Oracle、MySQL 到 TiDB 的横空出世,从 CentOS 滥觞到 openEuler 一跃成为国内最大的基础软件社区,似乎在并不太长的时间里,中国的信息基础设施领域出现了一些值得注意的新动向。

就在前两天举办的华为中国生态大会2021上,一众中国的基础软件企业联合发布了《共同打造基础软件根技术》倡议。提出了“加强基础科学的研究和突破;加强基础软件的协作与创新;加强基础教育的投入与培养”,倡议业界“坚持开源开放,推动全球科技进步共同打造基础软件根技术”。会上还举行了以“肩负历史使命,打造中国基础软件根技术”为主题的圆桌论坛,来自绿色计算产业联盟的李卫忠,麒麟软件有限公司执行总裁孔金珠,统信软件有限公司高级副总经理朱建忠,云和恩墨联合创始人董事长、首席架构师盖国强,北京海量数据技术股份有限公司总裁肖枫,华为鲲鹏计算业务副总裁openEuler社区理事长江大勇就打造中国基础软件根技术的核心议题展开了深入的探讨。会上中国软件行业协会副理事长、秘书长吕卫峰,携手绿色计算产业联盟宣传部部长李卫忠、openEuler开源社区理事长江大勇和华为公司副总裁、计算产品线总裁邓泰华一起发布《中国基础软件根技术发展白皮书》(框架),这充分展现了业界对于建设中国自主创新的基础软件产业的决心。

基础软件产业是一个战略性的基石产业,不仅仅需要逐一夯实各个薄弱之处,而且应该有宏观的战略指导思想。国家在十四五计划中着重提出了对基础软件、信息技术、开源的宏观指导。而基础软件产业的企业也纷纷在新形势下提出了响应战略。比如说华为,这两年一直在践行其提出的“三个三”方针来建设宏大的鲲鹏和昇腾基础软件生态。所谓的“三个三”是指:

  • 三个开源:开源openEuler操作系统、开源openGauss数据库和开源 AI 框架Mindspore。通过开源得到自主创新、自主发展的条件和土壤,让更多的企业和个人可以参与到中国基础软件的发展进程中,共同进步;
  • 三个开放:鲲鹏应用使能套件、昇腾 AI 应用使用套件和系列化开发套件。通过开放基础设施套件,让行业应用、业务应用可以以一个更低的成本参与到中国基础软件的开发和基于这些基础软件的应用之中,更好更快的打开基础软件的市场,覆盖更多的应用和业务场景,让更多的企业愿意也敢于使用自主创新的基础软件;
  • 三个使能:使能上游开源、使能伙伴发展和使能人才发展。通过使能上游、伙伴和人才,解决行业发展的大问题。通过基础软件使能伙伴,帮助其在更大范围的行业应用中一展手脚,帮助伙伴从1到N打开更多的行业应用市场,开发更多的应用场景,接受市场验证与认可。在人才发展方面,通过“智能基座”项目与高校合作,从师资、教材、课程课时、学科学院建设上,为中国基础软件产业建设提供未来的人才支撑,从基础培养,到专业人才的塑造,扩大整个自主创新的基础软件产业生态。

随着开源之风吹遍,中国也拥有了一批以开源为主旨和战略的公司。在前两天中国信通院发布的第一批开源供应商名录里,就有 26 家企业入选,这里不乏多年深耕开源的领军企业,也有积极拥抱开源的传统企业。

与之伴生的是,在这些开源生态中,已经聚拢了大量的熟悉开源、精通某个领域的开发者群体。以华为为例,华为鲁勇曾表示,“华为超过 70% 研发人员都是投入在基础软件领域”。并且,像涟漪一样,不断形成和扩大更大的开发者群体。在全国建设的 24 个生态创新中心,已经孵化了超过 6000 个鲲鹏昇腾解决方案,完成了超过 70 万个开发者的培训,认证了超过 3000 家合作伙伴。

中国基础软件,前景可期

回到本文开篇的那个问题。我认为当下的中国的基础软件领域获得了前所未有的重视,无论是政策扶植、资本青睐,还是学界支撑和技术群体的扩大,都为之后的中国基础软件爆发性发展奠定了有力的基石。

而稳固而广泛的基础软件,也使能了上层的信息社会、行业应用,形成真正自主创新的完善的信息社会基础设施。更进一步的,广大为信息社区添砖加瓦的独立软件供应商们,也将有坚实的立足点,可以构筑可信、可靠、高质量的行业解决方案。

相信,无论是华为、腾讯这样大型公司,还是 PingCAP、RT-Thread、统信这样的新锐企业,都能跻身于中国的基础软件发展浪潮,夯实中国的信息化发展高速公路之基。

最后一个 IE 版本将终止支持

微软宣布,IE 11 将在 2022 年 6 月 15 日终止支持,在 Windows 10 上 Edge 浏览器是 IE 的未来。此日期之后,IE11 桌面应用程序将被禁用,任何试图打开它的人都将被重定向到 Edge 浏览器。微软表示,如果你受到这项调整影响,那么建议那些依赖 IE11 的遗留网站组织和开发人员在 Edge 浏览器中的 IE 模式下进行处理。但是 IE 模式也会在几年内陆续停止支持。

我想说是,国内一些银行和机构到现在还是雷打不动的 IE-Only 的网站,在这种情况下,国内的同行们还是要考虑尽快推进技术的变革,让大家早日脱离 IE 的苦海。

最新数据显示腾讯 QQ 月活用户已不到微信的一半

5 月 20 日,腾讯发布第一季度财报。财报显示,一季度微信及 WeChat 月活 12.416 亿,同比增长 3.3%。而一季度 QQ 移动终端月活 6.064 亿,同比减少 12.6%。

QQ 虽然没落了,但是 QQ 从来没对 Linux 用户友好过,倒是旗下的 QQ音乐,对于 Linux 用户挺友好的。要说对开源最敷衍的顶级互联网公司,企鹅公司怕是其中之一,讽刺的是,QQ 的形象和 Linux 吉祥物都是企鹅。

Chromebooks 将正式支持 Linux 应用

谷歌在 I/O 大会上宣布,随着 Chrome OS 91 的发布,Chromebook 上的 Linux 终于走出了测试阶段。该公司曾在 Chrome OS 上与安卓应用一起提供 Linux 应用,然而自首次推出以来,一直处于测试阶段。谷歌以稳定的节奏增加了新的功能,实现了诸如 GPU 加速、对 USB 驱动器的更好支持等等。

除了 Linux 应用之外,谷歌还宣布它将把 Android 11 带到 Chromebook。该公司表示,Chromebook 上的安卓应用使用量增加了 3 倍,新的 Android 11 更新将使用虚拟机来运行安卓应用,而不是目前基于容器的方法,使其在未来更容易更新。

无论是 Chromebook 可以运行 Linux 应用和安卓应用,还是微软通过该 WSL 支持 Linux 应用,兼容已有应用成为了趋势,这也是一个操作系统发扬光大的必经之路。毕竟大家虽然会因为系统而选择,但现实是我们使用的不仅仅是系统,还有系统之上的应用生态。

有很多令人信服的理由来用 Pulp 来托管你自己的容器注册中心。下面是其中的一些。

 title=

Linux 容器极大地简化了软件发布。将一个应用程序与它运行所需的一切打包的能力有助于提高环境的稳定性和可重复性。

虽然有许多公共注册中心可以上传、管理和分发容器镜像,但有许多令人信服的论据支持托管自己的容器注册中心。让我们来看看为什么自我托管是有意义的,以及 Pulp,一个自由开源项目,如何帮助你在企业内部环境中管理和分发容器。

为什么要托管你自己的容器注册中心

你可以考虑托管自己的容器注册中心,原因有很多:

  • 体积:一些容器镜像是相当大的。如果你有多个团队下载同一个镜像,这可能需要大量的时间,并给你的网络和预算带来压力。
  • 带宽:如果你在一个带宽有限的地区工作,或在一个出于安全原因限制访问互联网的组织中工作,你需要一个可靠的方法来管理你工作的容器。
  • 金钱:服务条款可以改变。外部容器注册中心能引入或增加速率限制阈值,这可能会对你的操作造成极大的限制。
  • 稳定性:托管在外部资源上的容器镜像可能会因为一些原因消失几天。小到你所依赖的更新容器镜像,可能会导致你想要避免的重大更改。
  • 隐私:你可能也想开发和分发容器,但你不想在公共的第三方注册中心托管。

使用 Pulp 进行自我托管

使用 Pulp,你可以避免这些问题并完全控制你的容器。

1、避免速率限制

在 Pulp 中创建容器镜像的本地缓存,可以让你组织中的每个人都能拉取到 Pulp 上托管的容器镜像,而不是从外部注册中心拉取。这意味着你可以避免速率限制,只有当你需要新的东西时才从外部注册中心进行同步。当你确实需要从外部注册中心同步容器时,Pulp 首先检查内容是否已经存在,然后再从远程注册中心启动同步。如果你受到注册中心的速率限制,你就只镜像你需要的内容,然后用 Pulp 在整个组织中分发它。

2、整理你的容器

使用 Pulp,你可以创建一个仓库,然后从任何与 Docker Registry HTTP API V2 兼容的注册中心镜像和同步容器。这包括 Docker、Google Container registry、Quay.io 等,也包括另一个 Pulp 服务器。对于你结合来自不同注册中心的镜像容器的方式,没有任何限制或约束。你可以自由地混合来自不同来源的容器。这允许你整理一套公共和私人容器,以满足你的确切要求。

3、无风险的实验

在 Pulp 中,每当你对仓库进行修改时,就会创建一个新的不可变的版本。你可以创建多个版本的仓库,例如,development、test、stage 和 production,并在它们之间推送容器。你可以自由地将容器镜像的最新更新从外部注册中心同步到 Pulp,然后让最新的变化在开发或其他环境中可用。你可以对你认为必要的仓库进行任何修改,并促进容器内容被测试团队或其他环境使用。如果出了问题,你可以回滚到早期版本。

4、只同步你需要的内容

如果你想使用 Pulp 来创建一个容器子集的本地缓存,而不是一个完整的容器注册中心,你可以从一个远程源过滤选择容器。使用 Pulp,有多种内容同步选项,以便你只存储你需要的内容,或配置你的部署,按需缓存内容。

5、在断线和空气隔离的环境中工作

如果你在一个断线或受限制的环境中工作,你可以从一个连接的 Pulp 实例中同步更新到你断连的 Pulp。目前,有计划为 Pulp 实现一个原生的空气隔离功能,以促进完全断线的工作流程。同时,作为一种变通方法,你可以使用 Skopeo 等工具来下载你需要的容器镜像,然后将它们推送到你断线的 Pulp 容器注册中心。

还有更多!

通过 Pulp,你还可以从容器文件中构建容器,将私有容器推送到仓库,并在整个组织中分发这些容器。我们将在未来的文章中对这个工作流程进行介绍。

如何开始

如果你对自我托管你的容器注册中心感兴趣,你现在就可以 安装 Pulp。随着 Pulp Ansible 安装程序的加入,安装过程已经被大量自动化和简化了。

Pulp 有一个基于插件的架构。当你安装 Pulp 时,选择容器插件和其他任何你想管理的内容插件类型。如果你想测试一下 Pulp,你今天就可以评估 Pulp 的容器化版本。

如果你有任何问题或意见,请随时在 Freenode IRC 的 #pulp 频道与我们联系,我们也很乐意在我们的邮件列表 [email protected] 中接受问题。


via: https://opensource.com/article/21/5/container-management-pulp

作者:Melanie Corr 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

任天堂 Wii U 已停产数年,但其 Linux 驱动程序仍在开发中

任天堂 Wii U 游戏机已推出近十年,并已于数年前停产,但对它的 Linux 主线内核驱动的工作仍在继续之中。 libdrc.org 项目在开发一个内核驱动以支持 Linux 下的 Wii U 游戏机,并希望能并入主线,今天发布了最新的代码。这个驱动适合于那些想在 Wii U 游戏机上运行 Linux 的人。

虽然我觉得这个工作有点无用,但是 Linux 本来就是一个由一个“无用”的兴趣项目发展起来的,所以这种看似无用的爱好者群体才是 Linux 的草根基础。

Wear OS 和 Tizen 合并,联合对抗苹果的 watchOS

目前在智能手表市场苹果 watchOS 独占鳌头。谷歌和三星宣布合并 Wear OS 和 Tizen,联合起来与苹果的 watchOS 展开竞争。谷歌表示,双方的协作让应用启动速度在最新的芯片组上提高了 30%,动画更流畅。据报道称,谷歌将会为新的智能手表系统提供离线版的谷歌地图和 YouTube Music、Spotify。三星的下一代 Galaxy Watch 将运行合并后的系统。

联合生态是条路,但是我对联合对抗的前景持谨慎乐观态度。

安卓设备激活数量突破 30 亿

在 Google I/O 2021 大会上,谷歌宣布安卓设备激活数量突破了 30 亿。这一数据比 2019 年的 I/O 会议上宣布的数据多 5 亿。这一数据来自于谷歌 Play 商店,没有考虑使用第三方应用商店的安卓设备,这意味着安卓设备的真实激活数量会更高(如中国的安卓设备没有预装 Google Play)。苹果则在今年早些时候宣布它的 iPhone 手机激活数量超过了 10 亿。

这样看起来,安卓设备还真是多,新的生态兼容安卓看来非常有必要 —— 你知道我指的是谁。

你想在你的 Ubuntu 服务器上安装 GUI 吗?大部分情况下你是可以安装的,在本教程中我会详细介绍安装的步骤。

在正式开始之前,我来告诉你为什么服务器版的 Ubuntu 不带 GUI,以及在什么情况下你可以在服务器上安装 GUI。

为什么 Ubuntu 服务器没有 GUI?

你对比过 Ubuntu 的桌面版和服务器版会发现,两者的主要区别是服务器版缺少 GUI(比如 桌面环境)。Ubuntu 服务器基本上就是桌面版去掉图形模块后的降级版本。

这是刻意为之的。Linux 服务器需要占用系统资源来运行服务。图形化桌面环境会消耗大量的系统资源,因此服务器操作系统默认不包含桌面环境。

你可以在只有 512 MB RAM 的机器上使用 Ubuntu 服务器,但是 Ubuntu 桌面需要至少 2 GB 的 RAM 才能提供正常的功能。在服务器运行桌面环境被认为是浪费资源。

作为一个服务器使用者(或系统管理员),你应该通过命令行来使用和管理你的系统。为了达到这个水平,你需要掌握丰富的 Linux 命令相关的知识。

Typically, you have to manage a server from the command line

你是否真正需要在你的服务器上安装 GUI?

有些用户可能不太习惯在终端下使用命令行来完成工作。毕竟大部分用户是有条件通过图形界面操作计算机的。

你可能会在你的服务器上安装桌面环境并使用图形界面。大部分人不会这么干,但这是可行的。

但是这只有在你可以直接操作服务器时才行得通。假设你是在物理机器上运行它,比如服务器、台式机或笔记本电脑,抑或类似树莓派的设备。如果你可以直接操作宿主机系统,那么你还可以在运行在虚拟机上的服务器上安装。

如果你是通过 云服务器提供商如 Linode、DigitalOcean 或 AWS 部署的服务器,那么安装 GUI 就行不通了。如果你想通过图形界面来管理你的远程服务器,你可以使用 Webmin 或 Cockpit 等工具。你可以在 Web 浏览器中通过这些工具使用和管理你的服务器。相比于成熟的桌面环境,它能大大降低资源消耗。

Tools like Cockpit allow managing Linux servers graphically

如何在 Ubuntu 服务器上安装 GUI?

当你了解了基础知识后,我们一起来看看在 Ubuntu 服务器上安装桌面环境的步骤。

你需要做以下准备:

  • 已经配置好 Ubuntu 服务器,且 RAM 至少 2 GB
  • 管理员权限(你需要用 sudo 执行命令)
  • 网络连接正常(你需要下载和安装新包)

我是在虚拟机上安装的 Ubuntu 服务器,并且我可以直接操作宿主机器。我使用同样的方法在树莓派上安装了 Ubuntu 服务器

注意!

如果你是出于学习和调研等实验性的目的,那么你可以进行这些操作。请不要在生产环境的服务器上添加 GUI。后续删除 GUI 时可能会导致依赖问题,有些情况会破坏系统。

准备系统

首先,因为你将要做一些系统级的修改,因此先进行更新和升级以确保我们系统的包是最新的:

sudo apt update && sudo apt upgrade

安装桌面环境

更新结束后,你就可以安装桌面环境了。

有两种方法:

  • 使用 apt 来安装包
  • 使用一个名为 tasksel 的 Debian 工具,这个工具可以通过一条龙处理(任务)方式来安装多个包

任何一种方法都可以用完整包的方式来安装完整的桌面环境,就跟你从头安装桌面版本一样。我的意思是你可以得到跟桌面版本一样的所有的默认应用程序和工具。

如果你想使用 tasksel,需要先用下面的命令安装它:

sudo apt install tasksel

执行结束后,你就可以用 tasksel 来安装桌面环境(也叫 DE)了。

你可能知道有 很多可用的桌面环境。你可以选择自己喜欢的一个。有些桌面环境对系统资源占用得多(像 GNOME),有些占用得少(像 Xfce、MATE 等等)。

你可以自己决定使用哪个 DE。我会安装 GNOME 桌面,因为它是 Ubuntu 默认的桌面。之后我也会介绍其他桌面的安装。

如果你使用的是 tasksel,执行下面这条命令:

sudo tasksel install ubuntu-desktop

如果你使用 apt,执行下面这条命令:

sudo apt install ubuntu-desktop

这个过程可能会持续几分钟到一个小时,执行速度取决于你的网速和硬件。

我想提醒下,上面两个命令执行后都会安装完整的 GNOME 桌面环境。在本文中我两个命令都会执行,两个命令的结果是一样的。

安装和配置显示管理器

安装完成后,你需要一个名为 显示管理器 或“登录管理器”的组件。这个工具的功能是在管理用户对话和鉴权时启动 显示服务器 并加载桌面。

GNOME 桌面默认使用 GDM3 作为显示管理器,但从资源角度考虑它有点重。你可以使用更轻量级和资源友好的管理器。这里我们使用一个平台无关的显示管理器 lightdm。使用 apt 安装它:

sudo apt install lightdm

安装 lightdm 时系统会让我们选择默认的显示管理器,因为即使你可以安装多个管理器,也只能运行一个。

Use the arrow key to select an option and then use the tab key to select  and press enter

选择列表中的 “lightdm” 并点击 “”。这应该用不了几分钟。完成后你可以用下面的命令启动显示管理器并加载 GUI:

sudo service lightdm start

你可以使用下面的命令来检查当前的显示管理器:

cat /etc/X11/default-display-manager

运行后得到的结果类似这样:

Checking the default Display Manager

如果一切顺利,你现在会来到欢迎界面。

Greetings screen of GNOME Desktop with LightDM on an Ubuntu server

输入你的凭证,你的桌面就运行起来了。

GNOME Desktop fully loaded on Ubutnu server

如果你想关闭 GUI,那么打开一个终端并输入:

sudo service lightdm stop

安装其他的桌面环境(可选)

前面我说过我们可以选择不同的桌面。我们一起来看看一些其他的选项:

MATE

MATE 是基于 GNOME2 源码的轻量级桌面,它完全开源,是一个不错的选项。

用下面的命令来安装 MATE:

sudo tasksel install ubuntu-mate-core

sudo apt install ubuntu-mate-core
Lubuntu / LXDE/LXQT

如果你的系统资源有限或者电脑很旧,那么我推荐另一个轻量级的 Lubuntu。使用下面的命令安装它:

sudo tasksel install lubuntu-core

sudo apt install lubuntu-core
Xubuntu / Xfce

Xubuntu 是基于 Xfce 的 Ubuntu 衍生版,轻量、简单、稳定但可高度定制。如果你想使用它,执行下面的命令:

sudo tasksel install xubuntu-core

sudo apt install xubuntu-core

还有一些桌面没有列出来,像 KDECinnamonBudgie,不代表它们不好,它们也都是非常卓越的,你可以自己尝试安装它们。

如何从 Ubuntu 服务器上删除 GUI?

如果你觉得桌面环境占用了太多的计算资源,你可以把之前安装的包删除掉。

请注意在某些情况下删除 GUI 可能会带来依赖问题,因此请备份好重要数据或创建一个系统快照。

sudo apt remove ubuntu-desktop
sudo apt remove lightdm
sudo apt autoremove
sudo service lightdm stop

现在重启你的系统。你应该回到了正常的命令行登录。

结语

在大多数场景下是可以安装桌面 GUI 的。如果你不适应命令行,那么请使用类似 YunoHost 的发行版的服务器,YunoHost 基于 Debian 系统,你可以通过 GUI 来管理服务器。

上面说了,如果你是从头安装系统,那么我建议你使用桌面版本以避免后续的步骤。

如果你有任何问题,请在评论区留言。你会在服务器上使用 GUI 吗?参照本文后你遇到了什么问题吗?


via: https://itsfoss.com/install-gui-ubuntu-server/

作者:Chris Patrick Carias Stas 选题:lujun9972 译者:lxbwolf 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出