1.写时拷贝的概念
在Linux系统中,调用fork系统调用创建子进程时,并不会把父进程所有占用的内存页复制一份,而是与父进程公用相同的内存页,当子进程或者父进程对内存页进行修时才会进行复制,这就是写时拷贝。
当父子进程都没有修改那几个页面 那子进程那几个页面就是共享的,什么时候修改了我们就才复制新的页面(也就是创建新的物理空间),其实提高了复制的效率。
2.实现原理
fork()之后,kernel(实时操作系统)把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,不做任何动作,当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会把触发的异常的页复制一份,于是父子进程独立了。
举例:
fork()之后调用excve()。
由此引申一个问题 fork()是系统调用,那么系统调用和库函数调用有什么区别?
fopen -> open 系统调用
printf-> write
例如:printf打印时1. 库函数 ->系统调用->内核->硬件
2.也可以不用经过库函数 直接系统调用到内核
库函数本质是系统调用,本质上是一个接口(API),运行时属于用户态,开销小,而系统调用属于内核态,用户态和内核态的上下文切换开销比较大。
3.系统调用优点:
可以减少分配内存页(大量资源)带来的瞬时延时。
可减少不必要的资源分配。
4.系统调用的主要作用
为用户提供了一种硬件的抽象接口
保证了系统的稳定和安全
避免了用户直接对底层硬件的编程还有一个问题 :c编程时开辟的栈空间属于系统调用吗?
系统调用和库函数调用的区别
1、进程fork()后在open() 之后写入的过程中共享写入偏移量吗??非共享
** 2、进程open() 之后fork(),写入的过程中共享写入偏移量吗**??共享
PCB中有 一个文件表,在fork()过程中文件表也被复制了一份,所以先open()再fork()会发生偏移量的共享、。
当先fork()再open()时文件表产生的3号位置的指针对应的STRUCT FILE结构体都是各自产生的,所以不共享偏移量。
**man
系统调用 用户空间进入到内核空间 (切换到内核态)
window 上才有 文本文件和二进制文件区分,linux并没有这种区分
open() //(文件名,打开方式,权限mode),
我们试着往文件中写入数据,如下图:
open 系统调用中的0600是0400(S_IRUSR:读权限)和0200(S_IWUSR:写权限)的结合
我们可以看到写入的数据是从头部开始写入的。
write(fd(文件id信息,叫做文件描述符),“数据”,“数据长度”)
close(fd)
read返回零文件到末尾 read()==0;
1标准输入 stdin 返回FILE*
2. 标准输出 stdout 返回FILE*
3. 标准错误输出 stderr FILE*
ls -i 文件的ID (inode)号
2.我们可以从文件读入数据写到buff数组中:
3.我们可以把文件1的内容读完写入到文件2中(cp命令的实现)
不管有无b.txt文件 ,fdw 都能获取到新文件的地址,因为open()了。
注意open()函数里面第一个参数是文件的地址。
4.我们也可以通过把文件名做主函数参数传入到程序中以供使用
execl execlp execle
execv execvp
execve 系统调用
举例:用替换实现ps-f 命令
execl的用法
execl("/usr/bin/ps","ps","-f",(char*)0);//不返回值,替换失败就往下继续执行。
printf("替换失败");
exit(0);
}
execlp的用法:
printf("main pid=%dn",getpid());
execlp("ps","ps","-f",(char*)0);//p:环境变量path
printf("替换失败");
exit(0);
execle的用法:
int main(int argc,char*argv[],char*envp[])
{
printf("main pid=%dn",getpid());
execlp("/usr/bin/ps","ps","-f",(char*)0,envp);//
printf("替换失败");
exit(0);
}
execv的用法:
int main(int argc,char*argv[],char*envp[])
{
printf("main pid=%dn",getpid());
cahr *myargv[]={"ps","-f",0};
execv("/usr/bin/ps",myargv);//参数放数组里了
printf("替换失败");
exit(0);
}
execvp的用法*
int main(int argc,char*argv[],char*envp[])
{
printf("main pid=%dn",getpid());
cahr *myargv[]={"ps","-f",0};
execvp("ps",myargv);//参数放数组里了
printf("替换失败");
exit(0);
}
execve(系统调用):根本方法
int main(int argc,char*argv[],char*envp[])
{
printf("main pid=%dn",getpid());
cahr *myargv[]={"ps","-f",0};
execve("/usr/bin/ps",myargv,envp);
printf("替换失败");
exit(0);
}
下图我们可以看到我们把replace这个进程替换成了Ps-f 这个命令:。
我们可以看到PCB.里的PID是没有变的
bash 是所有待生成子进程 的父进程,bash 上新增一个环境变量,那么复制的进程中就带有原bash新增的这个环境变量
我们可以新增一个环境变量,然后供主函数envp 调用:
export MYSTR
在把所有环境变量打印:gcc -o main main9.c
./main
打印了所有环境变量。然后我们可以看见我们自己增加的环境变量MYSTR
虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。
有一个问题:内存硬件空间大小2G,那么用malloc申请2G可不可以实现?
第二个问题:内存有2G的空间,那么用malloc申请2G可不可以实现?
比如说内存4g,用户区为3G(内核区1G),那么申请3G的内存空间可以实现吗?
进程地址空间范围有限,就算是虚拟空间足够大,申请的空间大于进程地址空间,申请也是失败的。
但是申请的空间在进程地址空间范围内,内存空间不够,调用虚拟空间也可以malloc申请成功。
小知识点
打印环境变量的值($是取值符)



