Linux程序设计--期末复习
阅读以下程序:#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>int main()
{int in, out;int n = 0;char c;in = open("./1.txt", O_RDONLY);out = open("./1_back.txt", O_WRONLY);if (in == -1 || out == -1){ // 1printf("open file error\n");exit(0);}n = read(in, &c, 1);while (n > 0){write(out, &c, 1);n = read(in, &c, 1);}close(in);close(out);exit(0);
}以下关于如何改进与提高该程序,正确的是(D)。A.out = open("./1_back.txt", O_WRONLY); 改为out = open("1_back.txt", O_WRONLY); ,可以显著提高性能和效率。
B.将while(n > 0) 改为while(n > 1), 可以减少读写次数,可以显著提高性能和效率。
C.in = open("./1.txt", O_RDONLY); 改为in = open("1.txt", O_RDONLY);,可以显著提高性能和效率。
D.将char c定义改为char c[256]; 其它使用到c的地方做响应修改。并引入memeset对c进行初始化。这样可以显著提高性能和效率。
阅读以下程序,有关叙述错误的是,#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>int main(int c, char *a[])
{DIR *dir;struct dirent *pitem;if (c <= 1){printf("para error\n");exit(2);}dir = opendir(a[1]);if (dir == 0){printf("open dir failed\n");exit(1);}printf("d_ino, filename\n");while ((pitem = readdir(dir)) != 0)printf("%ld %s\n", pitem->d_ino, pitem->d_name);closedir(dir);exit(0);
}A.假设该程序编译链接后的运行程序名为mydir,当(在mydir所在目录)运行./mydir 时,会输出“para error”并结束。
B.该示例程序展示了文件目录的基本操作接口:readdir,opendir, closedir, writedir等基本用法。
C.去掉 #include<stdlib.h> 语句 ,(系统默认编译设置下)会有exit调用语句有关的警告信息。
D.假设该程序编译链接后的运行程序名为mydir,当(在mydir所在目录)运行./mydir /tmp时,会输出 /tmp目录包含的每个文件(含目录)的inode节点号及文件(含目录)名,然后结束。
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>void list_directory(const char *path, int show_details, int depth); //A// 显示文件权限
void print_permissions(mode_t mode) {printf((S_ISDIR(mode)) ? "d" : "-");printf((mode & S_IRUSR) ? "r" : "-");printf((mode & S_IWUSR) ? "w" : "-");printf((mode & S_IXUSR) ? "x" : "-");printf((mode & S_IRGRP) ? "r" : "-");printf((mode & S_IWGRP) ? "w" : "-");printf((mode & S_IXGRP) ? "x" : "-");printf((mode & S_IROTH) ? "r" : "-");printf((mode & S_IWOTH) ? "w" : "-");printf((mode & S_IXOTH) ? "x" : "-");
}// 显示文件详细信息
void print_file_details(const char *path, const char *filename, int show_details, int depth) {struct stat statbuf;char full_path[1024], path2[1024];snprintf(full_path, sizeof(full_path), "%s/%s", path, filename);
// printf("%s in print file details fun\n",path);if (stat(full_path, &statbuf) == -1) {perror("stat");return;}printf("%*s", depth, "");// 文件权限print_permissions(statbuf.st_mode);printf(" ");// 硬链接数printf("%ld ", statbuf.st_nlink);// 文件所有者struct passwd *pw = getpwuid(statbuf.st_uid);if (pw) {printf("%s ", pw->pw_name);} else {printf("%d ", statbuf.st_uid);}// 文件所属组struct group *gr = getgrgid(statbuf.st_gid);if (gr) {printf("%s ", gr->gr_name);} else {printf("%d ", statbuf.st_gid);}// 文件大小printf("%8ld ", statbuf.st_size);// 最后修改时间char timebuf[80];strftime(timebuf, sizeof(timebuf), "%b %d %H:%M", localtime(&statbuf.st_mtime));printf("%s ", timebuf);// 文件名printf("%s\n", filename);if (S_ISDIR(statbuf.st_mode) && depth > 0) {list_directory(full_path, show_details, depth + 4);}
}// 列出目录内容
void list_directory(const char *path, int show_details, int depth) {DIR *dir;struct dirent *entry;char path2[1024];struct stat mybuf;//printf("%s\n",path);if ((dir = opendir(path)) == NULL) {perror("opendir");return;}while ((entry = readdir(dir)) != NULL) {if (strcmp(".", entry->d_name) == 0 || strcmp("..", entry->d_name) == 0)continue;if (show_details) {print_file_details(path, entry->d_name, show_details, depth);//closedir(dir);} else {printf("%*s %s\n", depth, "", entry->d_name);snprintf(path2, sizeof(path2), "%s/%s", path, entry->d_name);lstat(path2, &mybuf);if (S_ISDIR(mybuf.st_mode) && depth > 0)list_directory(path2, show_details, depth + 4);}}closedir(dir);
}int main(int argc, char *argv[]) {const char *path = ".";int show_details = 0;int depth = 0;// 解析命令行参数if (argc > 1) {for (int i = 1; i < argc; i++) {if (strcmp(argv[i], "-r") == 0)depth = 1;else if (strcmp(argv[i], "-l") == 0) {show_details = 1;} else {path = argv[i];}}}// 列出目录内容
// printf("%s\n",path);list_directory(path, show_details, depth);return 0;
}A. 将前面(//A对应行)的list_directory函数声明删除注销,(-o)编译链接时会有警告信息出现;
B. 假设该程序编译链接后的运行程序名为myls,当(在myls所在目录)运行./myls -l /usr/local时,会输出/usr/local目录包含的每个文件的详细信息(长格式),然后结束。
C. 假设该程序编译链接后的运行程序名为myls,当(在myls所在目录)运行./myls -r /usr/local时,会按层进方式输出/usr/local目录包含的各级文件(含目录)名,然后结束。
D. 该程序中有一个递归,就是list_directory直接调用自己形成递归,没有其它形式的。
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>void printdir(char *dir, int depth)
{DIR *dp;struct dirent *entry;struct stat statbuf;if ((dp = opendir(dir)) == NULL) {fprintf(stderr, "cannot open directory: %s\n", dir);return;}chdir(dir);while ((entry = readdir(dp)) != NULL) {lstat(entry->d_name, &statbuf);if (S_ISDIR(statbuf.st_mode)) {/* Found a directory, but ignore . and .. */if (strcmp(".", entry->d_name) == 0 ||strcmp("..", entry->d_name) == 0)continue;printf("%*s%s/\n", depth, "", entry->d_name);/* Recurse at a new indent level */printdir(entry->d_name, depth + 4);}elseprintf("%*s%s\n", depth, "", entry->d_name);}chdir("..");closedir(dp);
}int main(int argc, char* argv[])
{char *topdir, pwd[2] = ".";if (argc != 2)topdir = pwd;elsetopdir = argv[1];printf("Directory scan of %s\n", topdir);printdir(topdir, 0);printf("done.\n");exit(0);
}A. 宏S_ISDIR(statbuf.st_mode)是判断(文件类型)是否是目录
B. 假设该程序编译链接后的运行程序名为mys,当(在mys所在目录)运行./mys 时,会输出当前目录包含的各级文件(含目录)名(以及最后的Done.),并结束。
C.if(strcmp(".",entry->d_name) == 0 ||strcmp("..",entry->d_name) == 0)continue;语句不仅仅会使得程序不打印输出.(以及..)隐藏文件名,也由于continue语句,使得不会出现"."调用"."的局面;D. 该程序没有递归终止条件,因此它会一直递归调用下去,不会停止,直到系统资源耗尽。正确答案:D:该程序没有递归终止条件,因此它会一直递归调用下去,不会停止,直到系统资源耗尽
// 程序一/program1.c
#include <stdlib.h>
#include <stdio.h>int main() {system("ls -l");printf("Ok\n");exit(0);
}// 程序二/program2.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main() {execlp("ls", "ls", "-l", NULL);printf("OK\n");exit(1);
}A. 程序一(成功)运行后,最后一行是OK。
B. 当它们(成功编译链接后)在相同目录下成功运行后,输出结果是一样的。
C. 程序二运行时,其实原进程被替换了,即当执行ls -l命令,执行该命令的(Shell)进程将原进程替换了。
D. 程序一运行时,其实有多个进程,一个是program1程序运行的进程,两外一个是执行ls -l命令的(Shell)进程。
-
system() 函数的工作原理(对应程序一)
system()
函数会创建一个新的 Shell 进程,在该进程中执行指定的命令(如ls -l
)。- 原进程会等待 Shell 进程执行完毕后,继续执行后续代码(如
printf("Ok\n")
)。 - 答案:选项 A 正确。程序一成功运行后,会先输出
ls -l
的结果,最后一行是 “Ok”。
-
execlp() 函数的工作原理(对应程序二)
execlp()
函数会用新的程序(如ls
)替换当前进程的映像,原程序的后续代码不会被执行。- 如果
execlp()
执行成功,printf("OK\n")
和exit(1)
永远不会被执行;只有当execlp()
失败时才会继续执行后续代码。 - 答案:选项 C 正确。程序二运行时,原进程会被
ls
进程完全替换。
-
进程创建与替换的区别(对应选项 D 和 B)
- 程序一:使用
system()
创建新的 Shell 进程,原进程与新进程同时存在,因此有多个进程。 - 程序二:使用
execlp()
替换当前进程,没有创建新进程,只有一个进程(但进程映像被替换为ls
)。 - 输出结果对比:
- 程序一:输出
ls -l
的结果后,还会输出 “Ok”。 - 程序二:仅输出
ls -l
的结果(成功时),不会输出 “OK”。
- 程序一:输出
- 答案:选项 D 正确,选项 B 错误。程序一和程序二的输出结果不同,因此选项 B 是错误的。
- 程序一:使用
-
错误处理与返回值
- 程序二的
execlp()
后如果执行失败,会执行printf("OK\n")
并返回 1,但成功时不会执行这些代码。 - 程序一的
system()
会等待命令执行完毕,并返回命令的退出状态。
- 程序二的
结论:
根据代码分析,错误选项是 B。程序一和程序二的输出结果不同,程序一最后会输出 “Ok”,而程序二(成功时)不会输出 “OK”。
// myhello.c
#include <stdlib.h>
#include <stdio.h>int main() {printf("hello world\n");exit(1);
}// myfork.c
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>int main() {int pid;int n = 0;pid = fork();switch(pid) {case -1:printf("fork error\n");break;case 0:printf("p chlid:%d\n", n);execlp("myh", "myh", NULL);printf("over\n");exit(22);default:n++;printf("p parent:%d\n", n);}printf("waiting\n");wait(&n);if (WIFEXITED(n))printf("the exit code is %d\n", WEXITSTATUS(n));elseprintf("%d process exit normally\n", n);exit(0);
}A. "p chlid:0"输出行一定在"waiting"输出行前面。因为输出“p chlid:0”的语句在输出"waiting"的语句前面。
B. 该程序创建子进程成功运行后,不一定有字符串“p chlid:0"输出。
C. 该程序创建子进程成功运行后,输出内容中一定有“p parent:1”字符串。
D. 有两行"waiting"字符串输出,父子进程各输出一行
-
选项A
- 错误。虽然子进程的
printf("p chlid:0\n")
在代码顺序上先于父进程的printf("waiting\n")
,但进程调度是不确定的。父进程可能在子进程执行前就完成了自己的printf("waiting\n")
。因此,“p chlid:0” 不一定在 “waiting” 之前输出。
- 错误。虽然子进程的
-
选项B
- 正确。若
execlp("myh", "myh", NULL)
成功执行,子进程会被myh
程序完全替换,不会执行后续的printf("over\n")
和exit(22)
。但若execlp
失败(例如myh
不在 PATH 中),则会执行printf("over\n")
和exit(22)
,此时子进程会输出 “p chlid:0”。因此,子进程成功运行后(即execlp
成功),不会有 “p chlid:0” 输出。
- 正确。若
-
选项C
- 错误。父进程的
printf("p parent:1\n")
在default
分支中,仅当fork()
返回子进程的 PID(非零值)时执行。但若fork()
失败(返回 -1),则父进程会执行case -1
分支,不会输出 “p parent:1”。题目中虽提到“创建子进程成功运行后”,但选项C的描述是“一定有”,而实际存在fork()
失败的情况,因此该选项错误。
- 错误。父进程的
-
选项D
- 错误。父子进程都会执行
printf("waiting\n")
,但由于execlp
成功时子进程会被替换,不会执行该行代码。因此,通常只有父进程会输出 “waiting”。若execlp
失败,子进程会输出 “over” 和 “waiting”,但题目假设子进程成功运行(即execlp
成功),因此该选项错误。
- 错误。父子进程都会执行
正确答案是 B。
// myalarm.c
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>int i = 0;void myalarm(int s) {i = rand() % 3;
}int main() {// int i; // 注释掉的局部变量声明,不影响全局变量isignal(SIGALRM, myalarm); // Awhile (1) {alarm(1);switch (i) {case 1:printf("Hello world\n");break;case 2:printf("Welcome\n");break;case 3:printf("byebye\n");break;default:printf("error\n");}sleep(1);}exit(0);
}A. 该程序逻辑表明,定义了一个全局变量i。该变量在信号处理函数和main函数中都能使用,从而起到控制switch分支的作用。
B. 该程序逻辑表明,定义了一个alarm时钟信号处理函数
C. 由于有while(1)死循环,该程序(运行时)将会有很多的Hello world输出。
D. 由于有while(1)死循环,该程序(运行时)将会有很多的Byebbye输出。
- **信号处理函数
myalarm
**中,i = rand() % 3
的取值为 0、1、2(因为%3
的余数最大为2)。 switch
分支中:case 1
对应输出Hello world
,case 2
对应Welcome
,case 3
永远不会触发(因为i
最大为2),默认分支default
在i=0
时执行(输出error
)。
-
选项A
- 正确。全局变量
i
被信号处理函数和main
函数共享,用于控制switch
分支,符合代码逻辑。
- 正确。全局变量
-
选项B
- 正确。通过
signal(SIGALRM, myalarm)
定义了闹钟信号(SIGALRM
)的处理函数myalarm
。
- 正确。通过
-
选项C
- 正确。当
i=1
时会输出Hello world
,由于alarm(1)
和sleep(1)
每秒触发一次信号,循环多次后可能多次输出Hello world
。
- 正确。当
-
选项D
- 错误。
i = rand() % 3
的取值范围是0、1、2
,永远不会等于3,因此case 3
(输出byebye
)永远不会被触发,该选项描述与代码逻辑矛盾。
- 错误。
错误的陈述是D。
下面是整理好格式的代码:
// mysignal.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>void f(int s) {printf("I am in signal func\n");signal(SIGINT, SIG_DFL); // 将SIGINT的处理恢复为默认(终止进程)
}int main() {int i;signal(SIGINT, f); // 注册SIGINT的处理函数fwhile (1) {printf("Hello world %d\n", i++);sleep(1);}exit(0);
}A. 由于有while(1)死循环,该程序一旦启动运行就需要(外力)干预才能使它停止运行。
B. 按一次Ctrl+c能使该程序停止运行
C. 按二次Ctrl+c才能使该程序停止运行
D. 按一次Ctrl+z能使该程序停止运行
-
初始时,程序通过
signal(SIGINT, f)
将SIGINT
(键盘中断,对应Ctrl+C
)的处理函数设为f
。 -
当第一次收到
Ctrl+C
时:- 调用
f
函数,打印"I am in signal func\n"
。 - 在
f
中,通过signal(SIGINT, SIG_DFL)
将SIGINT
的处理恢复为 默认行为(即终止进程)。
- 调用
-
第二次收到
Ctrl+C
时:- 由于处理方式已变为
SIG_DFL
,程序会直接终止。
- 由于处理方式已变为
-
选项A
- 正确。程序包含
while(1)
死循环,若无外部信号(如SIGINT
)干预,会一直运行,需外力终止。
- 正确。程序包含
-
选项B
- 错误。第一次按
Ctrl+C
时,程序进入信号处理函数f
,但并未终止,而是继续循环(仅修改了后续SIGINT
的处理方式)。按一次Ctrl+C
不会停止程序。
- 错误。第一次按
-
选项C
- 正确。第一次
Ctrl+C
触发f
函数,设置SIGINT
为默认处理;第二次Ctrl+C
时,程序按默认行为终止。因此需按两次Ctrl+C
才能停止。
- 正确。第一次
-
选项D
- 正确。
Ctrl+Z
发送的是SIGTSTP
信号,程序未注册该信号的处理函数,其默认行为是暂停进程(放入后台挂起),属于“停止运行”的一种形式。
- 正确。
错误的陈述是B。
// mypipe.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main() {int des[2];int pid;char mes[256];pipe(des);int s;pid = fork();if (pid == 0) {memset(mes, '\0', 256);read(des[0], mes, 256);printf("read:%s\n", mes);} else if (pid > 0) {write(des[1], "hello ahut\n", 10); // 注意:字符串实际长度为11,此处写入10字节可能不完整} else {printf("error\n");}exit(0);
}A. 该程序逻辑表明,先创建管道,再创建子进程。因为这样,便于子进程继承管道的读写描述符des数组。
B. 该程序中,父进程用文件描述符des[1]向道中写数据信息
C. 该程序中,子进程用文件描述符des[0]从管道中读取数据信息
D. 父进程创建子进程失败后,将向管道中写error信息
错误选项分析:
程序核心逻辑
-
管道创建与进程分叉:
- 先通过
pipe(des)
创建管道,再通过fork()
分叉进程,子进程会继承父进程的文件描述符des[0]
(读端)和des[1]
(写端)。 - 父进程(
pid > 0
)负责向管道写端des[1]
写入数据(字符串 “hello ahut\n”,但代码中写入长度为 10,实际应是 11 字节,存在越界风险,但不影响选项判断)。 - 子进程(
pid == 0
)负责从管道读端des[0]
读取数据并打印。
- 先通过
-
fork 失败处理:
- 当
fork()
返回负数(pid < 0
)时,进入else
分支,仅打印 “error\n”,未向管道写入任何数据。
- 当
选项逐一分析
-
选项A
- 正确。先创建管道再分叉进程,子进程通过继承机制获得管道描述符,这是管道在父子进程间通信的标准用法。
-
选项B
- 正确。管道的写端为
des[1]
,父进程在pid > 0
分支中通过write(des[1], ...)
向管道写入数据。
- 正确。管道的写端为
-
选项C
- 正确。管道的读端为
des[0]
,子进程在pid == 0
分支中通过read(des[0], ...)
从管道读取数据。
- 正确。管道的读端为
-
选项D
- 错误。当
fork()
失败(pid < 0
)时,程序在else
分支中仅打印 “error\n”,并未向管道写入任何信息(如 “error”)。管道的读写操作仅在fork()
成功后的父子进程中执行,失败时不涉及管道操作。
- 错误。当
错误的陈述是D。
下面是整理好格式的代码:
// mysig3.c
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>void fint(int s) {printf("i am in child:exiting\n");exit(0);
}int pid;void falarm(int s) {printf("paren in sig handler\n");kill(pid, SIGINT);
}int main() {pid = fork();if (pid > 0) {signal(SIGALRM, falarm);//sleep(2);alarm(2);while (1) {printf("parent\n");sleep(2);}}else if (pid == 0) {signal(SIGINT, fint);pause();}else {printf("error");}exit(0);
}A. 该程序在运行时,由于在ALARM信号处理中先打印,再给子进程发送SIGINT信息,因此“paren in sig handler”的输出应该在“i am in child:exiting”输出的前面
B. 该程序中,子进程可以注释掉pause的调用,而不影响父进程给子进程发送信号的处理,子进程也一定会输出i am in child:exiting字符串
C. 该程序逻辑表明,定义了两个信号处理函数,一个处理SIGINT,一个处理SIGALRM
D. 该程序在运行时,父进程会输出很多parent行
错误选项分析:
程序核心逻辑
-
进程分叉与信号注册:
- 父进程(
pid > 0
):设置SIGALRM
信号处理函数falarm
,启动2秒闹钟后进入无限循环,每秒打印 “parent”。 - 子进程(
pid == 0
):设置SIGINT
信号处理函数fint
,然后调用pause()
阻塞等待信号。
- 父进程(
-
信号触发流程:
- 2秒后闹钟触发
SIGALRM
,父进程执行falarm
,打印 “paren in sig handler”,并向子进程发送SIGINT
。 - 子进程收到
SIGINT
后执行fint
,打印 “i am in child:exiting” 并退出。
- 2秒后闹钟触发
选项逐一分析
-
选项A
- 正确。父进程在
falarm
中先打印再发送信号,子进程收到信号后才打印退出信息,因此 “paren in sig handler” 必然在 “i am in child:exiting” 之前输出。
- 正确。父进程在
-
选项B
- 错误。若注释掉子进程的
pause()
,子进程会直接执行exit(0)
退出,不会等待父进程发送的SIGINT
,也不会输出 “i am in child:exiting”。因此该选项描述与代码逻辑矛盾。
- 错误。若注释掉子进程的
-
选项C
- 正确。程序通过
signal()
分别为SIGINT
和SIGALRM
注册了处理函数fint
和falarm
。
- 正确。程序通过
-
选项D
- 正确。父进程在
while(1)
循环中每秒打印 “parent”,直到子进程退出后父进程仍会继续循环打印。
- 正确。父进程在
结论
错误的陈述是B。
// myf.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main() {char buffer[256];strcpy(buffer, "ihrllo world\0");printf("%s", buffer);exit(0);
}// mys.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main() {char buffer[256];memset(buffer, '\0', 256);scanf("%s", buffer);printf("read:%s\n", buffer);exit(0);
}// pipe1.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main() {int des[2];int pid;char mes[256];pipe(des);int s;pid = fork();if (pid == 0) {close(0);dup(des[0]);close(des[0]);close(des[1]);execl("./mys", "mys", NULL);}else if (pid > 0) {close(1);dup(des[1]);close(des[0]);close(des[1]);execl("./myf", "myf", NULL);}else {printf("error\n");}exit(0);
}A. read:ihrllo
B. 没有输出
C. read:world
D. read:ihrllo world
执行流程分析:
-
管道创建与进程分叉:
pipe1.c
创建管道后分叉进程,子进程(pid == 0
)执行mys
,父进程(pid > 0
)执行myf
。
-
子进程(
mys
)准备:- 关闭标准输入(
0
),将管道读端(des[0]
)复制到标准输入(0
),然后关闭管道两端。 - 调用
execl
执行mys
,此时mys
的标准输入连接到管道读端。
- 关闭标准输入(
-
父进程(
myf
)准备:- 关闭标准输出(
1
),将管道写端(des[1]
)复制到标准输出(1
),然后关闭管道两端。 - 调用
execl
执行myf
,此时myf
的标准输出连接到管道写端。
- 关闭标准输出(
-
数据流向:
myf
输出"ihrllo world"
到管道写端。mys
从管道读端读取数据,使用scanf("%s", buffer)
读取字符串,遇到空格停止,因此只能读取"ihrllo"
。mys
输出"read:ihrllo\n"
。
结论:
正确答案是A(read:ihrllo)。
代码整理:
fifocom.h
#define FIFO_NAME "/tmp/myfifo"struct mes {int pid;int state;
};
fiforead.c
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>int main() {char mes[256];int des;struct mes mymes;int r, iret;r = access(FIFO_NAME, F_OK);if (r == -1) {iret = mkfifo(FIFO_NAME, 0777);if (iret < 0) {printf("mkfifo error\n");exit(1);}}des = open(FIFO_NAME, O_RDONLY);read(des, &mymes, sizeof(struct mes));printf("read from fifo:%d,%d\n", mymes.pid, mymes.state);exit(0);
}
myfifow3.c
#include "fifocom.h"
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>int main() {char mes[256];int des;struct mes mymes;int iret, res;res = access(FIFO_NAME, F_OK);if (res == -1) {iret = mkfifo(FIFO_NAME, 0777);if (iret < 0) {printf("mkfifo error");exit(0);}}des = open(FIFO_NAME, O_WRONLY);mymes.pid = getpid();mymes.state = 1;write(des, &mymes, sizeof(struct mes));printf("write to fifo:%d,%d\n", mymes.pid, mymes.state);exit(0);
}
A. 通过access判定当fifo管道还没有建立时,将建立一个管道。
B. 该管道的名字(文件名)是/tmp/myfifo
C. 当运行到open处,如果有进程已经以读方式打开该fifo管道,open会立即返回;当还没有进程以读方式打开该fifo管道,open也会立即返回。
D. (如果可以写的话)该程序向管道中写一个struct mes结构体信息。
错误选项分析:
选项C
- 错误原因:
当以O_WRONLY
模式打开 FIFO 时,若没有进程以读模式(O_RDONLY
)打开该 FIFO,open
会阻塞,直到有读端打开或被信号中断。- 读端打开规则:
O_RDONLY
打开 FIFO 时,若没有写端打开,不会阻塞(立即返回)。 - 写端打开规则:
O_WRONLY
打开 FIFO 时,若没有读端打开,会阻塞(不会立即返回)。
- 读端打开规则:
- 代码验证:
myfifow3.c
中以O_WRONLY
打开 FIFO,若此时fiforead.c
未运行(无读端打开),open
会阻塞,直到读端打开。
其他选项正确性
-
选项A
- 正确。通过
access(FIFO_NAME, F_OK)
检查 FIFO 是否存在,不存在时调用mkfifo
创建。
- 正确。通过
-
选项B
- 正确。
fifocom.h
中定义FIFO_NAME
为/tmp/myfifo
,管道文件名为该路径。
- 正确。
-
选项D
- 正确。程序通过
write(des, &mymes, sizeof(struct mes))
向 FIFO 中写入struct mes
结构体数据。
- 正确。程序通过
结论:
错误的陈述是C。
下面是整理好格式的代码和分析:
代码整理:
mysh.sh
#!/bin/sh
read str
echo "in shell:$str"
exit 0
mypopen1.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main() {FILE *ffile;char buffer[256];int r;ffile = popen("./mysh.sh", "w"); // 注意:原代码中第二个参数有多余空格,应为"w"if (ffile == NULL) {printf("error\n");exit(1);}memset(buffer, '\0', 256);strcpy(buffer, "there was a lake beside the gate of the university...");r = fwrite(buffer, sizeof(char), 255, ffile);pclose(ffile);exit(0);
}A. 该程序中,open的第二参数也可以是“r”,也能编译与运行,其运行结果也将一样。
B. 当正确运行myp1时,mysh,sh中的read实际是从(popen)管道中读取数据信息。
C. 当正确运行myp1时,将输出in shell:there was a lake beside the gate of the university...
D. 该程序实际执行时,mysh.sh应该具备相应权限,如r和x权限,否则会出现权限不容许等出错。
错误选项分析:
选项A
- 错误原因:
popen
的第二个参数"w"
表示向子进程的标准输入写入数据(子进程的read
会从管道读取数据)。
若改为"r"
,则表示从子进程的标准输出读取数据,此时:- 子进程的
read str
会等待真正的标准输入(如键盘输入),而非管道数据(因为管道连接的是子进程的标准输出,而非输入)。 - 父进程无法通过
fwrite
向子进程发送数据,导致子进程的read
阻塞或读取不到数据,最终输出结果完全不同。
- 子进程的
- 结论:选项A错误,模式
"r"
与"w"
功能完全相反,结果不可能一样。
其他选项正确性
-
选项B
- 正确。
popen("w")
创建的管道连接子进程的标准输入,mysh.sh
中的read str
会从该管道读取父进程写入的数据。
- 正确。
-
选项C
- 正确。父进程通过
fwrite
向管道写入字符串,子进程读取后会输出in shell:there was a lake beside the gate of the university...
。
- 正确。父进程通过
-
选项D
- 正确。执行
./mysh.sh
需要脚本具备 可读(r)和可执行(x)权限,否则popen
会因权限不足失败。
- 正确。执行
结论:
错误的陈述是A。
下面是整理好格式的代码和分析:
// mypopen2.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main(int c, char *a[]) {FILE *ffile;char buffer[256], *p;int r;int i = 1;if (c <= 1) {printf("number of para is less\n");exit(1);}i = 1;p = buffer;memset(buffer, '\0', 256);while (i < c) {strcat(buffer, a[i]);strcat(buffer, " ");i++;}buffer[strlen(buffer) + 1] = '\0'; // 错误:越界写入ffile = popen(buffer, "r");if (ffile == NULL) {printf("popen open file error\n");exit(1);}memset(buffer, '\0', 256);r = fread(buffer, sizeof(char), 255, ffile);while (r > 0) {printf("read %s", buffer);memset(buffer, '\0', 256);r = fread(buffer, sizeof(char), 255, ffile);}pclose(ffile);exit(0);
}A. 该程序运行时,参数个数应该至少需要两个。否则会输出“number of para is less”错误并退出。
B. 程序中将传给该程序的参数数组a[](a[0]元素除外)拼接成一个命令对应的字符串,再将该字符串作为popen函数的第一个参数。
C. 当(myp所在目录为当前目录)正确运行./myp find . -name pi.c时,如果当前目录中有pi.c文件(且当前用户有读写权限),将输出read ./pi.c
D. 该程序很好的将参数拼接起来构成一个命令字符串,该字符串长度不受限制。
错误选项分析:
选项D
- 错误原因:
程序中使用char buffer[256]
存储拼接后的命令字符串,其长度最大为255字节(需留一个字节给字符串终止符\0
)。若参数拼接后的字符串超过255字节,会导致缓冲区溢出,引发未定义行为。 - 代码问题:
buffer[strlen(buffer) + 1] = '\0';
是错误的,应改为buffer[strlen(buffer)] = '\0';
,但这无法解决长度限制问题。- 即使修正赋值位置,
buffer
的静态大小仍限制了命令长度。
其他选项正确性
-
选项A
- 正确。程序通过
if (c <= 1)
判断参数个数,不足时输出错误并退出。
- 正确。程序通过
-
选项B
- 正确。程序将
a[1]
到a[c-1]
的参数用空格拼接成完整命令字符串,如find . -name pi.c
。
- 正确。程序将
-
选项C
- 正确。若当前目录存在
pi.c
,执行find . -name pi.c
会输出./pi.c
,父进程通过fread
读取后打印read ./pi.c
。
- 正确。若当前目录存在
结论:
错误的陈述是D。