01-应用编程概念与开发环境.md 28 KB


title: 应用编程概念与开发环境 tags: [嵌入式Linux, Linux应用编程, 系统调用, 库函数, glibc, gcc, 交叉编译, IMX6ULL] created: 2026-09-18 updated: 2026-09-18

pdf_ref: "《I.MX6U嵌入式Linux C应用编程指南V1.6》第一章 应用编程概念"

应用编程概念与开发环境

💡 关联知识:[[01-Linux应用编程基础/02-文件IO基础与深入探究]]、[[01-Linux应用编程基础/03-标准IO库]]、[[01-Linux应用编程基础/04-文件属性与目录]];延伸阅读:[[嵌入式Linux驱动开发实战/03-Linux驱动开发核心/01-字符设备驱动基础]]

本篇是应用编程的"地图"。它不教你写具体功能,而是先回答三个绕不开的问题:应用编程到底在和谁打交道(内核)、我们调用的是什么东西(系统调用 / 库函数)、程序从哪里开始、又在哪里结束(main / 返回值 / errno)。最后落地到开发环境:在 Ubuntu 上用 gcc 编译,在 I.MX6ULL 上用交叉编译器编译。


1. 三种编程形态:裸机、驱动、应用

在嵌入式 Linux 平台上做开发,代码大致落在三层上:

形态 运行位置 运行环境 典型载体 典型产物
裸机编程 直接跑在硬件上 无操作系统 51、STM32 单片机 .hex / .bin / .elf
驱动编程 内核空间(内核态) Linux 内核 字符设备、平台设备驱动 .ko 内核模块
应用编程 用户空间(用户态) Linux 应用层 open/read/write 等系统调用 可执行文件(ELF)

三者的根本区别在于硬件操作与用户逻辑是否隔离有没有操作系统做中介

1.1 用"点亮 LED"看三者差异

裸机程序:硬件操作和用户逻辑写在同一份代码里,没有操作系统,编译后直接"裸跑"。

/* LED 裸机程序:硬件操作与用户逻辑混在一起 */
static void led_on(void)
{
    /* 点亮 LED 硬件操作代码:直接写寄存器 */
}

static void led_off(void)
{
    /* 熄灭 LED 硬件操作代码:直接写寄存器 */
}

int main(void)
{
    for ( ; ; ) {
        led_on();       /* 点亮 LED */
        delay();        /* 延时 */
        led_off();      /* 熄灭 LED */
        delay();        /* 延时 */
    }
}

驱动程序:只实现硬件操作,注册到内核;应用层通过系统调用"遥控"它。下面是一个简化的字符设备 LED 驱动骨架。

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

static void led_on(void)
{
    /* 点亮 LED 硬件操作代码 */
}

static void led_off(void)
{
    /* 熄灭 LED 硬件操作代码 */
}

static int led_open(struct inode *inode, struct file *filp)
{
    /* 打开设备时需要做的事情 */
    return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,
                         size_t size, loff_t *offt)
{
    int flag;

    /* 获取应用层 write 的数据,存放在 flag 变量 */
    if (copy_from_user(&flag, buf, size))
        return -EFAULT;

    /* 0 熄灭 LED,非 0 点亮 LED */
    if (flag)
        led_on();
    else
        led_off();

    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
    /* 关闭设备时需要做的事情 */
    return 0;
}

static struct file_operations led_fops = {
    .owner   = THIS_MODULE,
    .open    = led_open,
    .write   = led_write,
    .release = led_release,
};

static int led_probe(struct platform_device *pdev)
{
    /* 驱动加载时需要做的事情 */
    return 0;
}

static int led_remove(struct platform_device *pdev)
{
    /* 驱动卸载时需要做的事情 */
    return 0;
}

