关于嵌入式开发的一些信息汇总:开发模型以及自托管开发(二)

这篇文章是关于嵌入式开发的一些基本信息,供想入行的人参考。有一些作者本人的想法,以及来自外网的大拿的文章翻译而来,原文链接在此Learning Linux for embedded systems,再次感谢,支持原创。

2 自托管开发

自托管开发是在主机系统上开发的一个分支,类似于我们在本系列前几期中对 VM 安装所做的工作,但是也有一些差异,因为目标系统与大多数嵌入式系统一样,内存有限且处理器相对较慢。

2.2 构建 Raspberry Pi 内核

首先我们将在 Raspberry Pi 上构建内核,然后在运行速度更快的主机系统上构建它。使用单独的主机进行开发是交叉开发模型的一个示例。
上一次在 RPi上构建内核,我们使用rpi-source 脚本下载并安装在 RPi 上构建内核模块所需的头文件。创建该脚本的同一位开发人员notro还创建了一个rpi-build 脚本,可以轻松构建内核。可以在GitHub 页面上找到说明:

$ wget https://raw.githubusercontent.com/notro/rpi-build/master/rpi-build
$ sudo mv rpi-build /usr/bin/rpi-build
$ sudo chmod +x /usr/bin/rpi-build

虽然rpi-source 脚本是用 Python 编写的,但rpi-build 脚本是用 Ruby 编写的,在我们运行它之前需要安装它:

$ sudo apt-get update
$ sudo apt-get install ruby

第一次运行rpi-build时,它将检查缺少的依赖项。当系统询问您是否要安装这些依赖项时,回答 Y。在此之后,我们可以使用此脚本构建内核:

$ rpi-build use [stdlib] linux install

需要等一段时间。在脚本下载文件并开始编译内核文件的同时,让我们启动运行 Fedora 的 VM,看看在更快的系统上构建 Raspberry Pi 内核
我们将使用 git 源代码控制系统从 github.com 上的 Rasbberry Pi 存储库复制交叉工具链和内核。首先我们需要以root身份安装git:

$ su
密码:
# yum install git

然后我们就可以下载工具链和内核源码了:

$ git clone –depth 1 git://github.com/raspberrypi/tools.git
$ git clone –depth 1 git://github.com/raspberrypi/linux.git

让我们准备好构建 Linux 内核:

$ cd ~/linux
$ make mrproper
$ export CCPREFIX=~/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin/arm-linux-gnueabihf-

这个指令将选择 GCC-4.8.3 的 Linaro 版本作为我们的交叉编译器。交叉编译器要在一个系统上运行,在本例中我们的 VM 在 X86-64 主机系统上运行,并生成将在不同处理器上执行的目标代码,在本例中为 ARM,特别是用于 Broadcom BCM2708 处理器树莓派。
我们将为 Raspberry Pi 使用默认的 Linux 配置:

$ cp arch/arm/configs/bcmrpi_defconfig .config
$ make ARCH=arm CROSS_COMPILE=${CCPREFIX} oldconfig

或者,我们可以从 Raspberry Pi 复制配置,它可以在/proc/config.gz中找到。使用 gunzip 解压缩此文件并将其从config重命名 为.config ,然后再运行make oldconfig ,如上所示。
执行此操作时,系统会询问您有关已添加到内核,但未在您使用的.config文件中提及的新选项。我只是为所有这些(许多)选项点击返回,选择默认值

我们现在准备在我们的主机 X86 系统上构建 ARM 目标:

$ make ARCH=arm CROSS_COMPILE=${CCPREFIX}
$ make ARCH=arm CROSS_COMPILE=${CCPREFIX} module

在 X86 主机上构建,甚至是虚拟机,都需要不到一个小时。在 Raspberry Pi 上,内核构建大约需要 6.5 小时。

2.3 安装内核

在树莓派上,我们可以将新建的内核复制到引导目录并重命名:

#cp /linux/arch/arm/boot/Image /boot/kernel_rpi.img

在主机系统上,事情有点复杂。关闭 Raspberry Pi 并取出 SD 卡将 SD 卡插入主机系统上的读卡器从主机上的 SD 卡挂载引导分区现在我们可以将在主机上构建的内核复制到 SD 卡上的正确位置。我们也可以将模块安装到临时目录中,然后将它们合并到 SD 卡上的 /lib 目录中

