嵌入式代码题
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;
}
运行结果
This is the first line
Second line
OK, the last line
函数解析:
在提供的C语言程序中,使用了多个标准库函数,下面是对这些函数的解析:
-
#include <stdio.h>
- 预处理指令,用于引入标准输入输出库函数的声明。
-
FILE *fp = NULL;
- 声明一个指向
FILE
结构的指针fp
,用于后续操作文件。
- 声明一个指向
-
fopen(const char *filename, const char *mode);
- 函数用于打开文件,并返回指向该文件的
FILE
指针。如果失败,返回NULL
。 - 第一个参数是文件名,第二个参数是打开模式。
- 函数用于打开文件,并返回指向该文件的
-
fputs(const char *str, FILE *stream);
- 函数用于将字符串写入到指定的文件流中,直到遇到字符串结束符
'\0'
。
- 函数用于将字符串写入到指定的文件流中,直到遇到字符串结束符
-
fflush(FILE *stream);
- 函数用于刷新输出缓冲区,确保所有输出操作都已执行。
-
fseek(FILE *stream, long offset, int whence);
- 函数用于在文件中移动文件位置指针。
- 第二个参数是偏移量,第三个参数
whence
指定了偏移量的计算方式:SEEK_SET
:从文件开头开始计算。SEEK_CUR
:从当前位置开始计算。SEEK_END
:从文件末尾开始计算。
-
fgets(char *str, int num, FILE *stream);
- 函数用于从指定的文件流中读取字符串,最多读取
num - 1
个字符加上一个结束符'\0'
。
- 函数用于从指定的文件流中读取字符串,最多读取
-
printf(const char *format, ...);
- 函数用于格式化输出到标准输出(通常是屏幕)。
-
fclose(FILE *stream);
- 函数用于关闭指定的文件流,并释放所有与该文件相关的资源。
-
return -1;
- 用于从
main
函数返回一个整数值-1
,通常表示程序执行失败。
- 用于从
-
int main() { ... }
- 程序的入口点,
main
函数的返回类型是int
,表示程序的退出状态。
- 程序的入口点,
-
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表示成功
}
运行结果
Child process received 'exit' command and will exit.
Child process exited with status 0.
函数解析
以下是代码中出现的每个函数及其解析:
-
pipe(pipefds)
:- 功能:创建一个管道,允许一个进程(通常是子进程)与另一个进程(通常是父进程)进行单向通信。
- 参数:
pipefds
是一个整型数组,用于存储管道的两个文件描述符,pipefds[0]
为读端,pipefds[1]
为写端。 - 返回值:成功时返回0,失败时返回-1。
-
fork()
:- 功能:创建一个新的进程,子进程是父进程的副本,从fork()调用处开始执行。
- 参数:无。
- 返回值:在子进程中返回0,在父进程中返回子进程的PID,在出错时返回-1。
-
close(fd)
:- 功能:关闭指定的文件描述符。
- 参数:
fd
是要关闭的文件描述符。 - 返回值:成功时返回0,失败时返回-1。
-
read(fd, buf, count)
:- 功能:从指定的文件描述符
fd
读取数据到缓冲区buf
,最多读取count
个字节。 - 参数:
fd
是文件描述符,buf
是存储读取数据的缓冲区,count
是要读取的字节数。 - 返回值:成功时返回实际读取的字节数,失败时返回-1,如果达到文件末尾返回0。
- 功能:从指定的文件描述符
-
strcmp(str1, str2)
:- 功能:比较两个字符串
str1
和str2
。 - 参数:
str1
和str2
是要比较的字符串。 - 返回值:如果
str1
小于str2
返回小于0的值,如果str1
等于str2
返回0,如果str1
大于str2
返回大于0的值。
- 功能:比较两个字符串
-
write(fd, buf, count)
:- 功能:向指定的文件描述符
fd
写入数据,数据从缓冲区buf
复制,写入count
个字节。 - 参数:
fd
是文件描述符,buf
是包含要写入数据的缓冲区,count
是要写入的字节数。 - 返回值:成功时返回实际写入的字节数,失败时返回-1。
- 功能:向指定的文件描述符
-
sleep(seconds)
:- 功能:使调用进程暂停执行指定的秒数。
- 参数:
seconds
是要暂停的秒数。 - 返回值:无。
-
_exit(status)
:- 功能:立即终止进程,并返回
status
状态值给父进程。 - 参数:
status
是退出状态值,通常是一个整数值。 - 返回值:无,因为进程将被终止。
- 功能:立即终止进程,并返回
-
waitpid(pid, status, options)
:- 功能:等待一个子进程的状态改变,可以是终止、停止或继续。
- 参数:
pid
是要等待的子进程的PID,status
是指向整数变量的指针,用于存储子进程的退出状态,options
是指定等待选项的整数。 - 返回值:成功时返回子进程的PID,失败时返回-1。
-
WIFEXITED(status)
和WEXITSTATUS(status)
:- 功能:
WIFEXITED
检查进程是否正常退出,WEXITSTATUS
获取进程的退出状态码。 - 参数:
status
是从waitpid
或其他等待函数获得的状态值。 - 返回值:
WIFEXITED
在进程正常退出时返回非零值,否则返回0;WEXITSTATUS
返回进程的退出状态码。
- 功能:
-
strlen(str)
:- 功能:计算字符串
str
的长度,不包括结尾的空字符\0
。 - 参数:
str
是要计算长度的字符串。 - 返回值:返回字符串的长度。
- 功能:计算字符串
-
perror(str)
:- 功能:将当前错误消息字符串打印到标准错误输出。
- 参数:
str
是要打印在错误消息前面的字符串。 - 返回值:无。
这些函数是UNIX和类UNIX系统编程中常用的库函数,用于进程控制、管道通信、文件描述符操作等。
3.
在主程序中创建两个线程mid_thread和term_thread,mid线程不断等待term线程终⽌它,并且每隔2秒打印⼀次等待的次数;term线程接受从主函数传进来的mid线程的ID。如果ID合法,就调⽤pthread_cancel 函数结束mid线程。
#include <pthread.h> // 引入pthread库,提供线程相关的功能
#include <unistd.h> // 引入unistd库,提供sleep函数
#include <stdio.h> // 引入stdio库,提供标准输入输出功能
#include <stdlib.h> // 引入stdlib库,提供标准库函数
// mid_thread 线程函数,每2秒打印一次等待时间
void* mid_thread(void* arg) {
int times = 0; // 初始化计数器
printf("mid thread start\n"); // 打印线程启动信息
while (1) { // 无限循环
printf("mid thread waiting %d\n", times); // 打印等待次数
sleep(2); // 每隔2秒打印一次等待次数
times++; // 增加计数器
}
return NULL; // 返回NULL
}
// term_thread 线程函数,接收 mid_thread 的 ID 并终止它
void* term_thread(void* arg) {
pthread_t tid; // 定义线程标识符
printf("term thread start\n"); // 打印线程启动信息
sleep(2); // 启动后休眠2秒
// 检查传递进来的参数arg是否为 NULL。如果不为NULL,则将其作为pthread_t类型的变量tid
if (arg != NULL) {
tid = *(pthread_t*)arg; // 获取 mid_thread 的线程 ID
// 使用pthread_cancel函数取消(终止)与tid相关联的线程,即 mid_thread 线程
pthread_cancel(tid);
printf("mid thread canceled\n"); // 打印线程取消信息
}
return NULL; // 返回NULL
}
int main() {
// 首先创建了两个线程标识符
pthread_t mid_tid, term_tid;
// 创建 mid_thread 线程
// 第一个参数:用于存储新线程的标识符
// 第二个参数:用于指定新线程的属性,设置为 NULL,使用默认属性
// 第三个参数:指向线程函数的指针,新线程将从该函数的起始点开始执行
// 第四个参数:传递给线程函数的参数,可以是任意类型的指针,通常通过void *进行传递
if (pthread_create(&mid_tid, NULL, mid_thread, NULL)) {
perror("create mid error"); // 如果创建线程失败,打印错误信息
return 1; // 返回1表示程序错误结束
}
// 创建 term_thread 线程,并传递 mid_thread 的线程 ID 作为参数
if (pthread_create(&term_tid, NULL, term_thread, &mid_tid)) { // 注意这里要传入mid线程标识符
perror("create term error"); // 如果创建线程失败,打印错误信息
return 1; // 返回1表示程序错误结束
}
// pthread_join 函数等待 mid_thread 线程结束
// 第一个参数:要等待的线程标识符,指定要等待的线程
// 第二个参数:存储线程返回值的指针,一般为NULL
// pthread_join 函数会阻塞主线程,直到指定的线程结束
if (pthread_join(mid_tid, NULL)) {
perror("join mid error"); // 如果等待线程失败,打印错误信息
return 1; // 返回1表示程序错误结束
}
// 等待 term_thread 线程结束
if (pthread_join(term_tid, NULL)) {
perror("join term error"); // 如果等待线程失败,打印错误信息
return 1; // 返回1表示程序错误结束
}
return 0; // 程序正常结束
}
运行结果
mid thread start
mid thread waiting 1
term thread start
mid thread waiting 2
mid thread canceled
函数解析
以下是代码中出现的每个函数及其解析:
-
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,失败时返回错误码。
-
pthread_self(void)
:- 功能:返回调用线程的ID。
- 参数:无。
- 返回值:调用线程的
pthread_t
类型的ID。
-
pthread_cancel(pthread_t thread)
:- 功能:请求取消指定的线程。
- 参数:
thread
: 需要被取消的线程的ID。
- 返回值:成功时返回0,失败时返回错误码。
-
pthread_join(pthread_t thread, void **retval)
:- 功能:等待指定线程终止。
- 参数:
thread
: 要等待的线程的ID。retval
: 可选参数,指向一个指针的指针,用于存储被取消线程的退出状态。
- 返回值:成功时返回0,失败时返回错误码。
-
sleep(unsigned int seconds)
:- 功能:使调用线程暂停执行指定的秒数。
- 参数:
seconds
: 要暂停的秒数。
- 返回值:无。
-
printf(const char *format, ...)
:- 功能:格式化输出到标准输出设备(通常是控制台)。
- 参数:
format
: 格式字符串,定义了后续参数的输出格式。...
: 可变参数列表,包含要输出的数据。
- 返回值:成功时返回打印的字符数,失败时返回负数。
-
perror(const char *str)
:- 功能:将当前错误消息字符串打印到标准错误输出,并在字符串前面添加
str
指定的错误描述。 - 参数:
str
: 错误描述字符串。
- 返回值:无。
- 功能:将当前错误消息字符串打印到标准错误输出,并在字符串前面添加
-
exit(int status)
:- 功能:立即终止程序,并返回给操作系统的状态码。
- 参数:
status
: 状态码,EXIT_SUCCESS
通常用于表示成功,EXIT_FAILURE
表示失败。
- 返回值:无(实际上,
exit
调用不会返回)。
-
pthread_exit(void *retval)
:- 功能:终止调用线程,并返回一个退出值给
pthread_join
。 - 参数:
retval
: 退出值,可以是NULL
或指向一个值的指针。
- 返回值:无(实际上,
pthread_exit
调用不会返回)。
- 功能:终止调用线程,并返回一个退出值给
这些函数是多线程编程中常用的,用于线程的创建、同步、取消和状态管理。
fork
让我们分析这段代码并确定其输出。我们需要特别关注fork
系统调用,它在创建一个新的进程时如何工作。
#include <unistd.h>
#include <stdio.h>
int main(void) {
pid_t pid; // 定义进程ID类型的变量
int count = 0; // 初始化count变量为0
pid = fork(); // 创建子进程
count++; // 增加count变量
printf("count=%d\n", count); // 打印count变量
return 0;
}
解释 fork
的行为
fork
调用会创建一个新的进程(子进程),这个子进程是父进程的副本。fork
的返回值在父进程和子进程中不同:- 在父进程中,
fork
返回子进程的 PID。 - 在子进程中,
fork
返回 0。
- 在父进程中,
代码的执行步骤
pid_t pid;
定义一个进程ID类型的变量。int count = 0;
初始化count
变量为0。pid = fork();
创建一个新的子进程。- 现在有两个进程:父进程和子进程。
count++;
无论是父进程还是子进程,都会执行这一行代码,因此count
变量都会被增加1。printf("count=%d\n", count);
打印count
变量的值。- 父进程打印
count = 1
。 - 子进程也打印
count = 1
。
- 父进程打印
预期输出
由于 fork
创建了一个子进程,因此 count
的打印操作会执行两次:一次在父进程中,一次在子进程中。每个进程都会输出 count = 1
。
运行结果
count=1
count=1
因此,程序的运行结果是:
count=1
count=1
请注意,输出的顺序可能会有所不同,这取决于操作系统调度父进程和子进程的顺序。但是,每个进程都会输出 count = 1
。
vfork
#include <unistd.h>
#include <stdio.h>
int main(void) {
pid_t pid; // 定义进程ID类型的变量
int count = 0; // 初始化count变量为0
pid = vfork(); // 创建子进程,共享地址空间
if (pid < 0) {
// 创建进程失败
perror("vfork failed");
return 1;
} else if (pid == 0) {
// 子进程执行的代码
count++; // 增加count变量
printf("Child: count=%d\n", count); // 打印count变量
_exit(0); // 使用 _exit 退出子进程,不影响父进程的堆栈
} else {
// 父进程执行的代码
count++; // 增加count变量
printf("Parent: count=%d\n", count); // 打印count变量
}
return 0;
}
解释 vfork
的行为
vfork
与fork
不同的是,子进程与父进程共享同一个地址空间。- 在
vfork
创建的子进程调用_exit
或exec
之前,父进程会被阻塞,等待子进程完成。 - 因为子进程和父进程共享地址空间,所以子进程不应该修改父进程的变量和堆栈。
代码的执行步骤
-
pid_t pid;
定义一个进程ID类型的变量。 -
int count = 0;
初始化count
变量为0。 -
pid = vfork();
创建一个新的子进程。
- 如果
vfork
返回负值,表示创建进程失败。 - 如果
vfork
返回0,表示这是子进程。 - 如果
vfork
返回正值,表示这是父进程,返回值是子进程的 PID。
- 如果
-
在子进程中:
count++
递增count
变量。- 打印
count
变量的值为1。 - 使用
_exit(0)
退出子进程。
-
在父进程中:
count++
递增count
变量。- 打印
count
变量的值为2。
预期输出
Child: count=1
Parent: count=2
注意:使用 vfork
时必须非常小心,因为子进程和父进程共享同一个地址空间,在子进程中不应修改任何父进程的变量或调用可能影响父进程堆栈的函数。