static const struct of_device_id led_of_match[] = {
    { .compatible = "alientek,led", },
    { /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, led_of_match);

static struct platform_driver led_driver = {
    .driver = {
        .owner          = THIS_MODULE,
        .name           = "led",
        .of_match_table = led_of_match,
    },
    .probe  = led_probe,
    .remove = led_remove,
};
module_platform_driver(led_driver);

MODULE_DESCRIPTION("LED Driver");
MODULE_LICENSE("GPL");

驱动里的 led_fopsopen / write / release 三个方法注册给内核:应用层调用 open 会执行 led_open,调用 close 会执行 led_release,调用 write 会执行 led_write。约定是写入非 0 点亮、写入 0 熄灭。

应用程序:只写业务逻辑,通过系统调用控制设备,完全不碰寄存器。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int fd;
    int data;

    /* 打开 LED 设备(假定设备文件为 /dev/led) */
    fd = open("/dev/led", O_WRONLY);
    if (0 > fd)
        return -1;

    for ( ; ; ) {
        data = 1;
        write(fd, &data, sizeof(data));   /* 写 1 点亮 LED */
        sleep(1);                         /* 延时 1 秒 */

        data = 0;
        write(fd, &data, sizeof(data));   /* 写 0 熄灭 LED */
        sleep(1);                         /* 延时 1 秒 */
    }

    close(fd);
    return 0;
}

三者关系可以用一张图概括:

flowchart TB
    subgraph 用户态["用户态(User Space)"]
        A["应用程序<br/>open / read / write / close"]
        L["C 标准库 glibc<br/>fopen / fread / printf"]
    end
    subgraph 内核态["内核态(Kernel Space)"]
        S["系统调用接口<br/>sys_open / sys_read ..."]
        D["设备驱动<br/>file_operations"]
        K["内核核心<br/>进程 / 内存 / VFS"]
    end
    HW["硬件<br/>LED / GPIO / 磁盘 / 网卡"]

    A -->|"系统调用陷入"| S
    L -->|"内部再调用系统调用"| S
    S --> D
    S --> K
    D --> HW
    K --> HW

应用与驱动是分离的:单独编译、单独部署,一个跑在用户态、一个跑在内核态。这就是应用编程与裸机编程"质的区别"。


2. 系统调用(System Call)

系统调用是 Linux 内核提供给应用层的应用编程接口(API),是应用层进入内核的入口。不只是 Linux,所有操作系统都会向应用层提供系统调用。

应用程序通过系统调用请求内核"以自己的名义"执行某些事情:打开磁盘文件、读写文件、关闭文件、控制各种硬件外设。应用层与内核的交互路径如下:

flowchart LR
    APP["应用程序"] -->|"调用系统调用 API"| SC["系统调用"]
    SC -->|"请求内核服务"| KER["Linux 内核"]
    KER -->|"操作"| RES["资源:文件 / 设备 / 内存 / 网络"]

几个关键点:

  • 系统调用是内核的一部分,运行在内核态;
  • 应用程序运行在用户态,调用系统调用时会从用户态陷入内核态;
  • 常见的系统调用:openreadwritecloselseekforkioctl 等。

3. 库函数(C 语言库函数)

库函数是 C 语言库提供的应用层函数库。在 Linux 下通常以动态库(.so)形式提供,存放在根文件系统的 /lib 目录下。

库函数与系统调用的关系要分两类看:

  • 有些库函数不调用任何系统调用:如字符串处理函数 strlen()strcat()memcpy()memset()strchr() 等,纯用户空间计算;
  • 有些库函数构建于系统调用之上:如 fopen() 内部调用 open()fread() 内部调用 read()fwrite() 内部调用 write()

既然可以直接用系统调用,为什么还要设计库函数?因为有些系统调用用起来并不方便,C 语言库函数提供了更方便、更好用、且更具可移植性的调用接口。

3.1 系统调用与库函数对比

对比项 系统调用 库函数(C 库)
归属 内核提供给应用层的接口,属于内核一部分 属于应用层
运行空间 调用时从用户态陷入内核态 运行在用户空间
缓存 无缓存 通常有缓存(stdio 缓冲),性能、效率通常更优
可移植性 不同操作系统差异大(定义/功能/参数/返回值往往不同) 不同操作系统接口定义几乎一样,可移植性更好
例子 openreadwriteclose fopenfreadfwritefcloseprintf
flowchart TB
    subgraph 用户空间
        U1["strlen / memcpy(不涉及系统调用)"]
        U2["fopen / fread / fwrite(有 stdio 缓冲)"]
    end
    subgraph 内核空间
        S1["open / read / write ..."]
    end
    U2 -->|"内部封装"| S1
    U1 -.->|"纯用户态计算,无陷入"| U1

实现者角度看,系统调用与库函数有根本区别;但从使用者角度看,区别并不重要,它们都是 C 语言函数。实际编程中两者都会用到,只需知道"我调用的这个函数是系统调用还是库函数"即可。

一句话总结应用编程:开发 Linux 应用程序,通过调用内核提供的系统调用,或使用 C 库函数,来实现具有相应功能的应用程序。