$ cp ~/linux/arch/arm/boot/Image /run/media/eager/boot/kernel_host.img
$ make ARCH=arm CROSS_COMPILE=${CCPREFIX} INSTALL_MOD_PATH=~/modules modules_install
$ sudo cp -r ~/modules /lib /run/media/eager/*/lib

在我们从主机系统中取出 SD 卡并将其重新插入 Raspberry Pi 之前的最后一件事。编辑内核命令行指定我们要启动在主机上构建的内核。在编辑器中打开/boot/config.txt 并进行以下更改:

#kernel=kernel.img
kenel=kernel_host.img

如果缺少默认的kernel=kernel.img 行,只需在文件底部添加第二行从 SD 卡上卸载文件系统并将其从主机系统中删除。将其重新插入树莓派并打开电源。如果一切都正确完成,我们的新 Linux 内核将在 Rpi 上启动。我们可以通过查看登录时显示的构建日期来确认这一点。

2.4 总结

Raspberry Pi 是一款功能中等的单板计算机,因此可以在目标上构建内核,即使需要几个小时。构建包含所有用户库和应用程序的整个文件系统可能需要数天时间。
我们已经开始研究经常与嵌入式系统一起使用的跨平台开发。在此模型中,我们使用跨开发工具链构建强大的主机系统,为目标生成目标代码。在这个例子中,我们从目标系统中物理地移动了包含根文件系统的 SD 卡,并将其挂载到主机上

3 连接目标板

  • 当我们在另一个系统上开发时,我们必须使用交叉编译器和目标环境的副本,这使得问题有点复杂,但是交叉开发环境通常比嵌入式目标快得多,具有更多的磁盘和内存空间。
  • 对于许多嵌入式 Linux 项目,根本不可能在目标上构建
    我们从开发系统中复制内核并将其写入我们插入树莓派的 SD 卡中。这行得通,但是将 SD 卡从开发系统移到目标系统,运行一些测试,然后在我们想要进行更改时将其移回,然后在我们想要测试这些更改时移回目标系统是很尴尬的
  • 令人高兴的是,Raspberry Pi 和几乎所有目标板都可以通过串行端口或以太网与外界通信。我们将使用它来将文件从开发系统传输到目标,甚至从开发主机控制目标

3.1 Raspberry Pi 上的网络设置

RPi 的 Raspbian发行版预装了一套完整的网络工具。在 RPi 控制台上,输入命​​令“ ip addr show ”,您应该会看到如下内容:

$ ip addr show
1: lo:   mtu 65536 qdisc noqueue state UNKNOWN     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00    inet 127.0.0.1/8 scope host lo       valid_lft forever preferred_lft forever
2: eth0:   mtu 1500 qdisc pfifo_fast state UP qlen 1000    link/ether b8:27:eb:bc:d0:81 brd ff:ff:ff:ff:ff:ff    inet 192.168.20.112/24 brd 192.168.20.255 scope global eth0       valid_lft forever preferred_lft forever   

这就告诉了您开发板已连接到网络,并且外部连接的地址 eth0是 192.168.20.112,那是我的 DHCP 服务器分配给开发板的地址,您可能会有不同的地址。
我将以下行添加到我的开发系统上的/etc/hosts 文件中,这样我就可以使用名称“rpi”而不是使用完整的 IP 地址:

192.168.20.112 raspberrypi rasbpberrypi.eagercon.com rpi

3.2 Ssh、rsh、rlogin 和 telnet 连接到目标

1) SSH 代表 Secure Shell,它是使用加密连接连接到远程系统的标准方式。SSH 是包括ssh 和scp在内的程序的集合,它默认安装在 Raspberry Pi 上,我们将通过在我们的开发主机上输入以下命令来使用它连接到目标:

$ ssh pi@192.168.20.112

要么

$ ssh pi@rpi

系统将要求您提供用户“pi”的密码,如果你没有改变它,它就是“raspberry”。一旦接受,您将进入目标的 bash shell。您可以从这个 shell 执行从 Raspberry Pi 控制台执行的任何操作。(您还可以使用共享密钥设置目标,这样每次连接时都可以绕过密码提示。)
第一次使用 ssh 时,系统会要求您验证要连接的系统是否是您想要的系统。你可以回答“是”。如果您碰巧使用 ssh 连接到使用 Internet 的系统并且您意外收到此消息,比如在您之前验证了系统身份之后,可能有几个原因。一是你没有连接到你想要的系统,最坏的情况是,因为有人拦截了连接。更有可能的是远程系统上的密钥已重新生成。
默认情况下,ssh 启动运行 bash 的终端会话。如果在行尾添加一条命令,则该命令将在远程系统上运行,然后终止。例如:

$ ssh 
pi@rpi df      
pi@rpi's password: Filesystem     
1K-blocks    Used Available Use% Mounted onrootfs           
6240824 4518044   1387688  77% //dev/root        
6240824 4518044   1387688  77% /devtmpfs          
219768       0    219768   0% /devtmpfs              
44788     224     44564   1% /runtmpfs               
5120       0      5120   0% /run/locktmpfs              
89560       0     89560   0% /run/shm/dev/mmcblk0p5     
57288   25688     31600  45% /boot$ 

如果您在 ssh 命令上指定“-X”选项,SSH 还允许您远程运行图形程序。该程序将在目标上运行,但窗口将显示在您的主机系统上
2) RSH 是一个包括 rsh 和 rcp 程序的包。与 ssh 类似,rsh 允许您通过以太网连接到远程系统。在大多数发行版中默认情况下通常不安装 RSH 的不同之处在于,没有对您输入的数据或远程系统返回的数据进行加密。您可能会发现 rsh 是未安装 ssh 的别名。使用 rsh 连接的命令与 ssh 的连接命令类似,只是您必须在命令行选项中指定用户 ID:

$ rsh -l pi rpi

与 rsh 类似,rlogin 还允许您连接到目标:

$ rlogin -l pi rpi

3) Telnet 是一种通过串行线路或网络与另一个系统通信的旧方法。它在大多数应用程序中已失宠,因为所有通信都是纯文本,而 SSH 对所有消息进行加密。对于使用SSH连接到您的开发系统的嵌入式 Linux 系统进行开发,几乎没有理由担心有人会监视您的通信。Telnet 是一个简单得多的程序,并且由于它不必加密或解密消息,因此它使用较少的CPU 资源,这对于低性能目标可能很重要没有足够 CPU 能力或内存来运行 SSH 的嵌入式 Linux 系统可以轻松支持 telnet。您可能必须在您的开发主机上安装 telnet 包,因为默认情况下大多数 Linux 发行版不安装Telnet
ssh 和 telnet 都允许您从主机控制目标系统,即使是没有连接显示器或控制台的主机。或者,正如撰写本文时所发生的那样,显示器突然停止工作。
4) 使用 ssh 和 rsh 复制文件
ssh包包含一个命令 scp,它允许您在开发主机系统和远程系统之间复制文件。它的工作原理类似于 cp 命令,但允许您添加远程系统的名称。让我们将在上一期中构建的 kernel.img 文件复制到 Raspberry Pi 目标的/boot目录中:

$ cd ~/linux/arch/arm/boot
$ scp Image
pi@rpi's password:
Image 100% 6226KB 778.3KB/s 00:00
$

最后一行将在执行传输时更新,告诉您已复制了多少数据以及完成传输需要多长时间。
将文件复制到RPi 上的/tmp 后,我们可以使用我们打开的 shell 到 RPi 目标将其复制到/boot 目录:

$ sudo cp /tmp/Image /boot/kernel.img

我们无法将文件直接复制到Raspberry Pi 上的/boot ,因为它归 root 所有。安装在 Raspberry Pi 上的 Debian 发行版没有 root 密码,因此复制文件的唯一方法是使用上面显示的“sudo”命令。与 ssh 一样,数据在发送前加密,在接收端解密。特别是在低性能目标上,这会使数据传输变慢。
rsh 包中包含 rcp 命令,它与 scp 类似,只是数据未加密。这意味着它比 scp 更高效,并且可以支持更快的数据传输,即使在低性能目标系统上也是如此。您需要将其安装在 Raspberry Pi 目标上:

$ sudo apt-get install xinetd rsh-server

您还需要修改/etc/hosts.equiv 文件以包含如下一行:

$ sudo cat “ ” >> /etc/hosts.equiv

代替 和 使用您的开发系统的主机名(hostname)和您的用户 ID。

在主机系统上,我们现在可以像上面使用 scp 一样使用 rcp:

$ rcp Image rpi:/tmp 

请注意,我们在使用 rcp 时不需要指定用户 ID 或输入密码。

与目标板交谈并不是世界上最令人兴奋的事情,但如果不能,这可能是最令人沮丧的事情之一

4 应用程序开发

让我们来看看嵌入式 Linux 系统(我们的 Raspberry Pi)的软件开发。我们首先要编写传统的“Hello World”程序并在树莓派上编译,然后我们将在开发主机上做同样的事情,使用我们安装的交叉开发工具来构建 Linux 内核。

4.1 在目标板上编译

系统控制台上登录到您的 RPi 系统,或者像我一样,在我的开发系统的窗口中使用ssh 。
在主目录下创建一个名为“projects”的目录,然后cd 进入该目录
现在我们可以使用编辑器(vi 或 nano)创建名为“hello.c”的源文件

 #include <stdio.h>
 int main (void)
 {  
 	printf ("Hello World!n");  
 	return 0;
 }

我们可以使用以下命令编译它: $ gcc -o hello hello.c
这表示运行 gcc,GNU C 编译器,使用名为“hello.c”的源文件创建一个名为“hello”的可执行程序。如果一切正常,会收到提示。与许多其他 Linux 命令一样,如果没有错误,gcc 什么也不会说。我们可以从命令行运行此命令,它将生成预期的输出。

 $ ./hello Hello World!

您需要指定“hello”程序的完整或部分路径。在这种情况下,我们输入“./”表示可以在当前目录中找到该程序如果没有指定完整路径或部分路径,bash 命令 shell 将在 $PATH 环境变量中列出的目录中搜索该程序。在这种情况下,bash 会说“找不到命令”。
这看起来很简单,但在幕后却发生了很多事情。如果将“-v”选项添加到gcc 命令,它将列出编译、汇编和链接程序所需的所有步骤。GCC 知道在哪里可以找到“stdio.h”包含文件以及包含“printf”函数和创建可执行程序所需的其他函数的库。如果您想查看 gcc 将搜索包含文件的目录,请运行以下命令:

$ cpp -Wp,-v /dev/null

您将看到,除其他外,gcc 将搜索 /usr/include 以找到 stdio.h。可以通过使用 -print-search-dirs 选项运行 gcc 来列出搜索到的库

4.2 在主机上编译

让我们在开发系统上做同样的事情:创建项目目录并创建“hello.c”源文件。(或者,您可以使用scp 将文件从 RPi 复制到您的开发系统。)
确保您已将 PATH 环境变量设置为包括我们之前使用的 Raspberry Pi 工具链目录,“~/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x86/bin”。完成此操作后,我们可以使用交叉编译器编译源文件:

$ arm-linux-gnueabihf-gcc -o hello hello.c

(如果您的手指厌倦了输入 gcc 编译器的长名称,您可以通过输入命令“ alias arm-gcc arm-linux-gnueabihf-gcc ”来创建一个较短的别名
我们可以使用“ scp hello pi@rpi:/tmp ”将可执行文件复制到 RPi 系统上的 /tmp 目录。在 RPi 系统上,我们可以运行“ /tmp/hello ”获得与本地编译版本相同的结果
如果您列出 arm-gcc 将搜索的目录以查找包含文件或库,您将看到列出的是工具链目录下的目录,而不是开发系统的目录,这个很重要;
我们想为 ARM 系统使用 include 文件和库,而不是 x86 主机上使用的那些

我们需要确保这两个环境,原生开发环境和交叉开发环境是同步的。当它们不同步时,可能是通过使用为不同版本的目标库构建的交叉编译器,或者可能是在更新目标库后没有相应更新交叉开发库,奇怪的事情就会发生。程序可能无法在目标上执行,或者运行时出现错误,或者调试时出现混乱。
交叉开发平台
目标系统开发

4.2.1 必要的工具

开始之前,要确保在 Linux 系统上安装了所有必要的工具。需要一个编译器,即 gcc、binutils 包和一个文本编辑器或一个 IDE。要选择文本编辑器还是某种 IDE 很大程度上取决于您的偏好。根据所使用的 Linux 发行版和安装选项,您可能已经安装了必要的工具。
这里有一个小脚本来帮助您查看是否安装了所有必需的开发工具

#!/bin/sh
gcc -v
if [ $? != 0 ]; then
       echo "GCC is not installed!"
fi
ld -v
if [ $? != 0 ]; then
        echo "Please install binutils!"
fi

将此脚本保存为 devtoolscheck.sh,运行它:

$ sh devtoolscheck.sh

在Fedora机器上,可以得到以下输出:

$ sh devtools.sh 
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.6.1/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.6.1-4' --with-bugurl=
file:///usr/share/doc/gcc-4.6/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++,go 
--prefix=/usr --program-suffix=-4.6 --enable-shared --enable-multiarch 
[config options snipped]
Thread model: posix
gcc version 4.6.1 (Debian 4.6.1-4) 
GNU ld (GNU Binutils for Debian) 2.21.52.20110606

为什么需要 binutils 二进制文件和 gcc,您很快就会看到。现在让我们稍微关注一下“编辑器与 IDE”的问题。

在这方面,我们唯一会建议您的是“使用您觉得舒服的东西,而不要理会别人告诉您的东西”。这件事是非常主观的,它取决于很多因素。例如,如果您在其他操作系统上开发(或曾经开发过),您可能会习惯使用 IDE。您会在 Linux 上找到许多不错的 IDE,包括 Eclipse、Geany、KDevelop 或 Anjuta。尝试安装它们,看看你觉得哪个更合适。另一方面,如果您想采用简单的编辑器方式,这里也有很多选项:vi(m)、emacs、kate、nano、jed 等等。通过搜索 Internet,您会发现很多关于什么是最好的编辑器的讨论。建议您安装其中的几个,然后找出最适合您的。你是唯一的判断者,它将是你经常使用的工具,所以慢慢来,使用它,阅读并熟悉它。无论您作何选择,我们都假定您已就编辑工具做出选择并且熟悉其使用。

4.2.2 编译过程

简而言之,这个过程是从您编写的源代码开始的,如果一切顺利,结果就是可执行二进制文件或库。你现在不需要记住所有的概念,随着所做的工作这些概念会逐渐清晰,在这个阶段,重要的是了解总体思路

假设我们已经编写了源代码,现在我们希望编译器对其进行处理并为我们提供可执行二进制文件。此过程的工作流程如右图所示。
工作流
请注意,它仅适用于 C,C是一种编译语言,与解释语言(Perl、Python、Shell)相反,我们将在这份指南的其余部分严格参考 gcc 和它的相关文件。如上图所示,预处理器 (cpp) 获取您的源代码查找预处理器指令(在 C 语言中,它们以散列开头)如果一切正常,则结果是编译器可以理解的输出。编译器 (gcc) 完成了所有艰巨的工作,包括底层硬件的代码优化(如果您对编译器理论或交叉编译感兴趣,有很多关于该主题的好书,但我们在这里假定初学者水平更高).预处理编译的结果是汇编代码,非常接近机器语言,将从中生成二进制文件(工具也是如此)。最后,根据选项和代码,“ld”会将可执行文件链接到所有必要的库,瞧!最终结果:就是你的程序。
如果您想查看所有生成的中间文件,gcc 标志 -save-temps as 将帮助您这样做。我们建议您至少简要地阅读 gcc 手册页,并确保您的编译器是最新的。

4.2.3 示例 C 程序

每个编程教程都是以“Hello, world”程序开始的
下面这个程序除了打印“Hello, world!”之外什么都不做,信息打印在屏幕上,然后退出,用于说明程序的基本结构和一些基本概念

#include <stdio.h>
/* This is a comment */
int main()
{
    printf("Hello, world!\n");
    return 0;
}

