嵌入式编程题

1.

⽂件编程实例:打开⼀个⽂件,向⽂件写⼊三个字符串;然后重新定位⽂件流读写指针到⽂件起始位置,从⽂
件读取刚写⼊的三个字符串到另⼀个缓冲,并且打印读出来的字符串。

#include <stdio.h>

int main() {
    // 声明一个指向文件的指针变量fp
    FILE *fp = NULL;
    
    // 声明一个字符串数组buf,用于存储文件的内容
    const char *buf[3] = {
        "This is the first line\n",
        "Second line\n",
        "OK, the last line\n"
    };
    
    // 声明一个二维字符数组tmp_buf,为题干中的“另一个缓冲”,用于存储从文件中读取的内容
    char tmp_buf[3][64];
    
    // 声明一个整型变量i,用于循环计数
    int i;
    
    // 打开名为text.dat的文件,并将文件指针赋值给fp
    // 使用"w+b"模式打开文件,以二进制读写方式打开,文件不存在则创建它
    fp = fopen("text.dat", "w+b");
    // "w+b" 模式用于以读写方式打开一个文件,如果文件已存在,则其内容被截断,即清空文件内容。如果文件不存在,则创建新文件。
    
    // 检查文件是否成功打开,如果打开失败,则打印错误消息并返回-1
    if (fp == NULL) {
        printf("Cannot open file\n");
        return -1;
    }
    
    // 将buf数组中的字符串逐行写入文件
    for (i = 0; i < 3; i++) {
        // fputs函数用于将字符串写入文件,直到遇到字符串结尾的空字符'\0'
        fputs(buf[i], fp);
    }
    
    // 清空缓冲区并刷新输出缓冲区,确保所有写入操作都已执行
    fflush(fp);
    
    // fseek函数用于在文件中进行定位,将文件指针fp重新定位到文件起始位置
    // SEEK_SET表示从文件的开头开始计算偏移量
    fseek(fp, 0, SEEK_SET);
    
    // 从文件中逐行读取内容并存储在tmp_buf数组中
    for (i = 0; i < 3; i++) {
        // fgets函数用于从文件中读取字符串,最多读取64个字符
        fgets(tmp_buf[i], sizeof(tmp_buf[i]), fp);
        // 打印读取的内容
        printf("%s", tmp_buf[i]);
    }
    
    // 关闭文件
    fclose(fp);
    return 0;
}

函数解析:

在提供的C语言程序中,使用了多个标准库函数,下面是对这些函数的解析:

  1. #include <stdio.h>

    • 预处理指令,用于引入标准输入输出库函数的声明。
  2. FILE *fp = NULL;

    • 声明一个指向 FILE 结构的指针 fp,用于后续操作文件。
  3. fopen(const char *filename, const char *mode);

    • 函数用于打开文件,并返回指向该文件的 FILE 指针。如果失败,返回 NULL
    • 第一个参数是文件名,第二个参数是打开模式。
  4. fputs(const char *str, FILE *stream);

    • 函数用于将字符串写入到指定的文件流中,直到遇到字符串结束符 '\0'
  5. fflush(FILE *stream);

    • 函数用于刷新输出缓冲区,确保所有输出操作都已执行。
  6. fseek(FILE *stream, long offset, int whence);

    • 函数用于在文件中移动文件位置指针。
    • 第二个参数是偏移量,第三个参数 whence 指定了偏移量的计算方式:
      • SEEK_SET:从文件开头开始计算。
      • SEEK_CUR:从当前位置开始计算。
      • SEEK_END:从文件末尾开始计算。
  7. fgets(char *str, int num, FILE *stream);

    • 函数用于从指定的文件流中读取字符串,最多读取 num - 1 个字符加上一个结束符 '\0'
  8. printf(const char *format, ...);

    • 函数用于格式化输出到标准输出(通常是屏幕)。
  9. fclose(FILE *stream);

    • 函数用于关闭指定的文件流,并释放所有与该文件相关的资源。
  10. return -1;

    • 用于从 main 函数返回一个整数值 -1,通常表示程序执行失败。
  11. int main() { ... }

    • 程序的入口点,main 函数的返回类型是 int,表示程序的退出状态。
  12. int i;

    • 声明一个整型变量 i,用于循环计数。

