42、Linux 打印与编程工具全解析

Linux 打印与编程工具全解析

1. Linux 打印相关

在 Linux 系统中,打印配置可能较为复杂。若遇到问题需手动配置,通常借助图形工具创建打印机是不错的选择,能为后续操作提供起点。

许多打印机,尤其是廉价型号,无法识别 PostScript 或 PDF 格式。为使 Linux 系统支持这些打印机,需将文档转换为打印机特定格式。CUPS 系统会将文档发送至 RIP(Raster Image Processor)处理器以生成位图。RIP 处理器大多使用 Ghostscript(gs)程序完成任务,但这有些复杂,因为位图需适配打印机格式。这意味着 CUPS 系统使用的打印机驱动会检查特定打印机的 PPD(PostScript Printer Definition)文件,以确定分辨率和纸张尺寸等设置。

2. Linux 用户界面相关

Linux 系统用户界面环境的一个有趣特性是,用户通常可选择想用的组件,也可摒弃不喜欢的组件。若想了解众多用户界面项目概况,可查看位于 http://www.freedesktop.org/ 的邮件列表和不同项目的链接。此外,还能找到其他用户界面项目,如 Ayatana、Unity 和 Mir。

另一个与 Linux 系统用户界面相关的大型编程项目是开源的 Chromium OS 项目,以及其对应在 Chromebook PC 设备上可用的 Google Chrome OS 系统。这些基于 Linux 系统的设备大量运用了本章描述的用户界面技术,且以 Chromium/Chrome 浏览器为基础。Chrome OS 系统中剔除了传统计算机中的大部分内容。

3. Linux 编程工具概述

Linux 和 Unix 深受程序员喜爱,不仅因其拥有丰富的工具和环境,还因其文档完善、完全透明。在 Linux 计算机上工作,即便不是程序员也能使用编程工具,但至少要了解编程基础知识,因为这些工具对该系统的重要性远超其他操作系统。最低要求是能够识别各个工具并了解其工作原理。

4. C 语言编译器

掌握 C 语言编译器的使用,能让我们了解许多在 Linux 系统中运行的程序细节。Linux 中几乎所有工具的代码以及众多其他应用程序,都是用 C 或 C++ 编写的。本文主要使用 C 语言代码示例,这些示例也适用于 C++。

C 语言程序的创建遵循传统模式:编写代码、编译、运行。也就是说,编写完 C 语言程序后,要运行它就必须将源代码编译成计算机能理解的底层二进制形式。这与后续介绍的脚本语言不同,脚本语言无需编译。

默认情况下,大多数发行版不包含编译 C 代码所需的工具,因为它们会占用大量空间。若找不到本文提及的部分工具,可安装 Debian/Ubuntu 发行版的 build - essential 包,或使用 Fedora/CentOS 发行版的 yum 工具的 Development Tools 安装组。若仍无法解决,可尝试搜索与 C 语言编译器相关的包。

在大多数 Unix 系统中,C 语言编译器名为 gcc(GNU C Compiler),不过来自 LLVM 项目的较新编译器 clang 正日益流行。源文件的扩展名为 .c。以下是一个简单的 C 语言程序示例:

#include <stdio.h> 
main() {
    printf("Witaj, świecie.\n"); 
}

将上述源代码保存为 hello.c 文件后,运行以下命令:

$ cc hello.c

编译后会得到一个名为 a.out 的可执行文件,可像运行系统中的其他程序一样运行它。不过,最好给程序起一个更易读的名称,例如 hello,可使用编译器的 -o 选项:

$ cc -o hello hello.c

在小型程序中,整个编译过程可能就是这样。有时可能只需添加其他文件或头文件目录(后续会详细介绍),在介绍这些内容之前,先看看如何处理稍大的程序。

5. 多个源文件的处理