现在,让我们逐行剖析程序,看看每一行代表什么。

  1. 第一个是预处理器指令,它要求提供stdio.h文件,该文件提供printf函数的定义。头文件是通常包含各种定义(函数、变量……)并使 .c 文件不那么混乱的文件。源文件 (.c) 所需要的只是一条#include语句,可能还有一个链接器参数。包含的头文件中定义的所有内容都将在您的源代码中可用。

  2. main()是每个 C 程序中的必备函数。顾名思义,无论您定义了多少函数,主要活动都将在这里发生。int main()意味着这个函数没有任何参数(空括号)并且它返回一个整数(初始int)。所有这些都将在后面讨论。这里最重要的是printf函数,它将我们的文本作为参数并显示它。“ \n” 表示“换行符”,相当于使用 Enter 键(或 ^M)。它被称为转义序列,C 中的所有转义序列都以“\”开头。例如,为了更好地理解转义序列是什么,假设您正在编写 HTML 代码并且您需要打印一个“<”字符。HTML 的语法使用尖括号来定义 HTML 标记,因此您的括号很可能会被解释为 HTML 代码,而不是被显示出来。那么该怎么办?我们用“<”转义它 它会正确显示。同样,如果你想插入一个换行符,你不能直接输入它,因为编译器可能不太关心你是否在一行上编写你的程序,因此你需要转义你的换行符“ \n ”。

  3. return 0告诉编译器一切正常并且main()函数的执行到此结束。这是因为 0 是成功执行的代码,而大于 0 的值(整数)表示出现了问题。开始和结束 main 函数的花括号界定了它的执行块,也就是说,在 main() 中发生的事情保留在main()中。您可能已经注意到语句末尾的分号:它们是强制性的,作为当前语句在那里结束的标志,但它们不能在预处理器指令中用作#include。

