1.文件操作的应用:
1).打开一个文件并往里面写入hello:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <assert.h>
int main()
{
int fd=open("file.txt",O_WRONLY|O_CREAT,0600);
assert(fd!=-1);printf("fd=%d\n",fd);
write(fd,"hello",5);close(fd);
exit(0);
}
2).打开文件,读取文件内容;
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <stdlib.h>
int main()
{
int fd=open("file.txt",O_RDONLY);
assert(fd!=-1);char buff[128]={0};
int n=read(fd,buff,127);
printf("n=%d,buff=%s\n",n,buff);close(fd);
exit(0);
}
3).利用读和写对文件进行复制
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>int main()
{
int fdr=open("file.txt",O_RDONLY);
int fdw=open("newfile.txt",O_WRONLY|O_CREAT,0600);if(fdr==-1||fdw==-1)
{
exit(0);
}char buff[256]={0};
int num=0;while((num=read(fdr,buff,256))>0)
{
write(fdw,buff,num);
}
close(fdr);
close(fdw);exit(0);
}
4).实现类似cp 命令
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>int main(int argc,char *argv[])
{
if(argc!=3)
{
printf("argc error\n");
}
char *file_name=argv[1];
char *newfile_name=argv[2];int fdr=open(file_name,O_RDONLY);
int fdw=open(newfile_name,O_WRONLY|O_CREAT,0600);if(fdr==-1||fdw==-1)
{
exit(0);
}char buff[256]={0};
int num=0;while((num=read(fdr,buff,256))>0)
{
write(fdw,buff,num);
}
close(fdr);
close(fdw);exit(0);
}
2.面试题详解fork
面试题目1:
(1)fork 以后,父进程打开的文件指针位置在子进程里面是否一样?(先open再fork)
(2)能否用代码简单的验证一下?
(3)先fork再打开文件父子进程是否共享偏移量?父进程打开的文件指针位置在子进程里面是否一样?能否用代码简单验证一下.(先fork再open会怎么样?)
inode:
文件数据都储存在”块”中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为”索引节点”。
每一个文件都有对应的inode,里面包含了与该文件有关的一些信息。通过这个inode节点,即通过文件具体的一些信息,我们才能找到这个文件,读取它.
每个inode都有一个号码,操作系统用inode号码来识别不同的文件。
2).先打开再fork的流程(重点)
代码如下:
先创建一个文件file.txt,内容为abcdefg;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>int main()
{
int fd=open("file.txt",O_RDONLY);
assert(fd!=-1);pid_t pid=fork();
assert(pid!=-1);if(pid==0)
{
char buff[128]={0};
int n=read(fd,buff,1);
printf("child:%s\n",buff);
sleep(1);
n=read(fd,buff,1);
printf("child:%s\n",buff);
}
else
{
char buff[128]={0};
int n=read(fd,buff,1);
printf("parent:%s\n",buff);
sleep(1);
n=read(fd,buff,1);
printf("parent:%s\n",buff);}
close(fd);
exit(0);
}
父进程打开文件以后,fork产生子进程,父子进程共享打开的文件,同时共享文件偏移量;
为什么?如图:
3).先fork再open
代码修改如下:
pid_t pid=fork();
assert(pid!=-1);int fd=open("file.txt",O_RDONLY);
assert(fd!=-1);
(了解文件偏移量不共享)
为什么?如图:
面试题答案:
在 (1)fork 之前打开的文件,在复制进程后,父子进程共享文件偏移量,所以文件指针在相同位置。(2)代码详见课件(3)先fork再打开文件,父子进程各自打开各自的,不共享偏移量;
面试题目2:
4).系统调用与库函数的区别
比如自己写的函数,调用的时候就是调换到函数的入口地址一句一句执行,但是系统调用就不一样,系统调用一旦执行,我们就需要 从用户空间切换到内核空间.
比如fopen :库函数 open:系统调用 fork:系统调用
可以man fopen (显示3),man 2 open (显示2),man fork (显示2)
系统调用的执行过程:
在Linux中,每个系统调用都被赋予了一个系统调用号.这样,通过这个独一无二的号就可以关联系统调用.当用户空间的进程执行一个系统调用的时候,这个系统调用号就用来指明到底是要执行哪个系统调用号;进程并不会提及系统调用的名称;
系统调用是为了方便使用操作系统的接口,而库函数则是为了人们编程的方便;
库函数调用与系统无关,不同的系统,调用库函数,库函数会调用不同的底层函数实现,因此可移植性好;
5).malloc和free的三个问题:
思考下面三个问题:
(1)申请了一块空间没有free,进程就结束了,那么空间被回收了吗?
(2)malloc()申请3G的内存能否成功?判断依据是什么?
(3)父进程堆区申请的空间复制后,子进程也会有一份,也需要释放?
演示代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>int main()
{
char *s=(char *)malloc(1024ll*1024*1024*3);
assert(s!=NULL);
memset(s,0,1024ll*1024*1024*3);
printf("main over!\n");
exit(0);
}
1.进程在执行的过程中,malloc申请空间,不使用时,没有free就会出现内存泄漏;
如果进程结束了,那么所有向操作系统申请的内存都会被回放(释放);
2.申请1G或者更大空间,到底能不能成功?
如果当前的物理内存剩余空间够用,那么申请的空间肯定能成功;
如果不够用,我们先要看有没有虚拟内存,如果没有,不能成功;如果有虚拟内存,那么我们看内存+虚拟空间的大小能否满足,如果满足,那么我们是可以申请成功的,如果不够,当然不能成功;
首先我们需要了解一个名词:虚拟内存:
基于分页技术或者分页和分段技术的组合的虚拟内存,是现代计算机中内存管理最常用的方法之一.虚拟内存对应用程序完全透明,使得每个进程在执行时好像有无限的内存可用.为实现这一点,操作系统为每个进程在磁盘上创建一块虚拟地址空间,即虚拟内存.在需要的时候可以把部分虚拟内存载入到正在的内存中.这样,多个进程便可以共享相对比较小的内存.为了使虚拟内存载入到真正的内存中.这样,多个进程便可以共享相对比较小的内存.为了使虚拟内存更为有效,需要硬件机制来执行基本的分页和分段功能,如虚拟地址和实地址之间的地址转换.
虚拟内存提供的三个重要的能力:
1) 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,根据需要在磁盘和主存之间来回传送数据,使得能够运行比内存大的多的进程。
2) 它为每个进程提供了一致的地址空间,从而简化了存储器管理.
3) 它保护每个进程的地址空间不被其他进程破坏 .
了解两个命令:
sudo swapoff -a;关闭虚拟内存;
sudo swapon -a;开启虚拟内存;
(3)(3)父进程堆区申请的空间复制后,子进程是不是也会有一份?是不是也需要释放?
我们先来看下面的代码:
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
char *s=(char *)malloc(128);
assert(s!=NULL);pid_t pid=fork();
assert(pid!=-1);free(s);
exit(0);
}
编译运行并没有出错,如果是共享空间的话, 那么父子进程会对一个空间分别free,我们有前面学过的C语言可以知道,如果我们对一个空间free两次,编译运行会出现错误.
所以父子进程堆空间不共享(这里指的是每个进程的堆空间).哪怕父子进程对申请的对空间都没有操作.
其实如果对空间操作也是没有问题的,如下:
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h> //1
int main()
{
char *s=(char *)malloc(128);
assert(s!=NULL);pid_t pid=fork();
assert(pid!=-1);if(pid==0)//2
{
strcpy(s,"child");//3
}
else //4
{
strcpy(s,"parent");//5
}
printf("s=%s\n",s); //6
free(s);exit(0);
}
结论:
父进程堆区申请的空间复制后,子进程也有一份.也需要释放;也就是说,fork会把进程的上下文都复制一遍,如果是malloc申请的话,内核会给子进程分配和父进程一样多的空间,父子进程都需要分别free;