系统调用直观解释 (与标准IO的对比)

来源:互联网 发布:淘宝买dota2饰品流程 编辑:程序博客网 时间:2024/05/01 22:31

系统调用

分类: 文件I/O操作 2010-01-08 11:21 288人阅读 评论(1)收藏 举报
fplinux工作null数据库c

 

Linux系统将进程的可执行空间分为用户空间和内核空间,如图

系统调用

这是因为在Linux中用户程序不能直接访问内核提供的服务。为了更好的保护内核空间,将程序的运行空间分为内核空间和用户空间,他们运行在不同的级别上,在逻辑上是相互隔离的。

这好比是我们去银行取钱,我要取500元钱,我先走到办公窗口,将我的服务请求(取钱)告诉工作人员,让他帮我从我的个人帐户里面取出500元钱来,然后将钱通过窗口送给我

,因为我们都知道,银行这样做的目的很明显,银行里面的用户帐户数据库资源还有抽屉里的钱,是受到保护的,用户没有权利去自己取钱,再从自己帐户上减掉500,我们linux的用户空间就是银行外面的用户大厅,内核空间就是办公区域,通过一面玻璃墙将两区域分开。

假如我们有下面的程序

helloworld.c

Int main(void)

{

printf(“hello world/n”);

}

gcc helloworld.c –o test

当执行./test时, test可执行文件作为一个用户应用程序他是工作在用户空间,他通过标准C的库函数printf打印出一个字符串,很明显,打印字符串的操作要将字符串信息调入显存区,将其显示到屏幕上,这一操作要访问硬件资源,必须要在内核里面实现(用户没有权力去直接操作硬件),他通过系统调用将字符串传递到内核空间,交给内核打印程序,内核打印完后,将返回值返回给用户空间,这里的系统调用好比银行里的办公窗口,打印字符串请求好比我们去取钱,字符串传递给内核空间好比我们将存折传递给办公区的服务人员,内核字符串打印处理函数好比工作人员帮我们操作我们的帐户取钱,最后,内核将打印结果返回,好比工作人员将取的钱和存折和小票再通过窗口给我们。

因此系统调用是指操作系统提供给用户程序的一组“特殊”接口,用户程序可以通过这组“特殊”接口来获得操作系统内核提供的特殊服务。

这里的特殊接口说白了其实就是函数,系统调用其实就是对内核函数进行调用,通过内核函数的参数将数据传进内核。看下面的例子。

 #include <stdio.h>

  #include <unistd.h>

  int main(void)

  {

          if(write(STDOUT_FILENO, "hello world", 11) < 0)

                   perror("write");

          return 0;

  }

上面例子中的write是一个系统调用,他的作用是将helloworld字符串写到标准输出里,也就是我们的屏幕上。之后我们会讲到write的具体用法。

有的同学可能会问,既然,可以用系统调用实现打印字符串,为什么还要用标准C的printf呢,这不是多转了一圈吗?请看下面的例子。

int main(void)

{

printf(“hello world/n”);

sleep(60);

}

sleep(60);是让程序休眠60秒,下面是执行的结果。

 带缓存的打印

sleep函数在Printf后被调用,按理说,应该先打印helloworld再睡觉,但是结果却没有打印。这是因为,printf在实现打印字符串的基础上还增加了缓存机制。Printf在用户空间维护一个缓存区,将要打印的数据全部放到缓冲区里面去,等缓冲区满了,他就会去执行系统调用去打印字符串,那有的同学又会说,为什么我单独写一条打印语句就没有问题啊,这是因为,打印缓冲区里的内容还有一个条件,就是动态的调用fflush()函数,这个函数是清空缓冲区的意思,一调用它,就会先将缓冲区内的数据,该打印的打印,该发送的发送。而我们的函数在执行结束后(应该说是main函数返回后)系统会自动的调用exit()函数,exit()函数会自动调用fflush(stdout)函数,这个之后我们会有讲到。

因此我们将上面的函数改一下:在sleep前加上fflush(stdout)

Int main(void)

{

printf(“hello world/n”);

fflush(stdout);

sleep(60);

}

 理想的打印结果

这样我们的hellowrold就先打印出来,再睡觉了。

那标准C里有缓冲区和我就用系统调用打印有什么关系啊?

先来看下面的程序。

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

        FILE * fp1, * fp2;

        if( (fp1 = fopen("cost_in.txt", "r")) == NULL ) {

                perror("fopen1");

                exit(-1);

        }

        if( (fp2 = fopen("cost_out.txt", "w")) == NULL ) {

                perror("fopen2");

                exit(-1);

        }

        char ch;

        while( (ch = fgetc(fp1) ) != EOF )

                fputc(ch, fp2);

        fclose(fp1);

        fclose(fp2);

        return 0;

}

这是一个最简单的文件拷贝的程序,从文件cost_in.txt里面一个字符一个字符的读出来,写入到onst_out.txt里面。

再看一个系统调用的文件拷贝程序:

#include <stdio.h>

#include <stdlib.h>

#include <fcntl.h>

int main(void)

{

        int fd1, fd2;

        if( (fd1 = open("cost_in.txt", O_RDONLY)) < 0) {

                perror("open1");

                exit(-1);

        }

        if( (fd2 = open("cost_out.txt", O_WRONLY | O_CREAT)) < 0) {

                perror("open2");

                exit(-1);

        }

        char buf[2];

        while( read(fd1, buf, 1) > 0 )

                write(fd2, buf, 1);

        close(fd1);

        close(fd2);

        return 0;

}

这儿和C库的单字符拷贝一样,我每次从cost_in.txt里面读一个字节,然后写到cost_out.txt里面去,看下面的运行结果,time是用来测试程序运行时间的命令

 

 系统调用的代价

 

由此对比可见,标准C库实现的1.8M的文件拷贝只用了0.163秒,而系统调用用了13秒多,这又是为什么呢?

我们的程序有两个空间,用户空间和内核空间,程序运行在用户空间,如果要进行系统调用那么要先将CPU切换到内核的执行空间去,这里面要有个代价问题,切换一次,要保存用户空间的现场,等系统调用完了,再切回到原先执行的程序。我们对比上面的两个执行结果也可以看到标准C库的文件拷贝花费时间里user用户空间花费0.063秒,sys系统空间花费了0.013秒,而系统调用文件拷贝用户空间花费了1.18秒,系统空间花费了9.4秒,很大部分都是系统空间花费的。

再来想想刚printf的缓冲区,我们可以得出这样的结论,标准C  库函数也是通过系统调用来实现具体的功能,不过,他在调用前通过使用缓冲区对性能进行了一系列的优化,尽量少的使用系统调用,因为系统调用的代价很大。


0 0
原创粉丝点击