4.2.4 汇编

本指南接下来的部分将更详细地讨论编译。但为了完整起见,这里有一个简单的命令行示例,说明如何编译和执行我们的第一个“Hello World”C 程序:

$ gcc -o hello hello.c 
$ ./hello 
Hello, world!

4.2.5 基本 I/O

无论操作系统如何,只要是某种 Unix,以下这些信息都是有效的,但是如果您偶然发现了Linux特有的东西,您就会知道。我们将处理标准输入、输出和错误、深入的 printf() 和文件访问等概念
正如您将看到的,标准 C 库为此定义了一系列函数,并且在阅读了一些内容之后您会发现,没有它您将很难进行开发工作,除非您为了好玩而重写这些函数。最好从一开始就清楚本章所讨论的功能本身并不是 C 语言的一部分;正如我所说,标准 C 库提供了这些接口
输入是在电传打字机上进行的(顺便说一下,设备名称 tty 就是由此而来的),这个过程缓慢而笨拙。任何类 Unix 系统仍然有一些关于 I/O 的历史遗留问题,不仅仅是 I/O,对于本文的其余部分,我们将把 stdin 视为键盘将 stdout/stderr 视为屏幕。您知道可以使用 shell 提供的“>”运算符重定向到文件,但我们暂时对此不感兴趣。

