Linux下用C语言调用gun函数如何入门?

99ANYc3cd6
预计阅读时长 30 分钟
位置: 首页 C语言 正文

目录

  1. 为什么选择 Linux + C?
  2. 开发环境搭建
    • 编译器:GCC
    • 构建工具:Make
    • 编辑器/IDE:Vim, VS Code, CLion
  3. 核心概念:Linux 与 C 的关系
    • 一切皆文件
    • 系统调用
    • GNU C Library (glibc)
  4. C 语言基础在 Linux 中的应用
    • stdio.h 与标准输入/输出
    • stdlib.h 与进程控制
    • unistd.h 与 POSIX 标准
    • sys/stat.h 与文件/目录信息
  5. 系统编程入门
    • 文件 I/O (open, read, write, close)
    • 进程控制 (fork, exec, wait)
    • 信号处理 (signal)
  6. 一个实战项目:简单的 Shell
  7. 调试与性能分析
    • GDB 调试器
    • Valgrind 内存检测
    • strace 系统调用追踪
  8. 总结与学习路径

为什么选择 Linux + C?

  • 性能极致:C 是编译型语言,没有解释型语言的额外开销,可以直接操作内存和硬件,非常适合操作系统、嵌入式、高性能服务器等场景。
  • 掌控力强:你可以精确控制程序的每一个细节,从内存管理到系统资源调度。
  • 系统级开发:Linux 内核本身以及绝大多数系统工具(如 ls, grep, vim)都是用 C 语言编写的,学习 Linux C 就是学习系统本身是如何工作的。
  • 跨平台基础:虽然代码可能需要少量修改,但 C 语言的设计哲学是“一次编写,到处编译”,是许多其他语言(如 Python, Go)的底层实现基础。
  • 庞大的生态:拥有无与伦比的库(如 glibc, pthreads)和工具链支持。

开发环境搭建

在 Linux 上,C 语言开发环境非常成熟和便捷。

linux gun c语言
(图片来源网络,侵删)

编译器:GCC (GNU Compiler Collection)

GCC 是最主流的 C/C++ 编译器。

安装 (以 Debian/Ubuntu 为例):

sudo apt update
sudo apt install build-essential  # 这会自动安装 gcc, g++, make 等核心工具

编译一个简单的 C 程序 (hello.c):

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

编译命令:

linux gun c语言
(图片来源网络,侵删)
# 生成可执行文件 a.out (默认)
gcc hello.c
# 指定输出文件名
gcc hello.c -o hello
# 运行
./hello

常用编译选项:

  • -Wall: 显示所有警告。
  • -g: 生成调试信息,用于 GDB 调试。
  • -O2: 进行优化,提升程序运行速度。
  • -static: 静态链接,生成的可执行文件不依赖外部库,体积较大但便携。

构建工具:Make

当项目文件变多时,手动编译会变得非常繁琐。MakeMakefile 可以自动化这个过程。

一个简单的 Makefile 示例:

# 目标: 依赖
# [TAB] 命令
hello: hello.c
    gcc -o hello hello.c
# 清理生成的文件
clean:
    rm -f hello

使用 Make:

linux gun c语言
(图片来源网络,侵删)
# 执行 'hello' 目标(默认执行第一个目标)
make
# 或者直接指定
make hello
# 执行 'clean' 目标
make clean

编辑器/IDE

  • Vim/Neovim: 经典、高效、高度可定制的编辑器,适合追求速度和键盘操作的开发者。
  • VS Code (Visual Studio Code): 轻量级,拥有强大的扩展生态(如 C/C++ 扩展),提供代码补全、调试、集成终端等现代化功能,是初学者的绝佳选择。
  • CLion: JetBrains 出品的商业 C/C++ IDE,功能强大,提供智能重构、深度代码分析等,适合大型项目开发(有免费试用版)。

核心概念:Linux 与 C 的关系

一切皆文件

这是 Unix/Linux 的核心理念之一,无论是普通文件、目录、硬件设备(如键盘、屏幕、硬盘),还是网络套接字,在 Linux 中都可以通过文件描述符来统一操作,你用 read()write() 函数读取键盘输入和向屏幕输出,其底层逻辑与读写文件是完全一样的。

系统调用