4. 标准 C 语言函数库(glibc)

Linux 下的标准 C 语言函数库是 GNU C 语言函数库(glibc),官方地址:http://www.gnu.org/software/libc/

4.1 确定系统的 glibc 版本

C 语言库以动态库文件形式提供,通常存放在 /lib 目录,命名方式通常是 libc.so.6。注意它是一个软链接文件,会链接到真正的库文件。

Ubuntu 16.04 的库文件实际位于 /lib/x86_64-linux-gnu

ls -l /lib/x86_64-linux-gnu/libc.so.6
# lrwxrwxrwx ... libc.so.6 -> libc-2.23.so

其中 2.23 就是 glibc 的版本号。也可以直接运行共享库获取信息:

/lib/x86_64-linux-gnu/libc.so.6
# GNU C Library ... stable release version 2.23 ...

4.2 获取 glibc 源码

glibc 源码可直接从 git 仓库下载,也可以通过 ftp 下载。想研究某个库函数的具体实现时,获取源码分析即可。


5. main 函数

在 Linux 应用程序中,main 函数是应用程序的入口函数。它有两种常见写法。

5.1 无需传参

int main(void)
{
    /* 代码 */
    return 0;
}

5.2 需要传参

int main(int argc, char **argv)
{
    /* 代码 */
    return 0;
}
  • argc:表示传入参数的个数,包括应用程序自身路径和程序名
  • argv:字符串指针数组,保存每一个参数。

例如运行当前目录下的 hello 并传入参数:

./hello 112233

此时参数个数为 2,且都以字符串形式传递:

下标 含义
argv[0] "./hello" 程序自身路径与名字
argv[1] "112233" 用户传入的第 1 个参数
argv[2] NULL 数组结尾哨兵

完整示例:

#include <stdio.h>

int main(int argc, char **argv)
{
    int i;

    printf("argc = %d\n", argc);
    for (i = 0; i < argc; i++)
        printf("argv[%d] = %s\n", i, argv[i]);

    return 0;
}
gcc -o hello hello.c
./hello 112233 445566
# argc = 3
# argv[0] = ./hello
# argv[1] = 112233
# argv[2] = 445566

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux C应用编程指南》内容,为扩展知识。

main 还有第三种标准写法,用于接收环境变量

