【操作系统】(fork)课堂问题记录
验证此实验需要在ubuntu中运行。若没有安装虚拟机,则下载MINGW,编写c程序,在Windows的cmd中运行程序。MINGW包含gcc和一系列工具,是windows下的gnu环境,让开发者在windows下可以写gnu的c/c++代码, 编译的结果是windows的可执行文件exe,PE格式的,在windows下运行。
文章目录
Q:关于fork( )的基本知识
fork()
是一个用于创建进程的系统调用函数,在 Unix 和类 Unix 操作系统中非常常见。它用于通过复制当前进程来创建一个新进程,称为子进程。fork()
的执行结果在父进程和子进程中有所不同。
主要特点:
- - 当
fork()
被调用时,它会创建一个与当前进程几乎完全相同的子进程。父进程的地址空间、环境变量等都会被子进程继承。 - - 在
fork()
的返回值中: - - 父进程 中,
fork()
返回子进程的 PID(进程 ID)。 - - 子进程 中,
fork()
返回 0。 - - 如果
fork()
调用失败,则返回 -1。
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
int main()
{
int r = fork();
if(r < 0)
{
fprintf(stderr,"fork failed\n");
exit(1);
}
else if(r == 0)
{
printf("I am child process (pid:%d)\n",(int) getpid());
}
else
{
printf("I am parent process (pid:%d)\n",(int) getpid());
}
exit(0);
}
注意事项:
-
子进程与父进程共享同一份代码,但运行的上下文不同。在
fork()
调用后,父进程和子进程都会执行fork()
后的代码,但它们通过不同的路径进行控制(根据fork()
的返回值)。 -
独立的进程空间:尽管父子进程共享相同的内存内容,但它们是独立的,修改子进程中的变量不会影响父进程,反之亦然。
-
资源消耗:
fork()
通常会消耗一定的系统资源来复制进程。如果频繁使用fork()
,可能会影响系统性能。
运行以上程序:
1.先使用 gcc filename.c -o filename 编译代码,生成可执行文件
2.运行可执行文件
Q:fork()中关于子进程与父进程哪个先执行?
通过上面的实验可以发现,fork()调用一次,返回两次,一次是在父进程中返回子进程的进程ID,另一次是在子进程中返回0.fork函数子进程与父进程之间的顺序不确定。有可能父进程先执行,也有可能子进程先执行,取决于操作系统的调度机制。
Q:关于父进程和子进程。
1.并发执行:父进程和子进程是并发执行的,顺序不确定。
2.独立的进程:父子进程有各自独立的地址空间,子进程是父进程的副本,它们不会互相干扰。
3.资源共享:文件描述符等资源在进程中共享,但地址空间和执行流是独立的。
Q:能否控制父子进程的输出顺序?
在 Unix/Linux 系统中,fork()
创建的父进程和子进程是并发执行的,操作系统的调度程序决定它们的执行顺序,因此,默认情况下,父进程和子进程的输出顺序是无法直接控制的。但我们可以通过某些同步机制来实现对父子进程输出顺序的控制。
常见的同步方法:
wait()
函数:父进程可以调用wait()
来等待子进程执行完毕后再继续执行。这样可以保证父进程的输出在子进程之后。
//wait_fork.c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork(); // 创建新进程
if (pid < 0) {
// fork() 失败
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子进程
printf("This is the child process. PID: %d\n", getpid());
} else {
// 父进程等待子进程结束
wait(NULL); // 等待子进程结束
printf("This is the parent process. PID: %d\n", getpid());
}
return 0;
}
在这个示例中,父进程调用了 wait()
,因此它会等待子进程执行完后才会输出信息,从而确保子进程的输出在前,父进程的输出在后。
- 管道(
pipe
):通过使用管道,父进程和子进程可以通过读写管道进行通信,从而控制它们的执行顺序。
pipe()
是一种进程间通信机制。父进程和子进程可以通过管道来交换数据,从而控制它们的输出顺序。
//pipe_fork.c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
int pipefd[2]; // 用于通信的管道
pipe(pipefd); // 创建管道
pid_t pid = fork(); // 创建子进程
if (pid < 0) {
// fork() 失败
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子进程
close(pipefd[0]); // 关闭读取端
printf("This is the child process. PID: %d\n", getpid());
write(pipefd[1], "done", 4); // 向管道写入完成标志
close(pipefd[1]); // 关闭写入端
} else {
// 父进程
char buffer[5];
close(pipefd[1]); // 关闭写入端
read(pipefd[0], buffer, 4); // 等待读取子进程的信号
buffer[4] = '\0';
printf("This is the parent process. PID: %d\n", getpid());
close(pipefd[0]); // 关闭读取端
}
return 0;
}
在这个示例中,子进程向管道中写入数据,父进程通过管道读取数据,读取到数据后才继续执行,保证了子进程先执行并输出内容。
- 信号(
signal
):通过使用信号,父进程和子进程可以相互通知对方已经完成某些任务,协调执行顺序。
父子进程可以通过信号相互通知,确保输出顺序。例如,父进程可以通过信号等待子进程完成后再执行。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void child_done(int signum) {
// 处理子进程完成的信号
}
int main() {
signal(SIGUSR1, child_done); // 父进程设置信号处理器
pid_t pid = fork(); // 创建子进程
if (pid < 0) {
// fork() 失败
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子进程
printf("This is the child process. PID: %d\n", getpid());
kill(getppid(), SIGUSR1); // 发送信号给父进程
} else {
// 父进程等待信号
pause(); // 等待子进程信号
printf("This is the parent process. PID: %d\n", getpid());
}
return 0;
}
在这个示例中,子进程通过 kill()
发送 SIGUSR1
信号给父进程,父进程接收到信号后再继续执行,从而确保子进程先输出,父进程后输出。
Q:在fork之前定义一个变量并为变量赋初值,在父进程和子进程处理时改变变量的值,最后输出的是父进程里的还是子进程里面的?
在调用 fork()
函数之前定义并赋值的变量,在 fork()
之后,父进程和子进程各自会拥有一份相同的变量副本。也就是说,fork()
会将进程的内存空间(包括变量)复制一份给子进程。
因此,在 fork()
之后,父进程和子进程各自对变量的修改互不影响,两个进程拥有独立的内存空间。每个进程可以独立地修改该变量,而不会影响对方的值。
#include <stdio.h>
#include <unistd.h>
int main() {
int x = 99;
pid_t pid = fork();
if (pid == 0) {
// 子进程
x = 33;
printf("子进程: x = %d\n", x);
} else if (pid > 0) {
// 父进程
x = 66;
printf("父进程: x = %d\n", x);
} else {
// fork 失败
printf("fork失败!\n");
}
return 0;
}
父进程和子进程各自修改的 x
变量不会互相影响,因为它们在不同的进程中。在 fork()
之后,父进程和子进程各自独立地拥有自己的变量副本,并且对变量的修改不会影响对方。因此,输出的是各自进程中的变量值。
Q:若改变的是全局变量,最终结果有何不同?
在 fork()
之前定义并赋值的全局变量与局部变量的行为类似。虽然它是全局的,但在调用 fork()
后,父进程和子进程会各自拥有该全局变量的副本。因为 fork()
会复制整个进程的地址空间,包括全局变量,因此父子进程对全局变量的修改互不影响。
#include <stdio.h>
#include <unistd.h>
int global_var = 10; // 定义全局变量
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
global_var = 20;
printf("子进程: global_var = %d\n", global_var);
} else if (pid > 0) {
// 父进程
global_var = 30;
printf("父进程: global_var = %d\n", global_var);
} else {
// fork 失败
printf("fork失败!\n");
}
return 0;
}
说明:
- 父进程和子进程拥有独立的全局变量副本:尽管全局变量在
fork()
之前被定义并赋值,但在fork()
之后,父进程和子进程各自拥有该全局变量的独立副本,修改一方的全局变量不会影响另一方。 - 父子进程互不影响:在子进程中将全局变量设置为 20,在父进程中将其设置为 30,但这两个进程中的修改不会相互影响。
- 即使是全局变量,父子进程在
fork()
之后也有各自的副本,修改一方的全局变量不会影响另一方的全局变量。因此,父进程输出父进程修改后的值,子进程输出子进程修改后的值。
写在最后:
在课堂上的疑问课下一定要及时解答,不要越积越多。✿✿ヽ(°▽°)ノ✿(▽)┏(^0^)┛
天天开心!!!