Chapter 5 Interlude: Process APIs
第 5 章 插叙:进程 API
补充:插叙
本章介绍更多系统实践的内容...如果不关心实践,你可以跳过,但你应该不会跳过...因为公司通常不会因为不实用的技能而聘用你...
UNIX 系统进程的创建
本章讨论 fork() and exec()...and wait()...
1. fork() 系统调用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("hello world (pid: %d)\n", (int)getpid());
int rc = fork();
if (rc < 0)
{
fprintf(stderr, "fork failed\n");
exit(1);
}
else if (rc == 0)
{
printf("hello, I am child (pid: %d)\n", (int)getpid());
}
else
{
printf("hello, I am parent of %d (pid: %d)\n", rc, (int)getpid());
}
return 0;
}
值得一提的是,我用WSL在Windows中编码,希望在Windows中进行测试,却又报错说fork()函数未声明。
原因是fork()是UNIX和Linux中独有的,windows中不通用...
1.1. 编译输出
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter5# gcc p1.c -o p1
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter5# ./p1
hello world (pid: 2712)
hello, I am parent of 2713 (pid: 2712)
hello, I am child (pid: 2713)
1.2. 解析
刚开始的时候,进程输出一条 hello world 以及自己的 process identifier
随后进程调用了 fork 系统调用,创建新进程
新进程与调用进程完全一样
对于操作系统来说,现在有两个完全一样的进程在运行
新创建的叫 child process,原来的叫 parent process
又输出我们可以知道
子进程不会从 main 开始执行,因为hello world 只执行了一次
子进程拥有自己的 process identifier、地址空间、寄存器、程序计数器...
fork 返回值根据 进程不一样
父进程获得返回值是子进程的 pid
子进程的返回值是 0
而且输出有可能不确定!子进程有可能先进性,获得更小的 pid......
CPU scheduler 决定某个时刻哪个进程被执行...
但是其非常复杂,我们无法进行假设...这种 non-determinism 会导致一些很有趣的问题...特别是 multi-threaded program
2. wait() 系统调用
有时候父进程需要等子进程执行完毕
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("hello world (pid: %d)\n", (int)getpid());
int rc = fork();
if (rc < 0)
{
fprintf(stderr, "fork failed\n");
exit(1);
}
else if (rc == 0)
{
printf("hello, I am child (pid: %d)\n", (int)getpid());
}
else
{
int wc = wait(NULL);
printf("hello, I am parent of %d (wc: %d) (pid: %d)\n", rc, wc, (int)getpid());
}
return 0;
}
2.1. 编译输出
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter5# ./p2
hello world (pid: 2749)
hello, I am child (pid: 2750)
hello, I am parent of 2750 (wc: 2750) (pid: 2749)
2.2. 解析
这样子无论父进程是先执行还是后执行都会晚于自己进程输出...
但其实不一定...警惕这些绝对性的话语
3. exec() 系统调用
可以让子进程执行与父进程不同的程序...与fork的拷贝不同...
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
printf("hello world (pid: %d)\n", (int)getpid());
int rc = fork();
if (rc < 0)
{
fprintf(stderr, "fork failed\n");
exit(1);
}
else if (rc == 0)
{
printf("hello, I am child (pid: %d)\n", (int)getpid());
char *myargs[3];
myargs[0] = strdup("wc"); // program: "wc" (word count)
myargs[1] = strdup("p3.c"); // argument: file to count
myargs[2] = NULL; // marks end of array
execvp(myargs[0], myargs); // run word count
printf("this shouldn't print out");
}
else
{
int wc = wait(NULL);
printf("hello, I am parent of %d (wc: %d) (pid: %d)\n", rc, wc, (int)getpid());
}
return 0;
}
3.1. 编译输出
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter5# gcc p3.c -o p3
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter5# ./p3
hello world (pid: 2763)
hello, I am child (pid: 2764)
31 108 823 p3.c
hello, I am parent of 2764 (wc: 2764) (pid: 2763)
3.2. 解析
这里使用execvp()来运行字符计数程序wc。
实际上是对p3.c运行wc
告诉我们
文件有多少行
多少单词
多少字节...
execvp() 也很奇怪
给定可执行程序名称(wc)和需要的参数(p3.c)后,会从可执行程序中加载代码和静态数据,并用它腹泻自己的代码段,以及静态数据...
因此他并没有创建新进程,而是直接将当前运行的程序(p3.c)替换为不同的程序(wc)...对exec()的成功调用也永远不会会返回....
和蒋老师上课讲的例子一模一样啊!!!
4. 为什么会有这样的 API
4.1. 重要的是做对事!
Get it right!abstraction and simplify will never replace get it right!
重定向功能
shell的
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter5# wc p3.c > newfile.txt
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter5# ls
newfile.txt p1 p1.c p2 p2.c p3 p3.c
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter5# cat newfile.txt
31 108 823 p3.c
redirect功能
通过如此相当于将输出重定向至文件,而不是屏幕...
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int rc = fork();
if (rc < 0)
{
fprintf(stderr, "fork failed\n");
exit(1);
}
else if (rc == 0)
{
close(STDOUT_FILENO);
open("./p4.output", O_CREAT | O_WRONLY | O_TRUNC | S_IRWXU);
// new exec "wc" ...
char *myargs[3];
myargs[0] = strdup("wc"); // program: "wc" (word count)
myargs[1] = strdup("p3.c"); // argument: file to count
myargs[2] = NULL; // marks end of array
execvp(myargs[0], myargs); // run word count
}
else
{
int wc = wait(NULL);
}
return 0;
}
4.2. 编译输出
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter5# gcc p4.c -o p4
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter5# ./p4
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter5#
无事发生...
可以看看文件
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter5# cat p4.output
31 108 823 p3.c
可以看到预期输出都完成了!
4.3. 管道的知识
UNIX管道使用类似方式实现...但是利用的是pipe()系统调用
In this case, the output of one process is connected to an inkernel pipe (i.e., queue), and the input of another process is connected to that same pipe; thus, the output of one process seamlessly is used as input to the next, and long and useful chains of commands can be strung together. As a simple example, consider looking for a word in a file, and then counting how many times said word occurs; with pipes and the utilities grep
and wc
, it is easy; just type grep -o foo file | wc -l
into the command prompt and marvel at the result. Finally, while we just have sketched out the process API at a high level, there is a lot more detail about these calls out there to be learned and digested; we’ll learn more, for example, about file descriptors when we talk about file systems in the third part of the book. For now, suffice it to say that the fork()
/exec()
combination is a powerful way to create and manipulate processes.
遇到不懂的可以阅读 man 手册,上面记载了 UNIX 系统中最原生的东西...他的出现甚至早于网络 Web
哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈太搞了!!!
这本书好有意思...
Finally, reading the man pages can save you some embarrassment. When you ask colleagues about some intricacy of fork(), they may simply reply: “RTFM.” This is your colleagues’ way of gently urging you to Read The Man pages. The F in RTFM just adds a little color to the phrase...
5. 其他API
作者叫你自己去看man手册
这里提到的有:kill()、ps命令、top工具...
我发现中文版少了一些东西啊!不知道是不是版本不对
5.1. sudo 的相关知识
ASIDE: THE SUPERUSER (ROOT) A system generally needs a user who can administer the system, and is not limited in the way most users are. Such a user should be able to kill an arbitrary process (e.g., if it is abusing the system in some way), even though that process was not started by this user. Such a user should also be able to run powerful commands such as shutdown (which, unsurprisingly, shuts down the system). In UNIX-based systems, these special abilities are given to the superuser (sometimes called root). While most users can’t kill other users processes, the superuser can. Being root is much like being Spider-Man: with great power comes great responsibility [QI15]. Thus, to increase security (and avoid costly mistakes), it’s usually better to be a regular user; if you do need to be root, tread carefully, as all of the destructive powers of the computing world are now at your fingertips.
5.2. 一些个关键词
ASIDE: KEY PROCESS API TERMS
Each process has a name; in most systems, that name is a number known as a process ID (PID).
The fork() system call is used in UNIX systems to create a new process. The creator is called the parent; the newly created process is called the child. As sometimes occurs in real life [J16], the child process is a nearly identical copy of the parent.
The wait() system call allows a parent to wait for its child to complete execution.
The exec() family of system calls allows a child to break free from its similarity to its parent and execute an entirely new program.
A UNIX shell commonly uses fork(), wait(), and exec() to launch user commands; the separation of fork and exec enables features like input/output redirection, pipes, and other cool features, all without changing anything about the programs being run.
Process control is available in the form of signals, which can cause jobs to stop, continue, or even terminate.
Which processes can be controlled by a particular person is encapsulated in the notion of a user; the operating system allows multiple users onto the system, and ensures users can only control their own processes.
A superuser can control all processes (and indeed do many other things); this role should be assumed infrequently and with caution for security reasons.
6. 作业(模拟作业)
中文版没有!
阉割了???
6.1. 模拟作业1 - fork.py
fork.py
跟着操作就好,大概是对几个进程的关系进行梳理,绘制进程树...这种题中国的考试应该会手撸...
具体参考作业的docs就好...
Questions
Run ./fork.py -s 10 and see which actions are taken. Can you predict what the process tree looks like at each step? Use the -c flag to check your answers. Try some different random seeds (-s) or add more actions (-a) to get the hang of it.
One control the simulator gives you is the fork percentage, controlled by the -f flag. The higher it is, the more likely the next action is a fork; the lower it is, the more likely the action is an exit. Run the simulator with a large number of actions (e.g., -a 100) and vary the fork percentage from 0.1 to 0.9. What do you think the resulting final process trees will look like as the percentage changes? Check your answer with -c.
Now, switch the output by using the -t flag (e.g., run ./fork.py -t). Given a set of process trees, can you tell which actions were taken?
One interesting thing to note is what happens when a child exits; what happens to its children in the process tree? To study this, let’s create a specific example: ./fork.py -A a+b,b+c,c+d,c+e,c-. This example has process ’a’ create ’b’, which in turn creates ’c’, which then creates ’d’ and ’e’. However, then, ’c’ exits. What do you think the process tree should like after the exit? What if you use the -R flag? Learn more about what happens to orphaned processes on your own to add more context.
One last flag to explore is the -F flag, which skips intermediate steps and only asks to fill in the final process tree. Run ./fork.py -F and see if you can write down the final tree by looking at the series of actions generated. Use different random seeds to try this a few times.
Finally, use both -t and -F together. This shows the final process tree, but then asks you to fill in the actions that took place. By looking at the tree, can you determine the exact actions that took place? In which cases can you tell? In which can’t you tell? Try some different random seeds to delve into this question.
6.2. 模拟作业2 - generator.py
generator.py
知识点:
包装器:
这两段代码
wait_or_die()
和只是和系统调用fork_or_die()
的简单包装器它们要么成功(通常会成功),要么检测到错误(通过检查存储在 中的返回代码)并通过调用退出
当失败时简单地退出是可以的(这里是这样的,但并非总是如此)
包装器很有用,并使代码 更易于阅读。
waitforkrcassert()main()
总结就是.py实现的一个C语言生成器...C代码生成器...说不定这是GPT编码的原理呢?
根据命令行的参数,你可以自由指定C语言代码,当然代码都是和进程相关的,通过这些代码,你可以自由练习...
7. 作业(编码作业)
小型练习...获得一些现代操作系统必须提供的基本API的体验...
花时间,称为智者,你可以做到的!
7.1. Q1
Write a program that calls fork(). Before calling fork(), have the main process access a variable (e.g., x) and set its value to something (e.g., 100). What value is the variable in the child process? What happens to the variable when both the child and parent change the value of x?
CODE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int a = 100;
printf("parent a = %d\n", a);
int rc = fork();
if (rc < 0)
{
fprintf(stderr, "fork failed\n");
exit(1);
}
else if (rc == 0)
{
printf("child a = %d\n", a);
a = 200;
printf("child a = %d\n", a);
}
else
{
printf("parent a = %d\n", a);
a = 300;
printf("parent a = %d\n", a);
}
return 0;
}
OUTPUT
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter5/hwk_code# ./q1
parent a = 100
parent a = 100
parent a = 300
child a = 100
child a = 200
EXPAIN
和书上讲的差不多。
第一行是程序 A 输出的第一行
然后fork 之后有了程序 B,所有变量都复制了一份
A继续执行,A里面的a还是100,所以输出100
A里面继续执行,改a值,所以输出300
B里面的程序也在执行,而且变量是复制的独立的空间,所以a还是100
改成200也正常输出...
7.2. Q2
Write a program that opens a file (with the open() system call) and then calls fork() to create a new process. Can both the child and parent access the file descriptor returned by open()? What happens when they are writing to the file concurrently, i.e., at the same time?
CODE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
const char *pathname = "./output.txt";
int flags = O_CREAT | O_WRONLY | O_TRUNC;
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; // 0644
// Open the file
int fd = open(pathname, flags, mode);
if (fd == -1)
{
perror("open");
exit(EXIT_FAILURE);
}
// Fork a new process
pid_t pid = fork();
if (pid < 0)
{
perror("fork");
close(fd);
exit(EXIT_FAILURE);
}
if (pid == 0)
{
// Child process
printf("Child process writing to file...\n");
const char *child_data = "Child process\n";
if (write(fd, child_data, sizeof(child_data) - 1) == -1)
{
perror("write");
close(fd);
exit(EXIT_FAILURE);
}
printf("Child process done writing.\n");
close(fd);
exit(EXIT_SUCCESS);
}
else
{
// Parent process
printf("Parent process writing to file...\n");
const char *parent_data = "Parent process\n";
if (write(fd, parent_data, sizeof(parent_data) - 1) == -1)
{
perror("write");
close(fd);
exit(EXIT_FAILURE);
}
printf("Parent process done writing.\n");
close(fd);
// Wait for the child process to finish
wait(NULL);
}
return 0;
}
OUTPUT
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter5/hwk_code# make
gcc -o q2 q2.c -Wall
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter5/hwk_code# ./q2
Parent process writing to file...
Child process writing to file...
Parent process done writing.
Child process done writing.
EXPLAIN
After
fork()
, Both the parent and child processes inherit the file descriptorfd
returned byopen()
.Both the parent and child processes write to the file using the same file descriptor
fd
What Happens When Writing Concurrently?
The output from the parent and child processes may be interleaved in the file. This is because both processes are writing to the same file descriptor, and the writes are not synchronized.
The file descriptor
fd
is shared between the parent and child processes afterfork()
. This means both processes can access and write to the file using the same descriptor.
7.3. Q3
Write another program using fork(). The child process should print “hello”; the parent process should print “goodbye”. You should try to ensure that the child process always prints first; can you do this without calling wait() in the parent?
CODE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
int main(int argc, char *argv[])
{
sem_t *sem = sem_open("my_semaphore", O_CREAT, 0644, 0);
int rc = fork();
if (rc < 0)
{
fprintf(stderr, "fork failed\n");
exit(1);
}
else if (rc == 0)
{
printf("hello\n");
sem_post(sem); // 释放信号量
}
else
{
// wait(NULL);
sem_wait(sem); // 等待信号量
printf("goodbye\n");
sem_close(sem);
}
return 0;
}
OUTPUT
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter5/hwk_code# ./q2
hello
goodbye
EXPLAIN
使用信号量(Semaphore)
信号量是一种同步机制,可以用来控制多个进程对共享资源的访问。你可以使用信号量来确保父进程在子进程之后输出。
sem_t *sem = sem_open("my_semaphore", O_CREAT, 0644, 0);
创建信号量,指针指向sem_post(sem); // 释放信号量
sem_wait(sem); // 等待信号量
sem_close(sem);
关闭信号量后,不能再对该信号量进行操作sem_unlink("my_semaphore");
删除信号量
POSIX 标准建议在删除信号量之前先关闭信号量
7.4. Q4
Write a program that calls fork() and then calls some form of exec() to run the program /bin/ls. See if you can try all of the variants of exec(), including (on Linux) execl(), execle(), execlp(), execv(), execvp(), and execvpe(). Why do you think there are so many variants of the same basic call?
CODE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void run_ls(const char *variant)
{
pid_t pid = fork();
if (pid < 0)
{
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0)
{
// Child process
printf("Running /bin/ls using %s\n", variant);
if (strcmp(variant, "execl") == 0)
{
execl("/bin/ls", "ls", NULL);
}
else if (strcmp(variant, "execle") == 0)
{
execle("/bin/ls", "ls", NULL, NULL);
}
else if (strcmp(variant, "execlp") == 0)
{
execlp("ls", "ls", NULL);
}
else if (strcmp(variant, "execv") == 0)
{
char *args[] = {"ls", NULL};
execv("/bin/ls", args);
}
else if (strcmp(variant, "execvp") == 0)
{
char *args[] = {"ls", NULL};
execvp("ls", args);
}
else if (strcmp(variant, "execvpe") == 0)
{
char *args[] = {"ls", NULL};
char *envp[] = {NULL};
execvpe("ls", args, envp);
}
else
{
printf("Unknown variant: %s\n", variant);
exit(EXIT_FAILURE);
}
// If exec* returns, it means an error occurred
perror("exec");
exit(EXIT_FAILURE);
}
else
{
// Parent process
wait(NULL); // Wait for the child to finish
printf("Child process finished.\n");
}
}
int main()
{
run_ls("execl");
run_ls("execle");
run_ls("execlp");
run_ls("execv");
run_ls("execvp");
run_ls("execvpe");
return 0;
}
OUTPUT
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter5/hwk_code# ./q4
Running /bin/ls using execl
Makefile output.txt q1.c q2.c q3.c q4 q4.c q5.c q6.c q7.c q8.c
Child process finished.
Running /bin/ls using execle
Makefile output.txt q1.c q2.c q3.c q4 q4.c q5.c q6.c q7.c q8.c
Child process finished.
Running /bin/ls using execlp
Makefile output.txt q1.c q2.c q3.c q4 q4.c q5.c q6.c q7.c q8.c
Child process finished.
Running /bin/ls using execv
Makefile output.txt q1.c q2.c q3.c q4 q4.c q5.c q6.c q7.c q8.c
Child process finished.
Running /bin/ls using execvp
Makefile output.txt q1.c q2.c q3.c q4 q4.c q5.c q6.c q7.c q8.c
Child process finished.
Running /bin/ls using execvpe
Makefile output.txt q1.c q2.c q3.c q4 q4.c q5.c q6.c q7.c q8.c
Child process finished.
EXPLAIN
程序:
先fork()调出子进程,在子进程进行exec()的ls查询...
父进程只需要wait()子进程并输出子进程结束
子进程中根据exec()和它的变体的要求输入参数即可...
exec() 变体:
execl()
: Takes a variable number of arguments, terminated byNULL
.execle()
: Similar toexecl()
, but also allows setting the environment.execlp()
: Searches for the executable in thePATH
environment variable.execv()
: Takes an array of arguments.execvp()
: Similar toexecv()
, but searches for the executable in thePATH
.execvpe()
: Similar toexecvp()
, but also allows setting the environment.
best practice:
之前一直没注意的,其实很多这种 system call 都是有返回值的,根据返回值可以进行错误处理...
有这么多变体是因为 需要 provide flexibility and convenience for different use case...
This flexibility allows developers to choose the variant that best fits their specific needs.
7.5. Q5
Now write a program that uses wait() to wait for the child process to finish in the parent. What does wait() return? What happens if you use wait() in the child?
CODE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int rc = fork();
if (rc < 0)
{
fprintf(stderr, "fork failed\n");
exit(1);
}
if (rc == 0)
{
// Child process
printf("Child process running...\n");
// Attempt to wait in the child process
int status = 0;
pid_t result = wait(&status);
if (result == -1)
{
if (errno == ECHILD)
{
printf("Child process: No child processes to wait for.\n");
}
else
{
perror("wait in child process");
}
}
else
{
printf("Child process: wait returned %d\n", result);
}
exit(42); // Exit with status 42
}
else
{
int rv2 = wait(NULL);
printf("rv2 = %d\n", rv2);
printf("goodbye\n");
}
return 0;
}
OUTPUT
root@LAPTOP-GT06V0GS:~/cs/osTEP/chapter5/hwk_code# make q5
gcc -o q5 q5.c -Wall
root@LAPTOP-GT06V0GS:~/cs/osTEP/chapter5/hwk_code# ./q5
Child process running...
Child process: No child processes to wait for.
rv2 = 1293
goodbye
EXPLAIN
prototype
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
If
wait()
successfully waits for a child process to terminate, it returns the PID of the child process that terminated.
pid_t pid = wait(NULL);
if (pid > 0) {
printf("Child process with PID %d terminated.\n", pid);
}
If
wait()
fails, it returns-1
and sets theerrno
variable to indicate the error.
pid_t pid = wait(NULL);
if (pid == -1) {
perror("wait");
}
解析:
如果在父进程用 wait(),会等待子进程执行,此时返回正确的返回值...
如果在子进程使用 wait(),会等待子进程的子进程...但是上面的程序没有,所以会进行 error handling
总结来说 wait() 会等待它的子进程执行
参数可以写上
Best Practices
Use
wait()
in the Parent Process: The parent process should callwait()
to wait for its child processes to terminate and to clean up their resources.Avoid
wait()
in the Child Process: The child process should not callwait()
unless it has its own child processes to manage, which is rare in most use cases.
7.6. Q6
Write a slight modification of the previous program, this time using waitpid() instead of wait(). When would waitpid() be useful?
CODE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int rc = fork();
if (rc < 0)
{
fprintf(stderr, "fork failed\n");
exit(1);
}
if (rc == 0)
{
// Child process
printf("Child process running...\n");
}
else
{
int rv2 = waitpid(rc, NULL, 0);
printf("rv2 = %d\n", rv2);
printf("goodbye\n");
}
return 0;
}
OUTPUT
root@LAPTOP-GT06V0GS:~/cs/osTEP/chapter5/hwk_code# make q6
gcc -o q6 q6.c -Wall
root@LAPTOP-GT06V0GS:~/cs/osTEP/chapter5/hwk_code# ./q6
Child process running...
rv2 = 1461
goodbye
EXPLAIN
Prototype
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
可以指定等待哪个子进程(通过PID来指定)
7.7. Q7
Write a program that creates a child process, and then in the child closes standard output (STDOUT FILENO). What happens if the child calls printf() to print some output after closing the descriptor?
CODE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid = fork();
if (pid < 0)
{
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0)
{
// Child process
printf("Child process running...\n");
// Close standard output
close(STDOUT_FILENO);
printf("This will not be printed to standard output.\n");
// Attempt to write to standard output
if (write(STDOUT_FILENO, "This will cause an error.\n", 26) == -1)
{
perror("write to stdout");
}
exit(0);
}
else
{
// Parent process
printf("Parent process running...\n");
// Wait for the child process to terminate
int status;
wait(&status);
if (WIFEXITED(status))
{
printf("Child exited normally with status %d.\n", WEXITSTATUS(status));
}
else if (WIFSIGNALED(status))
{
printf("Child was terminated by signal %d.\n", WTERMSIG(status));
}
}
return 0;
}
OUTPUT
root@LAPTOP-GT06V0GS:~/cs/osTEP/chapter5/hwk_code# make q7
gcc -o q7 q7.c -Wall
root@LAPTOP-GT06V0GS:~/cs/osTEP/chapter5/hwk_code# ./q7
Parent process running...
Child process running...
write to stdout: Bad file descriptor
Child exited normally with status 0.
EXPLAIN
This can lead to unexpected behavior or errors.
Forking a Child Process
using
fork()
The child process inherits the file descriptors from the parent process, including standard output (
STDOUT_FILENO
).
Closing Standard Output in the Child Process
The child process closes its standard output using
close(STDOUT_FILENO)
.After closing
STDOUT_FILENO
, any attempts to write to standard output will fail.
Attempting to Write to Standard Output
The child process attempts to print a message using
printf()
, which internally uses the standard output file descriptor.Since
STDOUT_FILENO
is closed,printf()
will fail, and the message will not be printed to the terminal.The program then attempts to write directly to
STDOUT_FILENO
using thewrite()
system call, which will also fail and seterrno
toEBADF
(Bad file descriptor).
7.8. Q8
Write a program that creates two children, and connects the standard output of one to the standard input of the other, using the pipe() system call.
CODE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
int pipefds[2];
pid_t pid1, pid2;
// Step 1: Create a pipe
if (pipe(pipefds) == -1)
{
perror("pipe");
exit(EXIT_FAILURE);
}
// Step 2: Fork the first child process
pid1 = fork();
if (pid1 < 0)
{
perror("fork");
exit(EXIT_FAILURE);
}
if (pid1 == 0)
{
// Child 1: Write to the pipe
close(pipefds[0]); // Close the read end of the pipe
dup2(pipefds[1], STDOUT_FILENO); // Redirect stdout to the write end of the pipe
close(pipefds[1]); // Close the original write end of the pipe
printf("Child 1: This message is sent to Child 2.\n");
exit(0);
}
// Step 3: Fork the second child process
pid2 = fork();
if (pid2 < 0)
{
perror("fork");
exit(EXIT_FAILURE);
}
if (pid2 == 0)
{
// Child 2: Read from the pipe
close(pipefds[1]); // Close the write end of the pipe
dup2(pipefds[0], STDIN_FILENO); // Redirect stdin to the read end of the pipe
close(pipefds[0]); // Close the original read end of the pipe
char buffer[100];
if (read(STDIN_FILENO, buffer, sizeof(buffer)) > 0)
{
printf("%s", buffer);
printf("Child 2: Received message: %s", buffer);
}
exit(0);
}
// Parent process: Wait for both child processes to finish
close(pipefds[0]); // Close the read end of the pipe
close(pipefds[1]); // Close the write end of the pipe
waitpid(pid1, NULL, 0); // Wait for the first child process
waitpid(pid2, NULL, 0); // Wait for the second child process
printf("Parent process: Both child processes have finished.\n");
return 0;
}
OUTPUT
root@LAPTOP-GT06V0GS:~/cs/osTEP/chapter5/hwk_code# make q8
gcc -o q8 q8.c -Wall
root@LAPTOP-GT06V0GS:~/cs/osTEP/chapter5/hwk_code# ./q8
Child 1: This message is sent to Child 2.
Child 2: Received message: Child 1: This message is sent to Child 2.
Parent process: Both child processes have finished.
EXPLAIN
步骤
Create a pipe using the
pipe(pipefds)
system call.returns two file descriptors
pipefds[0]
for readingpipefds[1]
for writing.
Fork two child processes.
The first child process (
pid1
) closes the read end of the pipe (pipefds[0]
).
Redirect the file descriptors in each child process to connect the standard output of one child to the standard input of the other.
Close unnecessary file descriptors in each child process.
Each child process closes the file descriptors it does not need to avoid file descriptor leaks.
Perform the desired operations in each child process.
The parent process waits for both child processes to finish using
waitpid()
.
Last updated