系统调用与标准函数库(下)

来源:互联网 发布:淘宝怎么开服装定制店 编辑:程序博客网 时间:2024/05/18 05:52

        友情提示:本文系接上一篇博文——系统调用与标准函数库(上)

        2. 系统调用与内核

        为了更好地保护了内核,在Linux中,把程序运行空间分为内核空间和用户空间,它们分别运行在不同的级
别上。用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,但在有些情况下,就比如本人最近做的GPON项目中,应用程序经常需要与内核打交道,这个时候用户空间的进程需要获得一定的系统服务,这时,就必须通过系统调用。

        应用程序运行在用户空间,系统调用需要切换到内核空间,应用程序应该以某种方式通知内核需要切换到内核空间。通知内核的机制是靠软件中断实现的:应用程序执行异常指令,引发一个异常,程序进入中断,系统切换到内核态去执行异常处理程序。此处的异常处理程序即系统调用处理程序syscall()。所有的系统调用陷入内核的方式都一样,所以仅仅是陷入内核空间是不够的,必须以某种方式通知内核进入异常的原因。Unix系统通过系统调用号通知内核进入异常的原因,操作系统给每个系统调用分配了一个唯一的编号,这个编号就是系统调用号。用户空间进程执行一个系统调用时,这个系统调用号就被用来指明执行哪个系统调用。系统调用号相当关键,一旦分配就不能再有任何变更,否则编译好的应用程序会崩溃。此外,如果一个系统调用被删除,它所占用的系统调用号也不允许被回收利用。(路径:/usr/include/i386-linux-gnu/asm/unistd_32.h)

                          


        3. 系统调用与库

        库函数由两类函数组成:第一类是不需要调用系统调用的:不需要切换到内核空间即可完成函数全部功能,
并且将结果反馈给应用程序,如strcpy、bzero等字符串操作函数。第二类是需要调用系统调用:需要切换到内核空间,这类函数通过封装系统调用去实现相应功能,如printf、fread等。

        库函数与系统调用的关系:并不是所有的系统调用都被封装成了库函数,系统提供的很多功能都必须通过系统调用才能实现。系统调用是需要时间的,程序中频繁的使用系统调用会降低程序的运行效率。当运行内核代码时,CPU工作在内核态,在系统调用发生前需要保存用户态的栈和内存环境,然后转入内核态工作。系统调用结束后,又要切换回用户态。这种环境的切换会消耗掉许多时间。库函数访问文件的时候根据需要,设置不同类型的缓冲区,从而减少了直接调用IO系统调用的次数,提高了访问效率。

        例:应用程序调用printf函数时,函数执行的过程:

        用户调用printf()函数     ===>     printf()函数想缓冲区写数据,缓冲区的大小在标准库中定义,有些系统是4096个字节     ===>     仅当1缓冲区已满2冲洗缓冲区或者3遇到scanf()类输入流时(读者不理解的话先记住缓冲区已满这个条件就足够了其他的情况会在后续有介绍),会将缓冲区中数据输出到屏幕。

        4. 标准库I/O函数

        无论是编写系统程序还是应用程序,都离不开I/O这个重要的环节。相对于低级的I/O操作(即系统调用级的I/O),标准I/O库函数处理了很多细节,如缓存分配等,考虑到代码的可移植性,开发人员应该在编写代码时尽可能使用标准库函数。

        I/O的管理分类:由ANSI标准提供的标准IO库函数几乎被所有的操作系统支持,如windows下编写的程序几乎不用做任何修改就可以在linux下重新编译运行,如:fopen、fread、fwrite、fclose。以系统调用的方式给用户提供函数接口(遵循POSIX标准),例如linux操作系统提供的文件IO接口,如:open、close、read、write、ioctl,系统调用与操作系统直接相关,直接使用系统调用编写的程序的可移植性差。

        头文件<stdio.h>中声明了标准C库的I/O函数,标准C库的I/O函数在所有通用计算机上的C语言实现都是相同的。使用标准C库的I/O函数,打开或创建一个文件时,会返回一个指向FILE结构体的指针,该结构体包含了I/O函数为管理文件所需要的尽可能多的信息,包括了用于I/O文件的文件描述符、指向流缓存的指针、缓存长度等。(定义路径:/usr/include/libio.h,别名(typedef):/usr/include/stdio.h)

        关于库的I/O函数我在C语言博文中已有详细介绍,在此不再重述fopen、fclose、fread等函数。但作为一个想提高自己编程水平的程序员,你还是应该熟练掌握格式化输入输出函数的编程

        △格式化输入

        int scanf(const char *format, …) ==> 从标准输入读入信息
        int fscanf(FILE *stream,const char *format, …) ==> 从stream指向的文件中读入信息
        int sscanf(const char *buf,const char *format, …) ==> 从buf指定的内存区域中读入信息

        格式化输入(例)

        1、取指定宽度的字符串
        char buf[512] = "";
        sscanf("123456", "%3s", buf);
        printf("%s\n", buf);
        结果为:123
        2、仅保留字符串"hello world"中的world
        sscanf("hello world", "%*s%s", buf); 
        printf("%s\n", buf);

        结果为:world

        3、取仅包含指定字符的字符串
        sscanf("bAcd", "%[Abcf]", buf);
        printf("%s\n", buf);
        结果为:bAc
        4、取仅包含指定字符集的字符串
        ssscanf("123abcABC", "%[1-9a-z]", buf);
        printf("%s\n", buf);
        结果为:123abc

        5、取到指定字符为止的字符串
        sscanf("123456Aabcdedf", "%[^A]", buf);
        printf("%s\n", buf);
        结果为:123456
        6、取到指定字符集为止的字符串
        sscanf("123abcABC", "%[^A-Z]", buf);
        printf("%s\n", buf);
        结果为:123abc

        7、取两个字符之间的字符串
        sscanf("abc#def@ghi", "%*[^#]#%[^@]", buf);
        printf("%s\n", buf);
        结果为:def
        8、分隔字符串2012:08:18 -2012:08:18
        char buf1[100] = "", buf2[100] = "";
        sscanf(“2010:08:18 - 2012:08:18”, "%s -%s", buf1, buf2); 
        sscanf(“2010:08:18 - 2012:08:18”,"%[0-9:] -%[0-9:]", buf1, buf2);

        格式化输出:比较简单,暂不举例
        int printf(const char *format, …);  ==>  
输出到标准输出
        int fprintf(FILE *stream,const char *format, …);  ==>  
输出到文件
        int sprintf(char *buf, const char *format, …);  ==>  
输出到buf指定的内存区域
        int snprintf(char *buf, size_t n, const char *format, …);   ==>  输出n个字节到buf指定的内存区域,注意:sprintf函数没有指定写入的字符数,可能会造成由buf指向的内存区域溢出。

       格式化输入输出小demo:实现IP地址(字符串)和整型数据(4个int型数据)之间的转换,转换后:p1=192; p2=168; p3=220; p4=5,分别实现:(1)IP->整型 (2)整型->IP。

        char *host=“192.168.220.5”;
        char ipaddr[16];
        int p1, p2, p3, p4;
        scanf(host, "%d.%d.%d.%d", &p1, &p2, &p3, &p4);
        sprintf(ipaddr, "%d.%d.%d.%d", p1, p2, p3, p4);

2 3
原创粉丝点击