请注意,程序中使用 fflush(fp); 可能不是必要的,因为 fputs 在写入字符串后会自动刷新缓冲区。然而,在某些情况下,如果需要确保数据立即写入磁盘,可以使用 fflush。此外,fflush 调用在读取文件之前是不必要的,因为读取操作不会影响缓冲区的状态。

2.

程序创建两个进程,在⽗进程和⼦进程之间通过管道传递数据,⽗进程向⼦进程发送字符串“exit”表示让⼦进程退出,并且等待⼦进程返回;⼦进程查询管道,当从管道读出字符串“exit”时结束

#include <stdio.h>    // 包含用于标准输入输出的函数,如printf和scanf
#include <stdlib.h>   // 包含用于内存分配和进程控制的函数,如malloc、free、exit
#include <unistd.h>   // 包含UNIX标准函数,如read、write、fork、pipe、sleep
#include <string.h>   // 包含用于字符串操作的函数,如strcmp、strlen
#include <sys/types.h>// 包含一些基本的数据类型定义,如pid_t
#include <sys/wait.h> // 包含等待进程状态的函数,如waitpid

int main() {
    int pipefds[2];    // 声明一个整型数组,用于存储管道的文件描述符  0代表读端,1代表写端
    pid_t pid;         // 声明一个pid_t类型的变量,用于存储fork()的返回值
    char buf[5];       // 声明一个字符数组,用于从管道中读取数据
    const char *cmd = "exit";  // 声明一个字符串常量,用于存储要发送的命令

    // 使用pipe函数创建一个管道,并将文件描述符存储在pipefds数组中
    // pipe(pipefds): 这是一个调用 pipe 函数的表达式。pipe 函数用于创建一个管道(pipe),这是一种特殊的文件描述符,允许两个进程通过一个通道进行单向通信。pipefds 是一个整型数组,它将被填充为两个文件描述符:pipefds[0] 用于读取,pipefds[1] 用于写入
    if (pipe(pipefds) == -1) {
        perror("pipe"); // 如果pipe函数失败,输出错误信息
        exit(EXIT_FAILURE); // 退出程序并返回失败状态
    }

    // 使用fork函数创建一个新的进程
    pid = fork();
    if (pid == -1) {
        perror("fork"); // 如果fork函数失败,输出错误信息
        exit(EXIT_FAILURE); // 退出程序并返回失败状态
    }

    if (pid == 0) {  // 如果pid为0,表示当前是子进程
        close(pipefds[1]);  // 子进程不需要写端,关闭它以避免错误
        // 从管道的读端读取数据,最多读取4个字节加一个空字符 '\0'
        read(pipefds[0], buf, sizeof(buf));
        // 如果读取的数据与cmd相同,表示接收到退出命令
        if (strcmp(buf, cmd) == 0) {
            printf("Child process received 'exit' command and will exit.\n");
        }
        close(pipefds[0]);  // 子进程操作完成后关闭读端
        _exit(EXIT_SUCCESS);  // 使用_exit立即退出子进程,不进行任何清理工作
    } else {  // 如果pid不为0,表示当前是父进程
        close(pipefds[0]);  // 父进程不需要读端,关闭它
        sleep(1);  // 让子进程先执行,这里使用1秒的睡眠时间
        // 向管道的写端写入"exit"命令,包括字符串末尾的空字符 '\0'
        write(pipefds[1], cmd, strlen(cmd) + 1);
        close(pipefds[1]);  // 写入完成后关闭写端

        // 使用waitpid等待子进程结束,这里使用0作为选项,表示等待任意一个子进程
        int status;
        waitpid(pid, &status, 0);
        // 检查子进程是否正常退出,并输出退出状态
        if (WIFEXITED(status)) {
            printf("Child process exited with status %d.\n", WEXITSTATUS(status));
        }
    }

    return 0; // 父进程结束,返回0表示成功
}

函数解析