大多数用 C 语言编写的程序太大,无法放在一个源文件中。程序员不喜欢处理大型文件,甚至编译器处理大型源文件时也会有问题。因此,程序开发者常将源代码的各个组件分组,每个元素放在一个单独的文件中。

编译大多数 .c 文件时,不会立即生成可执行文件。而是使用编译器的 -c 选项为每个文件创建目标文件。假设存在两个文件:main.c 和 aux.c,以下命令可完成构建可执行文件的大部分工作:

$ cc -c main.c 
$ cc -c aux.c

上述命令会将两个源文件转换为目标文件:main.o 和 funkcje.o。

目标文件是一个准备好的可执行文件,但缺少一些完成编译所需的数据。首先,系统不知道如何运行单个目标文件;其次,一个程序通常由多个目标文件组成。

要从多个目标文件构建一个完全可运行的可执行文件,需要使用链接器程序,在 Unix 中称为 ld。程序员很少在命令行中直接使用 ld 程序,因为编译器知道如何正确调用它。要从之前准备好的两个目标文件创建可执行文件 myprog,可执行以下命令:

$ cc -o myprog main.o aux.o

虽然手动编译多个源文件是可行的,但随着源文件数量的增加,这种方法会变得非常复杂。在 Unix 系统中,通常使用标准工具 make 来处理编译(后续会详细介绍)。该系统在后续两个部分描述的文件管理过程中起着特殊作用。

6. 头文件和目录

C 语言头文件是额外的源文件,通常包含库类型和函数的声明。例如,之前示例程序中使用的 stdio.h 就是一个头文件。

不幸的是,大多数编译程序时遇到的问题都源于头文件。很多错误是因为编译器无法找到所需的头文件和库。有时程序员会忘记在程序中包含所需的头文件,导致部分源代码无法编译。

6.1 解决头文件问题

查找正确的源文件并非易事。有时不同目录中存在同名的头文件,难以确定哪个是正确的。当编译器找不到所需的头文件时,会出现以下错误信息:

zlynaglowek.c:1:22: fatal error: nieznaleziono.h: No such file or directory

从上述信息可知,编译器无法找到源文件 zlynaglowek.c 引用的头文件 nieznaleziono.h。这个错误源于 zlynaglowek.c 文件第一行的指令:

#include <nieznaleziono.h>

在 Unix 中,默认存储头文件的目录是 /usr/include。编译器总是会搜索这个目录,除非我们明确禁止。也可以让编译器在其他目录中搜索头文件(大多数头文件目录的名称中包含 include 一词)。

假设我们在 /usr/junk/include 目录中找到了头文件 nieznaleziono.h,可使用 -I 选项让编译器在这个非标准目录中搜索该头文件:

$ cc -c -I/usr/junk/include zlynaglowek.c

此时,编译器不应再报告与 zlynaglowek.c 文件第一行引用的头文件缺失相关的错误。

同样,对于包含头文件的指令,若文件名用双引号括起来,而非尖括号,如下所示:

#include "mojplik.h"

双引号表示头文件不在系统头文件目录中,编译器会在其他地方搜索。这种写法通常意味着头文件与源文件位于同一目录中。若编译程序时出现指向双引号 include 指令的错误,很可能是编译程序中缺少某些文件。

6.2 C 语言预处理器(cpp)

实际上,C 语言编译器并不负责搜索所有头文件,这项任务由 C 语言预处理器完成。预处理器是在将源文件传递给编译器之前对其进行处理的程序,它会将程序代码修改为编译器能理解的形式。换句话说,它是一个便于读取源代码并创建一些缩写的工具。

