欢迎来到Cefler的博客
博客主页:那个传说中的man的主页
个人专栏:题目解析
推荐文章:题目大解析(3)
在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include
pid_t fork(void);
返回值:子进程中返回0,父进程返回子进程pid,出错返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核做:
fork常规用法
fork调用失败的原因
这里我们有个问题:
当父进程形成子进程之后,子进程写入,重新申请空间,写时拷贝,但何时拷贝?
答
:
父进程创建子进程的时候首先将自己的读写权限改成只读,然后再创建子进程。
此时当子进程进行写入修改时,页表转换会因为权限问题出错,操作系统就可以介入了!
为了更好理解,我们在稍微了解一下写时拷贝
写时拷贝(Copy-on-Write,简称COW)是一种优化技术,用于减少在复制数据时的开销。它通常应用于操作系统内核、虚拟内存和文件系统等领域。
在写时拷贝技术中,当一个进程试图对某个共享资源进行写操作时,操作系统并不会立即复制整个资源,而是先将该资源标记为只读状态,并为该进程创建一个新的副本。然后,该进程对该资源进行写操作时,操作系统会将这些写操作转向到新的副本上,而原来的资源仍然保持只读状态,直到有其他进程需要进行写操作时才会真正地复制。
写时拷贝技术的好处在于,它能够避免对共享资源的不必要复制,从而节省了系统资源和时间。特别是在多进程环境下,如果多个进程同时访问同一份资源,使用写时拷贝技术可以避免出现竞态条件和数据不一致的问题。
写时拷贝技术在虚拟内存和文件系统中也有广泛的应用。例如,在虚拟内存中,当一个进程需要修改其中某个页面时,操作系统会将该页面标记为只读状态,并为该进程创建一个新的页面副本;在文件系统中,当一个进程需要修改某个文件时,操作系统会将该文件标记为只读状态,并为该进程创建一个新的文件副本。这些副本都是在需要时才会真正地复制,从而避免了不必要的复制和开销。
总之,写时拷贝技术是一种非常实用的优化技术,它能够在多进程或多线程环境下有效地减少资源的复制和开销,提高系统的性能和效率。
正常终止(可以通过 echo $?
查看进程退出码):
异常退出:
exit()
函数是 C 标准库中的一个函数,用于终止当前正在运行的程序。它接受一个整数参数作为程序的退出状态码,并将控制权返回给操作系统。
exit()
函数的原型如下:
void exit(int status);
其中,status
参数表示程序的退出状态码。一般来说,退出状态码为 0 表示程序正常终止,非零值则表示程序异常终止,可以用来传递程序执行的结果或错误信息。
当调用 exit()
函数时,它会执行以下操作:
atexit()
函数,这些函数在程序终止前会被调用。fclose()
函数)。fflush()
函数)。在程序终止后,操作系统会接收到退出状态码,并根据该状态码来判断程序的执行情况。通常情况下,可以使用 echo $?
命令来查看上一个程序的退出状态码。
需要注意的是,exit()
函数不会直接返回到调用它的地方,而是将控制权交还给操作系统。因此,在 exit()
函数之后的代码将不会被执行。
另外,如果希望在程序终止前执行一些清理操作,可以使用 atexit()
函数注册一个函数,在程序退出时自动调用该函数。
int atexit(void (*function)(void));
atexit()
函数接受一个函数指针作为参数,该函数在程序终止前会被调用。可以多次调用 atexit()
函数来注册多个清理函数,它们将按照相反的顺序执行。
总结起来,exit()
函数是用于终止程序并返回退出状态码的函数,它在程序终止前执行一些清理操作,并将退出状态码传递给操作系统。
_exit()
函数是一个系统调用,用于终止当前进程的执行,并立即返回操作系统。它不同于标准库中的 exit()
函数,因为它不会进行任何清理操作,也不会刷新缓冲区或关闭文件流。相反,它会直接终止进程的执行,释放所有已分配的资源,并将退出状态码传递给操作系统。
_exit()
函数的原型如下:
void _exit(int status);
其中,status
参数表示进程的退出状态码。一般来说,退出状态码为 0 表示进程正常终止,非零值则表示进程异常终止,可以用来传递进程执行的结果或错误信息。
当调用 _exit()
函数时,它会执行以下操作:
需要注意的是,_exit()
函数不会刷新标准 I/O 缓冲区,也不会关闭打开的文件流。如果需要在进程终止前执行这些操作,可以使用标准库中的 exit()
函数。
另外,_exit()
函数并不是线程安全的,如果在多线程环境下使用,可能会导致不可预测的结果。在多线程环境下应该使用 pthread_exit()
函数来终止线程的执行。
总结起来,_exit()
函数是用于终止进程并返回退出状态码的系统调用,它直接终止进程的执行,并释放所有已分配的资源。与标准库中的 exit()
函数不同,它不会进行任何清理操作,也不会刷新缓冲区或关闭文件流。
_exit函数和exit函数都可以用于终止进程,但它们的行为和效果是略微不同的。
_exit函数是一个系统调用,它会立即结束进程,而不会进行任何清理工作(例如刷新缓冲区或关闭文件流)。这意味着,如果进程仍有未完成的I/O操作,则可能会导致数据丢失或损坏,因为退出进程将不会等待这些操作完成。此外,_exit函数不返回退出码,因为它假定进程终止是由于错误发生而不是正常完成。
相比之下,exit函数是一个库函数,它在调用前会对进程进行清理工作。例如,它会刷新所有输出流和关闭所有打开的文件描述符。然后,它会返回给操作系统一个退出码,以表示进程的状态。这个退出码可以被其他程序所使用,例如shell脚本可以根据进程的退出码确定后续行动。
那么_exit函数存在的意义是什么呢?_exit函数通常用于在进程出现致命错误时,立即终止进程。例如,如果进程遇到了无法恢复的错误,如内存耗尽或无法访问关键资源,则可以使用_exit函数,以确保进程立即停止,而不会留下任何未完成的I/O操作或其他问题。
总之,_exit函数和exit函数在使用场景上有所不同。_exit函数适用于在进程遇到无法恢复的错误时,立即终止进程;而exit函数适用于在完成某些任务后退出进程,并提供状态码以供其他程序使用。
printf打印的东西在缓冲区中,如果想要立马刷新打印要么加上\n或者使用flush刷新
而exit般结束程序后,会刷新缓冲区,_exit结束进程不会刷新
错误码和退出码都是用来表示程序执行结果的代码,但它们的含义和用途有所不同。
错误码通常是由函数或系统调用返回的,用于指示程序执行过程中发生的错误类型。例如,在 Linux 系统中,许多系统调用会返回一个整数类型的错误码,其中 0 表示调用成功,其他值则表示不同类型的错误。错误码通常是负数或非零整数,具体的取值和含义可以参考相关的文档或头文件。
退出码则是在进程正常或异常终止时返回的一个整数值,用于传递程序执行的结果或错误信息。在 Linux 系统中,退出码通常是一个非负整数,其中 0 表示程序正常终止,其他值则表示程序异常终止,可以用来传递程序执行的结果或错误信息。退出码可以通过 shell 命令 echo $?
来查看。
需要注意的是,错误码和退出码的取值范围和含义可能因操作系统、编程语言或应用场景而异,因此在使用时需要参考相关的文档或规范。另外,错误码和退出码的取值应该尽量避免重复或冲突,以免造成混淆或错误判断。
进程等待是指一个进程暂停其执行,直到某个特定条件满足为止。在操作系统中,进程等待通常用于协调多个进程之间的合作和同步,并确保进程按照特定的顺序执行。
进程等待的常见场景包括:
父进程等待子进程:父进程可能需要等待子进程完成某个任务,才能继续执行后续操作。通过wait/waitpid的方式,让父进程对子进程进行资源回收的等待过程,直到子进程退出或终止。
进程等待资源:当多个进程需要共享某个资源时,可能需要使用进程等待来避免资源竞争和冲突。一个进程等待另一个进程释放资源,以便自己可以获得该资源并进行操作。
进程等待事件:在事件驱动的编程模型中,进程可能需要等待某个事件的发生,然后再继续执行。这可以通过使用特定的系统调用或库函数来实现,例如select、poll或epoll等。
进程等待的实现方式通常涉及操作系统提供的相关系统调用或函数。这些调用或函数允许进程设置等待条件,并在等待期间将其挂起,直到满足条件为止。一旦条件满足,被等待的进程将被唤醒,并继续执行。
进程等待是操作系统中一种重要的同步机制,它可以确保多个进程之间的正确协作和资源管理。通过适当地使用进程等待,可以避免竞争条件和数据不一致等问题,提高系统的可靠性和性能。
进行进程等待的主要目的是确保多个进程之间的正确协调和同步,以避免可能产生的危害和问题。下面是进程等待的几个重要方面,以及它们解决的相关危害:
避免资源竞争:当多个进程需要共享某个资源时,如果没有适当的同步机制,可能会导致资源竞争问题。例如,如果两个进程同时试图修改同一个文件,可能会导致文件内容的混乱或损坏。通过进程等待,可以确保在一个进程使用资源时,其他进程等待,从而避免竞争条件和数据不一致的问题。
防止死锁:死锁是指一组进程相互等待彼此持有的资源,导致系统无法继续执行的情况。如果没有适当的进程等待机制,可能会发生死锁问题,导致系统崩溃或无法正常运行。通过合理地使用进程等待,可以有效地避免死锁的发生,提高系统的可靠性和稳定性。
合作和同步:在某些情况下,多个进程可能需要按照特定的顺序执行,或者需要等待其他进程完成某个任务后才能继续执行。例如,父进程可能需要等待子进程完成某个计算任务,然后再进行下一步操作。通过进程等待,可以实现进程之间的合作和同步,确保它们按照特定的顺序执行,并在需要时相互等待。
提高系统性能:适当地使用进程等待机制可以提高系统的性能和效率。通过让进程在必要时等待,可以避免无谓的忙等待和资源浪费。相反,进程可以在等待期间释放CPU资源,使其他进程有机会执行,从而提高整个系统的吞吐量和响应性能。
总之,进程等待是为了解决多个进程之间可能产生的资源竞争、死锁、协作和性能问题。通过适当地使用进程等待机制,可以确保进程之间的正确协调和同步,提高系统的可靠性、稳定性和性能。
解决子进程僵尸问题带来的内存泄漏问题
在操作系统中,可以使用wait和waitpid两个系统调用来实现进程等待。
wait系统调用:
wait系统调用用于父进程等待子进程的终止,并获取子进程的退出状态。其语法如下:
pid_t wait(int *status);
父进程在调用wait后会被阻塞,直到有子进程终止。当子进程终止时,父进程会解除阻塞并继续执行。父进程可以通过status指针获取子进程的退出状态,以进行后续处理。
waitpid系统调用:
waitpid系统调用也用于父进程等待子进程的终止,但相比wait,waitpid提供了更多的选项和灵活性。其语法如下:
pid_t waitpid(pid_t pid, int *status, int options);
waitpid系统调用与wait类似,但它允许通过pid参数指定特定的子进程。此外,waitpid还可以选择非阻塞模式,即父进程不会被阻塞并立即返回。
使用这两个系统调用,父进程可以合理地进行进程等待,并根据需要获取子进程的退出状态或进行其他处理。
使用wait和waitpid系统调用需要包含
头文件。
在Linux下,阻塞等待和非阻塞等待都是常用的进程/线程间通信方式。
阻塞等待(Blocking Wait)
当一个进程或线程调用了阻塞式等待函数时,该进程或线程会被挂起,直到等待条件满足为止。阻塞等待是同步等待的一种形式,可以让流程顺序执行,但缺点是会让其他任务停滞,从而降低整个系统的并发性能。常见的阻塞等待函数包括read、write、accept、select、poll等。
以read函数为例,在读取文件时,如果文件中没有数据可读,则read函数就会阻塞等待数据可用,直到有数据可读为止。示例如下:
#include
#include
#include
#include
#include
int main() {
int fd = open("example.txt", O_RDONLY);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
char buffer[1024];
ssize_t nbytes;
// 阻塞等待文件描述符可读
nbytes = read(fd, buffer, sizeof(buffer));
if (nbytes == -1) {
perror("read");
exit(EXIT_FAILURE);
}
printf("Read %zd bytes: %s\n", nbytes, buffer);
close(fd);
return 0;
}
在上述示例中,read函数会阻塞等待文件描述符fd可读,并将读取到的数据存储在buffer中。
非阻塞等待(Non-Blocking Wait)
当一个进程或线程调用了非阻塞式等待函数时,该函数立即返回,无论等待条件是否满足。非阻塞等待是异步等待的一种形式,它允许调用者在等待期间执行其他任务,并定期检查等待条件是否已满足。常见的非阻塞等待函数包括fcntl、select、poll等。
以fcntl函数为例,在设置文件描述符为非阻塞模式时,可以使用fcntl函数实现。示例如下:
#include
#include
#include
#include
#include
int main() {
int fd = open("example.txt", O_RDONLY | O_NONBLOCK);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
char buffer[1024];
ssize_t nbytes;
// 非阻塞等待文件描述符可读
nbytes = read(fd, buffer, sizeof(buffer));
if (nbytes == -1 && errno == EAGAIN) {
printf("No data available yet. Continue with other tasks.\n");
} else if (nbytes == -1) {
perror("read");
exit(EXIT_FAILURE);
} else {
printf("Read %zd bytes: %s\n", nbytes, buffer);
}
close(fd);
return 0;
}
在上述示例中,open函数使用O_NONBLOCK标志打开文件,将文件描述符设置为非阻塞模式。当调用read函数读取文件时,如果文件中没有数据可读,则read函数会立即返回,并设置errno为EAGAIN。程序可以继续执行其他任务,等待条件满足后再次尝试读取数据。
需要注意的是,在实际开发中,要根据不同的情况选择使用阻塞等待或非阻塞等待。阻塞等待适合于同步、顺序执行的场景,需要等待某个条件满足后再继续执行;而非阻塞等待适合于异步、并发执行的场景,允许调用者在等待期间执行其他任务,并定期检查等待条件是否已满足。
#include
2 #include<stdlib.h>
3 #include<sys/wait.h>
4 #include<sys/types.h> //pit_t
5 int fun()
6 {
E> 7 printf("call fun funciton done!\n");
8 return 11;
9 }
10 void Woker()
11 {
12 int cnt = 5;
13 while(cnt--)
14 {
E> 15 printf("I am child process,pid: %d ppid: %d,cnt: %d\n",getpid(),getppid(),cnt);
16 sleep(1);
17 }
18 }
19 int main()
20 {
21 pid_t id = fork();
22 if(id == 0)
23 {
24 //child process
25 Woker();
26 exit(0);
27 }
28 else{
29 //father process
30 sleep(10);
31 pid_t rid = wait(NULL);
32 if(rid==id)
33 {
E> 34 printf("wait success,pid: %d\n",getpid());
35 }
36 sleep(10);
37 }
38 return 0;
39 }
命令行输入:
while :; do ps ajx|head -1&& ps ajx |grep myprocess | grep -v grep;sleep 1;echo "---------------------";done
要获取进程的状态,可以使用waitpid系统调用或者wait系统调用。这两个系统调用都可以等待子进程的终止并获取其状态信息。
waitpid系统调用允许指定具体的进程ID来等待,而wait系统调用则等待任意子进程终止。这里以waitpid为例,下面是获取进程状态的一般步骤:
下面是一个示例代码,演示了如何使用waitpid来获取进程的状态:
#include
#include
#include
#include
#include
int main() {
pid_t child_pid;
int status;
child_pid = fork();
if (child_pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
} else if (child_pid == 0) {
// 子进程执行的任务
printf("Child process: PID=%d\n", getpid());
sleep(1); // 模拟子进程执行一段时间
exit(EXIT_SUCCESS);
} else {
// 父进程等待子进程终止并获取状态
printf("Parent process: Child PID=%d\n", child_pid);
waitpid(child_pid, &status, 0);
if (WIFEXITED(status)) {
int exit_status = WEXITSTATUS(status);
printf("Child process exited with status %d\n", exit_status);
} else if (WIFSIGNALED(status)) {
int signal_num = WTERMSIG(status);
printf("Child process terminated by signal %d\n", signal_num);
}
}
return 0;
}
在上述示例代码中,父进程调用fork创建子进程,并在子进程中进行一些任务。子进程执行完任务后使用exit退出,并传递一个合适的状态码。父进程调用waitpid等待子进程终止,并通过status参数获取子进程的状态信息。最后,通过WIFEXITED和WEXITSTATUS宏来判断子进程是否正常退出,并获取退出状态。
需要注意的是,waitpid和wait系统调用会阻塞父进程,直到子进程终止。如果不希望阻塞父进程,可以使用非阻塞方式(例如,使用waitpid的WNOHANG选项或使用信号处理机制)来获取进程状态。
总结起来,通过使用waitpid或wait系统调用以及相应的宏定义,可以获取进程的状态信息。
在wait系统调用中,参数status是一个指向整型变量的指针,用于存储子进程的退出状态。status变量是一个位图(bitmap),其中不同的位表示不同的状态信息。下面是status变量中各个位的含义:
可以使用waitpid和WIFEXITED宏以及WEXITSTATUS宏来检查status变量中的不同位,以获取子进程的状态信息。例如,以下是一个使用waitpid和WIFEXITED宏的示例:
pid_t pid;
int status;
pid = waitpid(child_pid, &status, 0); // 等待子进程结束
if (pid == -1) {
perror("waitpid");
exit(EXIT_FAILURE);
}
if (WIFEXITED(status)) { // 子进程正常退出
int exit_status = WEXITSTATUS(status);
printf("Child process exited with status %d\n", exit_status);
} else if (WIFSIGNALED(status)) { // 子进程被信号终止
int signal_num = WTERMSIG(status);
printf("Child process terminated by signal %d\n", signal_num);
} else if (WIFSTOPPED(status)) { // 子进程被暂停
int signal_num = WSTOPSIG(status);
printf("Child process stopped by signal %d\n", signal_num);
} else if (WIFCONTINUED(status)) { // 子进程被恢复执行
printf("Child process resumed\n");
}
上述代码中,首先使用waitpid等待子进程结束,并将子进程的状态信息存储在status变量中。然后,使用WIFEXITED、WIFSIGNALED、WIFSTOPPED和WIFCONTINUED宏分别检查子进程的不同状态,并采取不同的处理方式。
总之,wait系统调用中指针整型变量status是一个位图,其中不同的位表示不同的状态信息。可以使用waitpid和一些宏定义来检查这些状态信息,并获取子进程的退出状态。
如下是一个进程status返回值:可以用位图解释
如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注❤️ ,学海无涯苦作舟,愿与君一起共勉成长