以下是代码中出现的每个函数及其解析:

  1. pipe(pipefds):

    • 功能:创建一个管道,允许一个进程(通常是子进程)与另一个进程(通常是父进程)进行单向通信。
    • 参数:pipefds 是一个整型数组,用于存储管道的两个文件描述符,pipefds[0] 为读端,pipefds[1] 为写端。
    • 返回值:成功时返回0,失败时返回-1。
  2. fork():

    • 功能:创建一个新的进程,子进程是父进程的副本,从fork()调用处开始执行。
    • 参数:无。
    • 返回值:在子进程中返回0,在父进程中返回子进程的PID,在出错时返回-1。
  3. close(fd):

    • 功能:关闭指定的文件描述符。
    • 参数:fd 是要关闭的文件描述符。
    • 返回值:成功时返回0,失败时返回-1。
  4. read(fd, buf, count):

    • 功能:从指定的文件描述符 fd 读取数据到缓冲区 buf,最多读取 count 个字节。
    • 参数:fd 是文件描述符,buf 是存储读取数据的缓冲区,count 是要读取的字节数。
    • 返回值:成功时返回实际读取的字节数,失败时返回-1,如果达到文件末尾返回0。
  5. strcmp(str1, str2):

    • 功能:比较两个字符串 str1str2
    • 参数:str1str2 是要比较的字符串。
    • 返回值:如果 str1 小于 str2 返回小于0的值,如果 str1 等于 str2 返回0,如果 str1 大于 str2 返回大于0的值。
  6. write(fd, buf, count):

    • 功能:向指定的文件描述符 fd 写入数据,数据从缓冲区 buf 复制,写入 count 个字节。
    • 参数:fd 是文件描述符,buf 是包含要写入数据的缓冲区,count 是要写入的字节数。
    • 返回值:成功时返回实际写入的字节数,失败时返回-1。
  7. sleep(seconds):

    • 功能:使调用进程暂停执行指定的秒数。
    • 参数:seconds 是要暂停的秒数。
    • 返回值:无。
  8. _exit(status):

    • 功能:立即终止进程,并返回 status 状态值给父进程。
    • 参数:status 是退出状态值,通常是一个整数值。
    • 返回值:无,因为进程将被终止。
  9. waitpid(pid, status, options):

    • 功能:等待一个子进程的状态改变,可以是终止、停止或继续。
    • 参数:pid 是要等待的子进程的PID,status 是指向整数变量的指针,用于存储子进程的退出状态,options 是指定等待选项的整数。
    • 返回值:成功时返回子进程的PID,失败时返回-1。
  10. WIFEXITED(status)WEXITSTATUS(status):

    • 功能:WIFEXITED 检查进程是否正常退出,WEXITSTATUS 获取进程的退出状态码。
    • 参数:status 是从 waitpid 或其他等待函数获得的状态值。
    • 返回值:WIFEXITED 在进程正常退出时返回非零值,否则返回0;WEXITSTATUS 返回进程的退出状态码。
  11. strlen(str):

    • 功能:计算字符串 str 的长度,不包括结尾的空字符 \0
    • 参数:str 是要计算长度的字符串。
    • 返回值:返回字符串的长度。
  12. perror(str):

    • 功能:将当前错误消息字符串打印到标准错误输出。
    • 参数:str 是要打印在错误消息前面的字符串。
    • 返回值:无。

这些函数是UNIX和类UNIX系统编程中常用的库函数,用于进程控制、管道通信、文件描述符操作等。

3.

在主程序中创建两个线程mid_thread和term_thread,mid线程不断等待term线程终⽌它,并且每隔2秒打印⼀次等待的次数;term线程接受从主函数传进来的mid线程的ID。如果ID合法,就调⽤pthread_cancel 函数结束mid线程。

#include <stdio.h>      // 包含标准输入输出函数
#include <stdlib.h>     // 包含标准库函数,如exit
#include <pthread.h>    // 包含POSIX线程函数
#include <unistd.h>     // 包含UNIX标准函数,如sleep

// 全局变量,用于计数mid线程的等待次数
int count = 0;

// mid_thread 线程函数的声明
void* mid_thread_function(void* arg) {
    // 无限循环,模拟持续运行的线程
    while (1) {
        count++;           // 增加等待次数
        printf("Mid thread is waiting... Count: %d\n", count); // 打印当前等待次数
        sleep(2);          // 睡眠2秒,然后继续循环
    }
    return NULL;          // 线程函数返回NULL
}