> int main(int argc, char **argv, char **envp);
> ```
>
> `envp` 是一个字符串数组,每个元素形如 `"NAME=value"`,以 `NULL` 结尾;它不是 `main` 的形式参数,而是紧跟在 `argv` 数组之后由内核放置的。遍历方法:
>
> ```c
> #include <stdio.h>
>
> int main(int argc, char **argv, char **envp)
> {
>     int i = 0;
>     while (envp[i] != NULL) {
>         printf("%s\n", envp[i]);
>         i++;
>     }
>     return 0;
> }
> ```
>
> 也可以直接使用全局变量 `environ` 或 `getenv("PATH")` 获取环境变量。在应用层最常用的是 `getenv()`,`envp` 主要用于需要整体遍历环境的场景。

---

## 6. 返回值与 errno(初步)

Linux 下几乎所有 API 函数都有返回值,用来告诉调用者"执行成功还是失败"。约定规律:

- 绝大多数函数**成功返回 0,失败返回负数(通常是 -1)**;
- 但并非绝对,例如 `open` 成功返回文件描述符(非负整数)、`read`/`write` 成功返回实际读写字节数、`malloc` 失败返回 `NULL`。

因此常见的判断方式是:

c if (func()) {

/* 执行失败 */

} else {

/* 执行成功 */

}


当我们只知道"失败返回 -1",却不知道失败原因时,就需要 `errno`:

- `errno` 本质是一个 `int` 类型的**全局变量**,每个进程维护自己的 `errno`;
- 错误发生时,操作系统把错误编号赋给 `errno`;**下一次错误会覆盖上一次**,所以要及时读取;
- 并非所有函数出错都会设置 `errno`,这要查 man 手册的返回值描述;
- 使用需包含头文件 `<errno.h>`。

把编号翻译成人话,用 `strerror()` 或 `perror()`:

c #include #include #include

int main(void) {

printf("errno = %d\n", errno);              /* 直接读编号 */
printf("msg   = %s\n", strerror(errno));    /* 转成字符串 */
return 0;

}


`errno`、`strerror`、`perror` 的完整用法(含错误码表)见 [[01-Linux应用编程基础/02-文件IO基础与深入探究]]。

---

## 7. 开发环境:Ubuntu + gcc

本书的示例代码围绕 Ubuntu 编写,推荐 16.04 或 14.04(较稳定),也可用 CentOS、Redhat 等发行版。IDE 可选 Eclipse、VSCode;本书使用 **VSCode + gcc**,与正点原子驱动开发文档保持一致。用什么 IDE 都不重要,哪怕直接用 `vi`,重点是学习应用编程本身。

### 7.1 两个阶段,两套编译器

| 阶段             | 运行目标                      | 编译器             | 产物运行位置      |
| ---------------- | ----------------------------- | ------------------ | ----------------- |
| 入门篇(本模块) | PC 端 Ubuntu                  | 本地 gcc(x86)    | Ubuntu 上直接运行 |
| 提高篇(外设)   | ALPHA/Mini I.MX6U(ARM 架构) | 交叉编译器 ARM gcc | 开发板上运行      |

入门篇的示例都在 Ubuntu 下测试验证,所以用 `vscode + gcc` 即可;当代码要跑到 I.MX6U 这类 ARM 平台上时,必须用 **交叉编译工具(ARM 架构下的 gcc)** 编译,才能得到能在开发板上运行的可执行文件。

### 7.2 gcc 本地编译

bash gcc -o testApp_1 testApp_1.c


- `gcc`:Ubuntu 下使用的 C 语言编译器;
- `-o`:指定生成的可执行文件名;
- `testApp_1.c`:待编译的 C 源文件。

### 7.3 交叉编译

bash

以 arm-linux-gnueabihf 工具链为例(I.MX6U 属于 Cortex-A7,带硬件浮点)

arm-linux-gnueabihf-gcc -o testApp_1 testApp_1.c

把可执行文件拷贝到开发板运行

scp testApp_1 root@192.168.1.10:/home/root/


> ⚠️ **来源说明**:本节不属于《I.MX6U嵌入式Linux C应用编程指南》内容,为扩展知识。
>
> **gcc 的编译流程**可分为四步,每一步都可以单独执行、单独观察:
>
> ```bash
> # 1) 预处理:展开宏、头文件,去掉注释  -> .i
> gcc -E hello.c -o hello.i
> # 2) 编译:把 C 转成汇编              -> .s
> gcc -S hello.i -o hello.s
> # 3) 汇编:把汇编转成目标文件          -> .o
> gcc -c hello.s -o hello.o
> # 4) 链接:把目标文件、库链接成可执行文件
> gcc hello.o -o hello
>
> # 也可一步到位
> gcc hello.c -o hello
> ```
>
> ```mermaid
> flowchart LR
>     C["hello.c<br/>源文件"] -->|"gcc -E 预处理"| I["hello.i<br/>预处理结果"]
>     I -->|"gcc -S 编译"| S["hello.s<br/>汇编代码"]
>     S -->|"gcc -c 汇编"| O["hello.o<br/>目标文件"]
>     O -->|"gcc 链接(+ libc)"| E["hello<br/>可执行文件"]
> ```
>
> 常见选项:`-Wall`(开警告)、`-O2`(优化)、`-g`(带调试信息)、`-D宏`(命令行定义宏)、`-I路径`(指定头文件目录)、`-L路径 -l库名`(指定库路径与库)。交叉编译只是把 `gcc` 换成对应的 `arm-...-gcc`,流程完全一致。

### 7.4 程序运行

编译产物是可在目标平台运行的 ELF 文件。在 Ubuntu 下:

bash ./testApp_1


在开发板上:把可执行文件放到文件系统(如通过 `scp`、NFS、SD 卡),修改可执行权限后运行:

bash chmod +x testApp_1 ./testApp_1


---

## 8. 完整示例源码与逐段解释

下面这份程序把本篇的几个要点串起来:`main(argc, argv)` 接收参数、`open/write/close` 系统调用、返回值与 `errno` 判断。它模拟"向 LED 设备写入数据"的应用层逻辑,并打印参数。

c #include #include #include #include #include #include #include

int main(int argc, char **argv) {

int fd;
int data;
int i;

/* 1. 打印命令行参数,验证 main 的 argc / argv */
printf("argc = %d\n", argc);
for (i = 0; i < argc; i++)
    printf("argv[%d] = %s\n", i, argv[i]);

/* 2. 以只写方式打开设备文件(这里用普通文件代替 /dev/led) */
fd = open("./led_dev", O_WRONLY | O_CREAT, 0664);
if (-1 == fd) {
    /* 3. 出错时用 strerror 把 errno 翻译成可读信息 */
    printf("open error: %s\n", strerror(errno));
    return -1;
}

/* 4. 写入非 0 表示点亮 */
data = 1;
if (sizeof(data) != write(fd, &data, sizeof(data))) {
    printf("write error: %s\n", strerror(errno));
    close(fd);
    return -1;
}

printf("LED on command sent\n");

/* 5. 显式关闭文件,释放文件描述符 */
close(fd);
return 0;

}


逐段解释:

| 行    | 代码                                   | 说明                                                                                                                                                                      |
| ----- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1-7   | `#include ...`                         | `open` 需要 `<sys/types.h>`、`<sys/stat.h>`、`<fcntl.h>`;`write`/`close` 需要 `<unistd.h>`;`printf` 需要 `<stdio.h>`;`errno`/`strerror` 需要 `<errno.h>`、`<string.h>` |
| 11    | `int main(int argc, char **argv)`      | 需要接收命令行参数,因此用带参写法                                                                                                                                        |
| 17-18 | 参数打印循环                           | `argc` 是参数个数,`argv[i]` 是第 i 个字符串                                                                                                                              |
| 21    | `open(..., O_WRONLY \| O_CREAT, 0664)` | 只写打开,不存在则创建,权限 `rw-rw-r--`                                                                                                                                  |
| 22-25 | `if (-1 == fd)`                        | `open` 失败返回 -1;用 `strerror(errno)` 得到原因                                                                                                                         |
| 30-34 | `write(fd, &data, sizeof(data))`       | 写入 4 字节;返回值是实际写入字节数                                                                                                                                       |
| 39    | `close(fd)`                            | 不再使用时显式关闭,归还文件描述符                                                                                                                                        |