在我们最后开始这篇文章之前,要提醒一点:Mac OS 版本 9 具有一些关于我们主题的独特功能,促使我在开始开发之前阅读一些文档。例如,在所有 Unix(类)系统上,Enter 键都会生成一个 LF(换行符)。在 Windows 上它是 CR/LF ,在 Apple 直到 Mac OS 9 上它是 CR 。简而言之,每个商业 Unix 供应商都试图通过添加功能使他们的操作系统“独一无二”

我们在之前的文章中看过 printf() 以及如何在屏幕上打印文本。我们还看到 scanf() 是从用户那里获取文本的一种方式。对于单个字符,您可以依靠 getchar() 和 putchar()。我们现在将从标准库中包含的头文件中看到一些有用的函数。我们要讨论的第一个标头是ctype.h,它包含用于检查字符大小写或更改字符的函数。请记住,每个标准头文件都有一个手册页,解释可用的函数,而所述函数又有手册页,详细说明返回类型、参数等。

下面是一个使用 tolower() 将字符串中的每个字符转换为小写的示例
你将如何达到相反的目的?

#include <stdio.h> 
#include <ctype.h> 

int main() 
{ 
	int c; /* 读取的字符 */ 
	while ((c = getchar()) != EOF) 
    	putchar (tolower(c)); 
    return 0}

另一个问题是:应该以何种方式修改代码,以便它仅在句子后打印小写结果?也就是说,前提是句子总是以点和空格结尾。

4.2.6 在官方 Debian 存储库中获取一个包

gcc 手册很大而且肯定会让人头疼,但是当你遇到问题时,阅读手册,然后先在网上搜索才是正确的方法没有例外

要进入实际操作,需要的第一个工具显然是编写程序所用语言的编译器
或者,如果程序是用某种解释语言编写的,请确保解释器(Perl, Python、Ruby…) 作为依赖存在。

我们将专注于 C 方面,因为这毕竟是一个 C 开发文章系列,并且会给你一个非详尽的实用程序列表,你最好在你的开发机器上安装:

  1. auto tools* (autoconf, automake, …)
  2. debhelper and dh-make – Debian-specific
  3. devscripts, fakeroot – same, see the Guide for details
  4. a VCS of your choice, depending on the situation at hand – we prefer to take no sides here
  5. gnupg – for digitally signing your packages, mandatory in Debian
  6. lintian – the name is a combination of lint and Debian, so it’s self-explanatory
  7. patch - you should know by know why you’d need it
  8. pbuilder – for creating a chroot
  • 20
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

elsa_balabala

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值