// term_thread 线程函数的声明
void* term_thread_function(void* arg) {
    pthread_t* midThreadIdPtr = (pthread_t*)arg; // 从参数中获取指向mid线程ID的指针

    // 检查mid线程的ID是否非空且不是当前term线程自身
    if (midThreadIdPtr != NULL && *midThreadIdPtr != pthread_self()) {
        // 使用pthread_cancel终止mid线程
        pthread_cancel(*midThreadIdPtr); 
        printf("Term thread has canceled the mid thread.\n");
    }

    return NULL;          // 线程函数返回NULL
}

int main() {
    pthread_t mid, term;  // 声明两个线程的变量
    pthread_t midThreadId; // 存储mid线程的ID

    // 创建mid线程,参数依次为目标线程函数、线程属性(NULL表示默认属性)、线程函数参数(NULL)
    if (pthread_create(&mid, NULL, mid_thread_function, NULL) != 0) {
        perror("Failed to create mid thread"); // 如果创建失败,打印错误信息
        exit(EXIT_FAILURE);                   // 退出程序
    }
    midThreadId = mid; // 存储mid线程的ID

    // 创建term线程,传递mid线程的ID
    if (pthread_create(&term, NULL, term_thread_function, &midThreadId) != 0) {
        perror("Failed to create term thread"); // 如果创建失败,打印错误信息
        exit(EXIT_FAILURE);                    // 退出程序
    }

    // 主线程等待term线程结束,确保term线程有机会取消mid线程
    pthread_join(term, NULL);

    printf("Main function finished.\n"); // 打印主函数结束信息
    return EXIT_SUCCESS;                // 返回成功状态码
}

函数解析

以下是代码中出现的每个函数及其解析:

  1. pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg):

    • 功能:创建一个新线程。
    • 参数:
      • thread: 指向 pthread_t 变量的指针,用于存储新创建线程的ID。
      • attr: 指向 pthread_attr_t 结构的指针,指定线程的属性(传入 NULL 表示使用默认属性)。
      • start_routine: 线程函数的入口,即新线程将执行的函数。
      • arg: 传递给线程函数的参数。
    • 返回值:成功时返回0,失败时返回错误码。
  2. pthread_self(void):

    • 功能:返回调用线程的ID。
    • 参数:无。
    • 返回值:调用线程的 pthread_t 类型的ID。
  3. pthread_cancel(pthread_t thread):

    • 功能:请求取消指定的线程。
    • 参数:
      • thread: 需要被取消的线程的ID。
    • 返回值:成功时返回0,失败时返回错误码。
  4. pthread_join(pthread_t thread, void **retval):

    • 功能:等待指定线程终止。
    • 参数:
      • thread: 要等待的线程的ID。
      • retval: 可选参数,指向一个指针的指针,用于存储被取消线程的退出状态。
    • 返回值:成功时返回0,失败时返回错误码。
  5. sleep(unsigned int seconds):

    • 功能:使调用线程暂停执行指定的秒数。
    • 参数:
      • seconds: 要暂停的秒数。
    • 返回值:无。
  6. printf(const char *format, ...):

    • 功能:格式化输出到标准输出设备(通常是控制台)。
    • 参数:
      • format: 格式字符串,定义了后续参数的输出格式。
      • ...: 可变参数列表,包含要输出的数据。
    • 返回值:成功时返回打印的字符数,失败时返回负数。
  7. perror(const char *str):

    • 功能:将当前错误消息字符串打印到标准错误输出,并在字符串前面添加 str 指定的错误描述。
    • 参数:
      • str: 错误描述字符串。
    • 返回值:无。
  8. exit(int status):

    • 功能:立即终止程序,并返回给操作系统的状态码。
    • 参数:
      • status: 状态码,EXIT_SUCCESS 通常用于表示成功,EXIT_FAILURE 表示失败。
    • 返回值:无(实际上,exit 调用不会返回)。
  9. pthread_exit(void *retval):

    • 功能:终止调用线程,并返回一个退出值给 pthread_join
    • 参数:
      • retval: 退出值,可以是 NULL 或指向一个值的指针。
    • 返回值:无(实际上,pthread_exit 调用不会返回)。

这些函数是多线程编程中常用的,用于线程的创建、同步、取消和状态管理。

posted @ 2024-06-30 14:04  清澈的澈  阅读(14)  评论(0编辑  收藏  举报