编译与运行:

bash gcc -Wall -o testApp testApp.c ./testApp hello 123


预期输出:

text argc = 3 argv[0] = ./testApp argv[1] = hello argv[2] = 123 LED on command sent


---

## 9. 实验步骤与调试方法

### 9.1 实验步骤

1. 在 Ubuntu 下建工作目录并创建源文件:

bash mkdir -p ~/vscode_ws/1_chapter cd ~/vscode_ws/1_chapter


2. 用 VSCode 打开该目录,创建 `testApp.c`,粘贴上面的源码。
3. 在 VSCode 菜单"终端 → 新终端"打开终端,编译:

bash gcc -Wall -o testApp testApp.c


4. 运行并传入参数:

bash ./testApp hello 123


5. 观察 `argc/argv` 输出,以及 `led_dev` 文件是否被创建(`ls -l led_dev`)。

### 9.2 调试方法

| 现象                                    | 可能原因                             | 排查手段                                       |
| --------------------------------------- | ------------------------------------ | ---------------------------------------------- |
| 编译报 `undefined reference to 'open'`  | 漏包含头文件或库未链接               | 检查 `#include <fcntl.h>` 等;确认写法         |
| `open error: No such file or directory` | 路径错误或文件不存在且未加 `O_CREAT` | `ls` 确认路径;加 `O_CREAT`                    |
| `open error: Permission denied`         | 权限不足                             | 检查文件权限 `ls -l`,换路径或提权             |
| 参数打印不对                            | `argv` 下标越界                      | 循环条件用 `i < argc`                          |
| 程序运行无输出                          | 可执行文件无执行权限                 | `chmod +x testApp`                             |
| 想知道失败原因                          | 只看返回值不够                       | 加 `perror("open error")` 或 `strerror(errno)` |

调试常用命令:

bash gcc -Wall -Wextra -o testApp testApp.c # 打开更多警告 man 2 open # 查看系统调用手册(2 系统调用、3 库函数) echo $? # 查看上一条命令/程序的退出状态 ```

man 手册章节编号:

编号 内容
1 Linux 命令
2 系统调用
3 标准 C 库函数

10. 跨平台对比:IMX6ULL vs STM32 vs RK3568

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux C应用编程指南》内容,为扩展知识。