操作系统内核提供了一系列接口,允许用户程序请求内核服务(如创建进程、读写文件),这些接口就是系统调用,C 语言库函数(如 printf, fopen)在底层通常会封装一个或多个系统调用。

  • printf -> write (系统调用)
  • fopen -> open (系统调用)

查看系统调用的工具:strace

GNU C Library (glibc)

glibc 是 Linux 上最主要的 C 标准库实现,它不仅提供了 C 标准函数(如 printf, malloc),还提供了大量 Linux 系统调用的封装(Wrapper Functions),使得我们可以用更友好的 C 函数来调用内核功能。

C 语言基础在 Linux 中的应用

stdio.h 与标准输入/输出

  • printf(): 格式化输出到标准输出。
  • scanf(): 从标准输入读取格式化数据。
  • getchar(), putchar(): 逐个字符地输入/输出。

stdlib.h 与进程控制

  • system(): 执行一个 shell 命令。
    #include <stdlib.h>
    system("ls -l"); // 执行 ls -l 命令
  • exit(): 终止当前进程。

unistd.h 与 POSIX 标准

unistd.h 是 POSIX 标准中定义的 C 语言头文件,包含了大量 Unix/Linux 系统服务的接口。

  • getpid(): 获取当前进程 ID。
  • getppid(): 获取父进程 ID。
  • fork(): 创建一个新进程(见下文)。
  • write(), read(): 进行文件 I/O 操作(见下文)。

sys/stat.h 与文件/目录信息

  • stat(): 获取文件或文件状态的结构体。

系统编程入门

这是 Linux C 开发的核心,直接与内核交互。

文件 I/O

不同于 fopen/fclose/fread/fwrite(由 C 库提供,会进行缓冲),这里的 open/read/write/close 是直接的系统调用,更底层、更灵活。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
    int fd; // file descriptor
    char buf[256] = "Hello from low-level I/O!\n";
    // O_WRONLY: 只写, O_CREAT: 如果文件不存在则创建, O_TRUNC: 如果文件存在则清空
    fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644); // 0644是权限
    if (fd == -1) {
        perror("open failed");
        return 1;
    }
    // 写入文件
    ssize_t bytes_written = write(fd, buf, sizeof(buf));
    if (bytes_written == -1) {
        perror("write failed");
        return 1;
    }
    printf("Wrote %zd bytes.\n", bytes_written);
    // 关闭文件
    close(fd);
    return 0;
}
  • perror(): 打印一个描述性的错误信息,非常实用。

进程控制

这是 Linux C 最经典和强大的部分。

  1. fork(): 创建子进程,调用一次,返回两次。

    • 在父进程中,fork() 返回子进程的 PID (一个正整数)。
    • 在子进程中,fork() 返回 0。
    • 如果失败,返回 -1。
    • 子进程会复制父进程的代码段、数据段、堆栈,但拥有独立的 PID。
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/wait.h>
    int main() {
        pid_t pid = fork();
        if (pid < 0) {
            perror("fork failed");
        } else if (pid == 0) {
            // 子进程代码
            printf("Child process: My PID is %d, Parent PID is %d\n", getpid(), getppid());
            sleep(2); // 模拟子进程工作
        } else {
            // 父进程代码
            printf("Parent process: My PID is %d, Child PID is %d\n", getpid(), pid);
            // 等待子进程结束
            int status;
            waitpid(pid, &status, 0); // WNOHANG 可以让父进程不等待,继续执行
            printf("Child process has finished.\n");
        }
        return 0;
    }
  2. exec() 系列函数: 用一个新的程序替换当前进程的映像,它不会创建新进程,只是“变身”。

    • 常用 execlp, execvpp 表示会从 PATH 环境变量中寻找程序。
    • 通常与 fork() 一起使用:fork() 创建子进程,然后在子进程中调用 exec() 执行新程序,父进程则 wait()
  3. wait() / waitpid(): 父进程调用,用于等待子进程结束,并回收其资源(避免僵尸进程)。

信号处理

信号是 Linux 中一种异步通信机制,用于通知进程发生了某个事件。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handle_sigint(int sig) {
    printf("\nCaught signal %d (SIGINT)\n", sig);
    // 在这里可以做一些清理工作
    // 然后正常退出
    _exit(0); 
}
int main() {
    signal(SIGINT, handle_sigint); // 注册信号处理函数
    printf("Process running. Press Ctrl+C to send SIGINT.\n");
    while (1) {
        sleep(1);
    }
    return 0;
}

