Linux系统编程手册 学习笔记(一)

第三章 系统编程概念

1、系统调用的概念:
用户通过系统调用来进入到linux内核中,可以利用应用程序编程接口(API)的形式来访问系统提供的服务。其中包括创建新的进程、执行I/O、为进程间通信创建管道等。


2、用户态进行系统调用情况:
用户通过调用glibc库中的外壳函数来发起系统调用。
外壳函数会将系统调用所需参数传入到特定的寄存器內;系统调用进入内核的方式都是一样的。
内核为了区分要调用哪一个系统操作,所以使得外壳函数将需要使用的系统调用编号也要存放到相应的cpu寄存器中(%eax)。
系统调用完毕会返回给外壳函数一个值来表明调用结果,然后外壳函数利用返回值来设置全局变量errno。然后,外壳函数会返回调用程序,并返回一个整型值来表明系统调用是否成功。
在linux上,系统调用的惯例是调用成功返回非负值。发生错误时,将相对应的errno常量取反再返回。外壳函数会对其再次取反,并将结果拷贝到errno中,同时以-1作为外壳函数的返回值返回,向调用程序表明有错误发生。
系统调用的开销小,但系统在用户态与核心态之间的切换耗时较长,不如直接使用库函数。


3、库函数:
库函数有的可以不使用任何系统调用,而有的则建立在系统调用之上。例如,库函数fopen()就利用系统调用open()来执行打开文件的实际操作。标准c语言函数库:glibc。在ubuntu上可以使用 ldd –version 来查看自己的glibc版本号。


4、处理错误:
几乎每个系统调用和库函数都会返回某类状态值,用来表明调用是否成功。要了解调用是否成功,必须要对状态值进行检查。如果调用失败,必须作出响应,至少要将错误信息显示出来,警示有错误发生。不然少敲几行代码的代价就是浪费大量的程序调试时间。
每个系统调用的手册页记录有调用可能的返回值,并指出了哪些值表示错误。通常,返回值为-1表示出错。因此,需要对系统调用的返回值进行检查。:

fd=open(pathname,flags,mode);  /*系统调用去打开一个文件*/
if(fd==-1){
    /*判断系统调用的返回值,返回-1则发现系统调用出错
    如果发现系统调用失败,则进行解决错误方案*/
}
...
if(close(fd)==-1)
{
    /*判断系统调用的返回值,返回-1则发现系统调用出错
    如果发现系统调用失败,则进行解决错误方案*/
}

系统调用失败时,会将全局整型变量errno设置为一个正值,用来表示具体的错误。
程序应包含相应的头文件,该文件提供了对errno的声明,以及一组针对各种错误编号而定义的常量。所有这些错误的符号名都以字母E开头。
利用errno诊断系统调用错误的一个简单示例:

#include <errno.h>  //要调用errno,就必须要包含该头文件
cnt=read(fd,buf,numbytes);
if(cnt==-1){
    if(errno=EINTR)
        fprint(stderr,"read was interrupted by a signal\n");
    }else{
        /*一些其他的错误发生了*/
    }

如果调用系统调用和库函数成功,errno绝对不会被重置为0。
在进行错误检查是,必须坚持首先检查函数的返回值是否表明调用出错,然后再检查errno确定错误原因。而一些特殊的系统调用(会在调用成功时,仍然返回-1),在判断这类系统调用是否发生错误时,应在调用前将errno置为0,并在调用后对其进行检查。
系统调用失败后,常见的做法之一就是根据errno值打印错误消息。提供的库函数perror()和strerror()就是为了打印错误消息。
函数perror()会打印出其msg参数所指向的字符串,紧跟一条与当前errno值相对应的消息

#include <stdio.h>
void perror(const char * msg);

函数strerror()会针对其errnum参数中所给定的错误号,返回响应的错误字符串。

#include<string.h>
char *strerror(int errnum);
returns pointer to error string corresponding to errnum

两个函数的调用示例:

#include<stdio.h>
#include<string.h>
#include<errno.h>


int main ()
{
   FILE *fp;

   /* 首先重命名文件 */
   rename("file.txt", "newfile.txt");

   /* 现在让我们尝试打开相同的文件 */
   fp = fopen("file.txt", "r");
   if( fp == NULL ) {
       /*用来输出错误的编号*/
       printf("errno is: %d\n",errno); 
        /*输出错误编号相对应的信息*/
        printf("errno is: %s\n",strerror(errno));
       strerror(errno);
       /*输出字符串,并返回相应错误的信息*/
      perror("Error: ");
      return(-1);
   }
   fclose(fp);

   return(0);
}

以上代码调用显示的结果为
运行结果1:
运行结果1
运行结果2:
运行结果2
截取其他示例:
其他示例

由于strerror()所返回的字符串可以是静态分配的,这意味着后续对strerror()的调用可能会覆盖该字符串。
如果无法识别errnum所含的错误编号,则strerror()会返回“Unkonwn error nnn”形式的字符串。在这种情况下,strerror()也有可能返回NULL。
处理来自库函数的错误时:
不同的库函数在调用发生错误时,返回的数据类型和值也可能不一样。从处理方式来看,可分为一下几类:
1)某些库函数返回错误信息的方式与系统哦你调用完全相同,——发生错误返回-1,并伴以errno来表示具体的错误。例如remove()函数。对于此类的函数的处理方式与系统调用完全相同。
2)某些库函数在出错时会返回-1之外的其他值,但仍会设置errno来表明具体的出错情况。例如,fopen()在出错时会返回一个NULL指针,但还会根据出错的具体底层系统调用来设置errno。那么就还可以使用perror()和strerror()来诊断此类错误。
3)有些函数发生错误时不会使用errno。那么确定错误存在与否和它的起因的方法就要随函数来具体设定。

猜你喜欢

转载自blog.csdn.net/weixin_39116058/article/details/82121070