程序代码中的预处理器指令称为指令,可以通过每个指令开头的井号(#)来识别。有三种类型的指令:
- 头文件 :#include 指令要求预处理器将整个文件包含到程序中。要记住,编译器的 -I 选项会让它在指定目录中搜索头文件,如前面的示例所示。
- 宏定义 :带有 #define BLABLA coś 的行要求预处理器将源代码中所有出现的 BLABLA 替换为 coś。按照传统,宏名应该用大写字母书写,但有些程序员会给宏起类似于变量或函数名的名称,这种做法常导致程序出现严重问题,部分程序员将过度使用预处理器视为一种“运动”。还要注意,宏不一定要在程序源代码中定义,也可以通过传递编译器的相应参数来定义。-DBLABLA=coś 选项与上述预处理器指令具有相同的含义。
- 条件指令 :可以将部分源代码包含在 #ifdef、#if 和 #endif 指令内。#ifdef MAKRO 指令检查预处理器中是否已定义 MAKRO,而 #if 条件指令检查给定条件是否为非零值。在这两种情况下,如果指令中的条件不满足,预处理器将不会将 #if 或 #ifdef 与 #endif 指令之间的源代码传递给编译器。分析任何 C 语言代码时,都要记住这些依赖关系。

以下是一个条件指令的示例:

#ifdef DEBUG
  fprintf(stderr, "To jest komunikat debugowania.\n"); 
#endif

当预处理器接收到上述代码时,会检查是否已定义 DEBUG 宏。如果已定义,它会将包含 fprintf() 语句的行传递给编译器;否则,预处理器会跳过该行,并从 #endif 指令后的行继续分析代码。

C 语言预处理器不了解 C 语言的任何元素,如语法、变量、函数等。它只处理自己的宏和指令。

在 Unix 中,C 语言预处理器程序名为 cpp,也可以通过运行 gcc -E 命令来调用它。不过,实际上单独运行预处理器几乎是不必要的。

7. 与库的链接

编译器没有足够的信息来独立准备一个可运行的程序。现代系统构建程序需要所谓的库。C 语言库是一组预先编译好的函数,可以包含到我们的程序中。例如,许多可执行文件会使用 math 库,因为其中包含了三角函数等现成的函数。

库主要在链接阶段使用,即链接器从目标文件创建可执行程序时。例如,当我们准备一个使用 gobject 库的程序,但忘记告诉链接器在链接时使用该库时,屏幕上会出现类似以下的错误信息:

badobject.o(.text+0x28): undefined reference to ‘g_object_new’

上述错误信息中最重要的部分已加粗显示。当链接器检查目标文件 badobject.o 的内容时,无法找到错误信息中列出的数学函数,因此无法创建可执行文件。在这种情况下,我们可以推测是忘记包含 gobject 库,因为缺失的函数名是 g_object_new()。

缺失的引用并不总是意味着系统中缺少某个库。同样,我们可能在命令行中忘记指定某个目标文件的名称。不过,通常很容易区分库中函数的调用和目标文件中函数的调用。

要解决这个问题,需要找到 gobject 库,然后使用 -l 选项告诉链接器在链接时使用该库。与头文件一样,库也分散在整个系统中(默认位置是 /usr/lib 目录),但大多数库存储在名为 lib 的目录中。运行前面示例中的程序所需的库文件名为 libgobject.a,位于 /usr/lib 目录中,这意味着库的名称是 gobject。因此,我们的程序必须使用以下命令进行链接:

$ cc -o badobject badobject.o -lgobject

我们还需要告诉链接器非标准库的存储位置,这可以使用 -L 参数。假设程序 badobject 需要使用位于 /usr/junk/lib 目录中的 libcrud.a 库,可使用以下命令进行编译和准备可执行文件:

$ cc -o badobject badobject.o -lgobject -L/usr/junk/lib -lcrud

可以使用 nm 命令在库中查找指定名称的函数。使用该命令可能会得到很多结果。例如,可以使用 nm libgobject.a 命令。有时可能需要使用 locate 命令来查找 libgobject.a 文件。目前,在许多发行版中,库被放置在 /usr/lib 目录下特定于架构的子目录中。

8. 共享库

文件名以 .a 结尾的库文件(如 libgobject.a)是静态库(static library)。在将程序与静态库链接时,链接器会从库中复制相应的机器代码片段,并将其插入到可执行文件中。这样,生成的可执行文件在运行时不再需要库文件,而且其运行方式永远不会改变。

然而,静态库的不断增长的大小和数量导致它们在磁盘和内存中占用了大量空间。此外,如果静态库后来被证明不合适或不安全,在不重新编译相关可执行文件的情况下,无法更改与之关联的任何可执行文件。

解决这个问题的方法是使用共享库(shared libraries)。当运行与共享库链接的程序时,系统只有在实际需要时才会将这些库的代码加载到程序进程的内存中。此外,不同的进程可以共享内存中库的代码。如果需要对库的代码进行轻微修改,通常可以在不重新编译任何程序的情况下实现。

但是,使用共享库也有一定的代价:操作方式复杂,编译过程也稍微复杂一些。为了正确控制共享库,我们需要了解以下四个信息:
- 如何检查我们的程序需要哪些共享库。
- 我们的程序如何搜索共享库。
- 如何将程序与共享库链接。
- 与共享库相关的最常见问题有哪些。

后续部分将包含有关在系统中正确管理共享库的信息。关于链接器和共享库本身的工作原理的信息,可以在 John R. Levine 的《Linkers and Loaders》(Morgan Kaufmann 出版社,1999 年)、David M. Beazley、Briana D. Ward 和 Ian R. Cooke 的文章《The Inside Story on Shared Libraries and Dynamic Loading》(《Computing in Science & Engineering》杂志,2001 年 9 月/10 月)或网络资源中找到,如文档《Program Library HOWTO》(http://dwheeler.com/program-library/)。也值得阅读手册页 ld.so(8)。

8.1 打印共享库依赖关系

共享库文件通常与静态库存储在同一位置。在 Linux 系统中,标准上为此指定了两个目录:/lib 和 /usr/lib,但 /lib 目录应仅存储静态库。

共享库的扩展名为 .so,例如 libc - 2.15.so 和 libc.so.6。可以使用 ldd program 命令(其中 program 是可执行文件的名称)来检查某个程序使用了哪些共享库。以下是一个关于 shell 的示例:

$ ldd /bin/bash
    linux-gate.so.1 =>  (0xb7799000)
    libtinfo.so.5 => /lib/i386-linux-gnu/libtinfo.so.5 (0xb7765000)
    libdl.so.2 => /lib/i386-linux-gnu/libdl.so.2 (0xb7760000)
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75b5000)
    /lib/ld-linux.so.2 (0xb779a000)

为了实现最佳性能和灵活性,可执行文件通常不包含它们使用的共享库在系统中的位置,只包含这些库的名称以及可能的一些额外信息,以便找到它们。查找和加载共享库的工作由一个小的程序 ld.so(运行时动态链接器/加载器)完成。在上述 ldd 程序的输出结果中,左边列出的是库文件的名称,这是可执行程序所拥有的全部信息;右边列出的是 ld.so 程序找到这些库的目录。

上述输出的最后一行显示了 ld.so 程序的实际名称:ld - linux.so.2。

8.2 ld.so 程序如何查找共享库

与共享库相关的一个麻烦问题是链接器找不到库。动态链接器首先应该在程序中预先配置的库搜索路径中查找程序所需的共享库(如果存在的话)。下面简要介绍创建此路径的方法。

然后,动态链接器会检查系统组件文件 /etc/ld.so.cache,查看某个库是否存储在标准位置。这是一个存储在 /etc/ld.so.conf 配置文件中列出的目录中的库文件名列表。

与 Linux 系统中许多之前讨论过的配置文件一样,ld.so.conf 文件可以包含位于 /etc/ld.so.conf.d 等目录中的多个文件。

该文件中的每一行描述了一个应包含在列表中的目录。这样的目录列表通常不长,大致如下:

/lib/i686-linux-gnu 
/usr/lib/i686-linux-gnu

标准库目录 /lib 和 /usr/lib 是默认的,不需要包含在 /etc/ld.so.conf 文件中。

在对 /etc/ld.so.conf 文件或存储共享库的目录之一进行更改后,需要通过运行以下命令来重建 /etc/ld.so.cache 文件:

# ldconfig -v

-v 选项会使该命令打印出添加到列表中的库的详细信息以及在其中检测到的所有更改。

ld.so 程序还会在环境变量 LD_LIBRARY_PATH 中搜索共享库,后续会详细介绍。

不要过于习惯向 /etc/ld.so.conf 文件中添加新元素。我们应该了解系统中存在哪些共享库,将各种奇怪的库放入其中可能会导致系统中出现冲突和混乱。在编译一个需要非标准库搜索路径才能运行的程序时,应该在编译时将该路径写入程序中。下面将介绍如何实现这一点。

8.3 程序与共享库的链接

假设系统中有一个名为 libweird.so.1 的共享库,存储在 /opt/obscure/lib 目录中,我们需要将其与程序 myprog 链接。可以使用以下命令完成此操作:

$ cc -o myprog myprog.o -Wl,-rpath=/opt/obscure/lib -L/opt/obscure/lib -lweird

-Wl,-rpath 选项告诉链接器将指定的目录添加到程序的库搜索路径中。但是,即使添加了此选项,也不能省略 -L 选项。

如果已经有一个现有的二进制文件,也可以使用 patchelf 程序来插入另一个运行时库搜索路径。不过,通常在编译时进行此操作更好。

8.4 共享库相关问题

共享库是一种非常灵活的工具,可以执行非常惊人的操作。然而,如果过度使用,可能会使我们的系统变得一团糟。可能会遇到以下三个问题:
- 缺失的库 :程序运行时找不到所需的共享库。
- 性能显著下降 :共享库的使用可能会导致程序性能下降。
- 不正确的库 :使用了错误版本或损坏的共享库。

Linux 打印与编程工具全解析

9. 总结与操作建议

为了更清晰地呈现 Linux 打印与编程工具相关内容,下面通过表格和流程图进行总结,并给出操作建议。

9.1 关键信息表格总结
主题 内容要点 操作命令示例
打印配置 复杂时用图形工具创建打印机;转换文档格式用 CUPS 与 RIP 处理器
C 语言编译 用 gcc 或 clang 编译,源文件扩展名为 .c cc -o hello hello.c
多源文件处理 用 -c 选项创建目标文件,用 ld 链接 cc -c main.c cc -o myprog main.o aux.o
头文件处理 用 -I 选项指定搜索目录,预处理器处理头文件 cc -c -I/usr/junk/include zlynaglowek.c
库链接 用 -l 选项指定库,-L 选项指定库目录 cc -o badobject badobject.o -lgobject -L/usr/junk/lib -lcrud
共享库 灵活但操作复杂,用 ldd 查看依赖,-Wl,-rpath 指定搜索路径 ldd /bin/bash cc -o myprog myprog.o -Wl,-rpath=/opt/obscure/lib -L/opt/obscure/lib -lweird
9.2 编译程序流程图
graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px

    A([开始]):::startend --> B(编写源文件):::process
    B --> C{是否多个源文件}:::decision
    C -- 是 --> D(用 -c 选项创建目标文件):::process
    C -- 否 --> E(直接编译源文件):::process
    D --> F(用 ld 或编译器链接目标文件):::process
    E --> F
    F --> G{是否使用库}:::decision
    G -- 是 --> H(用 -l 和 -L 选项指定库和库目录):::process
    G -- 否 --> I(生成可执行文件):::process
    H --> I
    I --> J{是否使用共享库}:::decision
    J -- 是 --> K(用 -Wl,-rpath 指定搜索路径):::process
    J -- 否 --> L([结束]):::startend
    K --> L
9.3 操作建议
  • 打印配置 :当遇到打印问题时,优先使用图形工具创建打印机,若仍无法解决,再深入研究文档格式转换和驱动配置。
  • C 语言编程 :在编写程序前,确保系统安装了编译所需的工具,如 Debian/Ubuntu 的 build - essential 包或 Fedora/CentOS 的 Development Tools 安装组。
  • 多源文件处理 :随着源文件数量增加,使用 make 工具管理编译过程,提高效率。
  • 头文件处理 :养成良好的头文件引用习惯,明确文件位置,避免因头文件缺失导致编译错误。
  • 库链接 :在使用库时,仔细检查库的名称和路径,避免出现链接错误。
  • 共享库使用 :谨慎向 /etc/ld.so.conf 文件添加新元素,尽量在编译时指定库搜索路径。
10. 常见问题及解决方案

在使用 Linux 打印与编程工具过程中,可能会遇到各种问题,下面针对常见问题给出解决方案。

10.1 编译问题
问题描述 可能原因 解决方案
编译器找不到头文件 头文件路径配置错误;头文件缺失 使用 -I 选项指定头文件搜索路径;检查头文件是否存在并安装缺失的头文件
链接器找不到库 库路径配置错误;库文件缺失 使用 -L 选项指定库搜索路径;检查库文件是否存在并安装缺失的库文件
编译报错未定义引用 忘记包含库;目标文件缺失 用 -l 选项指定所需库;检查命令行是否指定所有目标文件
10.2 共享库问题
问题描述 可能原因 解决方案
缺失的库 库文件未安装;库搜索路径配置错误 安装缺失的库文件;用 -Wl,-rpath 或修改 /etc/ld.so.conf 文件指定库搜索路径
性能显著下降 共享库版本不兼容;库加载过多 检查并更新共享库版本;优化程序对共享库的使用
不正确的库 库文件损坏;使用错误版本的库 重新安装库文件;确保使用正确版本的库
11. 总结

本文全面介绍了 Linux 打印与编程工具的相关知识,包括打印配置、用户界面项目、C 语言编译、多源文件处理、头文件和库的使用以及共享库相关内容。通过对这些知识的学习和掌握,我们可以更好地在 Linux 系统中进行打印操作和程序开发。同时,通过总结关键信息、绘制流程图和给出操作建议,为读者提供了清晰的操作指引,帮助读者解决可能遇到的问题。希望读者在实际使用中能够灵活运用这些知识和技巧,提高工作效率和编程质量。

基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究(Matlab代码实现)内容概要:本文围绕“基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究”,介绍了利用Matlab代码实现配电网可靠性的仿真分析方法。重点采用序贯蒙特卡洛模拟法对配电网进行长时间段的状态抽样统计,通过模拟系统元件的故障修复过程,评估配电网的关键可靠性指标,如系统停电频率、停电持续时间、负荷点可靠性等。该方法能够有效处理复杂网络结构设备时序特性,提升评估精度,适用于含分布式电源、电动汽车等新型负荷接入的现代配电网。文中提供了完整的Matlab实现代码案例分析,便于复现和扩展应用。; 适合人群:具备电力系统基础知识和Matlab编程能力的高校研究生、科研人员及电力行业技术人员,尤其适合从事配电网规划、运行可靠性分析相关工作的人员; 使用场景及目标:①掌握序贯蒙特卡洛模拟法在电力系统可靠性评估中的基本原理实现流程;②学习如何通过Matlab构建配电网仿真模型并进行状态转移模拟;③应用于含新能源接入的复杂配电网可靠性定量评估优化设计; 阅读建议:建议结合文中提供的Matlab代码逐段调试运行,理解状态抽样、故障判断、修复逻辑及指标统计的具体实现方式,同时可扩展至不同网络结构或加入更多不确定性因素进行深化研究。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值