编译并运行它,然后按 Ctrl+C,你会看到程序捕获到了信号并执行了自定义的函数。

一个实战项目:简单的 Shell

一个简单的 Shell 可以让你深刻理解 fork, exec, wait 的协作。

逻辑:

  1. 打印一个命令提示符 (如 my_shell$)。
  2. 读取用户输入的一行命令。
  3. 解析命令和参数。
  4. 创建一个子进程 (fork)。
  5. 在子进程中,用 execvp 执行用户输入的命令。
  6. 在父进程中,wait 等待子进程执行完毕。
  7. 回到步骤 1。
// simple_shell.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#define BUFFER_SIZE 1024
int main() {
    char command[BUFFER_SIZE];
    char *args[BUFFER_SIZE / 2 + 1]; // 假设参数不会太多
    while (1) {
        // 1. 打印提示符
        printf("my_shell$ ");
        fflush(stdout); // 确保提示符立即显示
        // 2. 读取输入
        if (fgets(command, BUFFER_SIZE, stdin) == NULL) {
            if (feof(stdin)) { // 用户输入了 EOF (Ctrl+D)
                printf("\n");
                break;
            } else {
                perror("fgets error");
                continue;
            }
        }
        // 3. 解析命令和参数
        int i = 0;
        char *token = strtok(command, " \n"); // 按空格和换行符分割
        while (token != NULL && i < BUFFER_SIZE / 2) {
            args[i++] = token;
            token = strtok(NULL, " \n");
        }
        args[i] = NULL; // execvp 要求参数列表以 NULL 
        if (i == 0) continue; // 空输入,继续循环
        // 4. fork
        pid_t pid = fork();
        if (pid == -1) {
            perror("fork failed");
            continue;
        }
        if (pid == 0) { // 子进程
            // 5. execvp
            if (execvp(args[0], args) == -1) {
                perror("execvp failed");
                exit(EXIT_FAILURE);
            }
        } else { // 父进程
            // 6. wait
            waitpid(pid, NULL, 0);
        }
    }
    return 0;
}

调试与性能分析

GDB (GNU Debugger)

GDB 是功能强大的命令行调试器。

编译时带上 -g 选项:

gcc -g hello.c -o hello

启动 GDB:

gdb ./hello

常用 GDB 命令:

  • break mainb main: 在 main 函数处设置断点。
  • runr: 运行程序。
  • nextn: 单步执行(不进入函数)。
  • steps: 单步执行(进入函数)。
  • print ip i: 打印变量 i 的值。
  • continuec: 继续运行,直到下一个断点。
  • quitq: 退出 GDB。

Valgrind

Valgrind 是一套用于调试内存问题的工具集,最著名的是它的内存泄漏检测器 memcheck

安装:

sudo apt install valgrind

使用:

valgrind --leak-check=full ./your_program

它会详细报告内存泄漏、非法内存访问等问题。

strace

strace 可以追踪程序执行过程中的所有系统调用和信号。

使用:

strace ./hello

你会看到 hello 程序从 execve 加载,到 write 打印信息,再到 exit 退出的全过程,非常有助于理解程序和系统内核的交互。

总结与学习路径

  • 基础: 熟练掌握 C 语言语法、指针、内存管理。
  • Linux API: 深入学习 man 手册,在终端输入 man 2 open 查看 open 系统调用的手册页(man 3 printf 查看 C 库函数)。
  • 动手实践: 从写简单的文件操作程序开始,然后是进程控制,最后尝试实现一个简单的 Shell。
  • 阅读源码: 阅读一些开源的小型 C 项目,coreutils 包里的一些工具(如 cat, ls 的简化版),或者 suckless 组织的软件(如 dwm, st),它们的代码量小且设计精巧。
  • 深入内核: 当你对应用层系统编程非常熟悉后,可以开始阅读 Linux 内核源码,了解驱动、调度、内存管理等更深层次的知识。

Linux C 语言开发是一个广阔而深入的领域,但只要打好基础,多动手实践,你就能逐步掌握它的精髓,成为一名强大的系统级开发者。

-- 展开阅读全文 --
头像
织梦仿大前段博客模板效果如何?
« 上一篇 今天
织梦更新首页后为何没变化?
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

目录[+]