维度 I.MX6ULL(本教程) STM32(裸机/RTOS) RK3568
内核 Cortex-A7 Cortex-M3/M4/M7 Cortex-A55
有无 MMU 有,跑完整 Linux 无,通常裸机或 RTOS 有,跑完整 Linux
运行模式 用户态 / 内核态 单一特权模式 用户态 / 内核态
应用编程形态 系统调用 + glibc,ELF 可执行文件 直接寄存器操作 + HAL/LL 库,main 永不返回 系统调用 + glibc,ELF 可执行文件
交叉工具链 arm-linux-gnueabihf-gcc arm-none-eabi-gcc(无 OS 支撑) aarch64-linux-gnu-gcc
启动入口 内核加载 ELF/脚本 → _startmain 复位向量 → 启动文件 → main 内核加载 → _startmain
文件系统 有(ext4/UBIFS/rootfs) 无(或用 FatFs 访问 SD 卡)
main 之外 进程、内存、文件、网络全部由内核提供 所有资源自己管理 同 I.MX6ULL
迁移要点 系统调用/库函数接口在 Linux 间基本通用 与 Linux 应用是两套完全不同的编程模型 与 I.MX6ULL 代码几乎一致,注意 64 位与库版本

结论:I.MX6ULL 与 RK3568 同属"Linux 应用编程"范畴,本篇所学的系统调用、glibc、gcc/交叉编译流程可直接复用;STM32 裸机则是另一套世界观,没有系统调用、没有文件描述符、没有 errno,切忌混用概念。


11. 面试精选

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux C应用编程指南》内容,为扩展知识。

Q1:系统调用和库函数有什么区别?

  • 归属不同:系统调用是内核提供给应用层的接口,属于内核的一部分;库函数属于应用层。
  • 运行空间不同:库函数运行在用户空间;调用系统调用会由用户态陷入内核态。
  • 缓存不同:库函数通常有缓存(如 stdio 缓冲),系统调用无缓存,因此在性能和效率上库函数通常更优。
  • 可移植性不同:不同操作系统的系统调用在定义、功能、参数、返回值上往往不同;而 C 库函数在各系统间接口几乎一致,可移植性更好。
  • 联系:部分库函数构建于系统调用之上,如 fopen 内部调用 openfread 调用 readfwrite 调用 write;也有库函数(strlenmemcpy)完全不涉及系统调用。
  • 从使用者角度看,二者都是 C 函数,实际编程中都会用到,知道自己在调用哪一类即可。

Q2:什么是用户态和内核态?应用程序调用 open 时发生了什么?

:操作系统下有两种状态:内核态与用户态,应用程序运行在用户态、内核运行在内核态。应用程序调用 open 时,会通过系统调用从用户态陷入内核态,内核根据路径找到文件、分配文件描述符并完成打开操作,然后把结果(文件描述符或 -1)返回给用户态。涉及硬件和资源的操作必须由内核在内核态完成,用户态程序不能直接访问。

Q3:main 函数的 argcargv 各是什么?执行 ./app a b 后它们的值是多少?

argc 是传入参数个数,包含程序自身路径与程序名argv 是字符串指针数组。执行 ./app a bargc == 3argv[0] == "./app"argv[1] == "a"argv[2] == "b",且 argv[argc] == NULL。因此遍历时循环条件应写 i < argc

Q4:函数返回 -1 就代表失败,但如何知道失败原因?

:Linux 为常见错误做了编号,函数出错时操作系统会把错误编号写入进程私有的全局变量 errnoint 类型,下次出错会被覆盖,要及时读取)。包含 <errno.h> 即可使用 errno;用 strerror(errno) 可把它转成可读字符串,用 perror("...") 可直接打印并自动附加冒号和错误描述。需要注意并非所有函数出错都会设置 errno,需查阅 man 手册的返回值说明。

Q5:入门篇用 gcc,提高篇为什么要用交叉编译?两者产物有何不同?

:入门篇示例在 PC 端 Ubuntu 上运行验证,CPU 是 x86 架构,用本机 gcc 编译出的 ELF 可直接在 Ubuntu 运行。提高篇的代码要在 ALPHA/Mini I.MX6U 开发板上运行,板子是 ARM 架构,指令集与 x86 不同,必须使用面向 ARM 的交叉编译器(ARM 架构下的 gcc)编译,才能生成在开发板上运行的可执行文件。二者编译流程一致,区别只在于编译器目标架构和最终产物运行的平台。


内容来源:《I.MX6U嵌入式Linux C应用编程指南》第一章 应用编程概念