⛄️ printf函数输出问题
printf函数不会直接将数据输出到屏幕,而是首先放到缓冲区,只有以下四种情况之一满足的时候,才会刷新缓冲区:
- 缓冲区满了,会自动刷新缓冲区
- 强制刷新缓冲区的时候 fflush
- 程序结束的时候,如果缓冲区还有数据的话,会执行刷新指令
- 当printf函数中含有换行符的时候,会强制刷新缓冲区,输出其中的数据
比如下面这段程序
test.c
对其进行编译执行的时候,现象是,先睡眠3秒,再输出printf里面的数据,为什么这样呢?
其原因是printf中的hello会先放入缓冲区中,当满足上面提到的四种情况才会将其中的数据输出,否则不会输出,程序先睡眠3秒,执行到退出命令时,检查缓冲区,这个时候发现有数据,就将数据刷新出来,所以hello显示在屏幕上。
如下图所示:
但是如果这个时候在printf函数后面加上一个fflush强制刷新,或者在其后面加上一个换行符号,那么会先输出hello,后睡眠3秒,如下图所示:
其中的stdout为屏幕标准输入流,执行之后效果如下:
如果退出函数为_exit(0),则不会刷新缓冲区,如下图所示:
直接连hello就不输出了,十分神奇,其原因就是因为没有刷新缓冲区导致的
🐑 主函数参数介绍
int main(int argc, char* argv[], char* envp[])
//argc 参数个数
//argv 参数内容
//envp 环境变量
可以写一段代码,来显示这些参数都是什么值:
🍄 复制进程fork
🍇 fork方法
pid_t fork(void);
函数的返回类型pid_t实质是int类型,Linux内核2.4.0版本对于pid_t的定义为:
typedef int __kernel_pid_t;
typedef __kernel_pid_t pid_t;
一旦调用fork就会新生成一个进程,其中调用fork函数的进程为父进程,新生成的进程为子进程。在父进程中返回子进程的pid,在子进程中返回0,失败返回-1。
开辟子进程示意图,和返回的pid示例,可以参考下图:
对于fork之后,子进程和父进程的判断,我们可以采用对其返回的pid进行判断来进行区分,如果是0那么就是子进程,如果非0那么就是父进程。
fork之后,父子进程并发运行。
看一下fork的声明:
🌴 示例
写一端代码,来实现演示一下fork函数的作用,如下图所示:
示例1:
下面是一段代码,一个父进程,生成了一个子进程,父子进程做不同的事情:
父进程的父进程ppid为2618,父进程的pid为5834,子进程的当前进程为5835,子进程的父进程ppid为5834,整个程序的过程如下图:
示例2:
这里面写了一个C语言程序,C语言里面有一个循环,循环里面一个fork函数,完了输出一个A
运行该程序,结果如下图所示:
输出了6个A
为什么会出现6个A,其原理和过程如下图所示:
🍋 写时拷贝
fork函数为进程开辟一个子进程的时候,将父进程的空间页表赋值一份给子进程,但是如果没有对某个页表的数据进行修改,那么父子进程会共享这些空间,而如果要改变某个页表中的数据,即写入某个页表,那么子进程会再次分配一个相同的页表,这个页表大小与要改变数据的页表大小一样,则子进程会使用这个新的页表区,对数据进行改变,如下图所示:
🍬 物理地址和逻辑地址
C语言程序,在未编译的时候,已经给其中某些变量和函数入口地址设置了逻辑地址,而在编译之后,这些地址不变,而一些变量的地址会在某个范围之内随机分配,这种是为了程序安全所设计的,原理如下图所示
🐘 僵死进程
🌿 定义
看一下僵死进程的原理,子进程先于父进程结束,父进程没有调用wait()函数获取子进程的退出码,如下图所示:
但是在系统中,虽然父进程没有获取子进程的退出码,在子进程结束而父进程未结束的时候,子进程是僵死状态,但是当父进程结束的时候,系统在回收父进程的同时也会回收子进程:
为了观察上面这个效果,我们来看下面这段代码
那么对于这段代码编译运行之后,调用ps命令检查当前进程时,可以观察到下面的现象:
🍤 孤儿进程
孤儿进程指的是,父进程先于子进程结束,导致子进程没有了父进程,使得子进程成了孤儿进程,如下图所示:
但是父进程死去,子进程并不是不能继续运行下去了,Linux系统给这种孤儿进程分配一个统一的父进程:Init进程,其pid为1,所以在其接管孤儿进程之后,孤儿进程的父进程pid为1,其原理如下图所示:
但是由于从某一版本的Linux更新之后,具体我也不知道哪一版本开始,系统开始对于孤儿进程的接管跑到层级结构的上一层接管,并不是固定的由init进程接管,init进程是终极父进程,是所有进程的最终极的父进程。
为了看到这一情况,我们可以通过下面这段代码来进行观察:
运行代码,我们可以看到下面的情况:
从这里面我们可以看出是pid为1638的一个进程接管了此孤儿进程,那么这个进程是什么进程呢,我们可以查看一下所有的进程,查看一下pid为1638的为什么进程,采用下面这条命令:
ps -aux
如下图:
可以看出接管这个孤儿进程的为当前用户的最顶层进程,可以验证上面这个进程。
🍯 wait()函数
为了解决僵死进程的情况,可以使用wait函数,来对子进程进行等待,直到子进程结束,并且父进程获取子进程的退出码为止,使用方法如下图所示:
需要导入sys/wait库
获取是否成功退出、退出码的函数:
那么通过下面这段代码进行使用:
运行这段代码得到:
由此我们可以看出,父进程在没获得子进程的退出码之前一直在wait,即阻塞状态,直到获得子进程的退出码之后开始执行,注意这里面的退出码与exit(int exit_id)中的退出状态码有关,如果这里为3,那么上面的val就为3
🍭 文件操作的系统调用
示例1:
示例2:
将etc/passwd复制到当前文件夹,将其显示出来: