3. 系统编程概念

这一章讲述了关于system calls入门知识以及在执行过程中的具体细节。无论在system call还是使用library function的情况下,我们都应该检查调用的返回状态以确认是否成功。

目录

3.1 System Calls

3.2 Library Functions

3.3 标准C库,GNU C库glibc

3.4 system call和library functions的错误处理

处理system call errors

处理库函数错误

3.5 使用这本书的一些文件

3.6 移植问题

3.6.1 Feature Test Macros

3.6.2 System Data Types

3.8 练习


3.1 System Calls

对于系统调用而言,有一些点是比较重要的,如下:

  • 一个system call的调用会将处理器状态由user mode转为kernel mode,这样子CPU就可以进入被保护的kernel内存
  • system calls的数量是固定的,所以每一个system call都会被一个唯一的号码所确定
  • 每一个系统调用都可能会有自己的参数等特殊信息由user space传递给kernel space

从程序的角度来讲调用一个system call就像调用一个普通的C函数一样,但是实际在执行system call的过程中有很多其余步骤。我们这里考虑x86-32下的硬件执行,

对照着图3.1,我们可以做一个简要的分析:(以execve为例)

  1. 应用软件会调用execve function,这是一个在提供在C library中的函数(这个时候还没有真正进入内核空间)
  2. wrapper函数execve会根据trap所要求的形式,比如设置eax寄存器为trap number 11(0x0B),将所需对应位置的arguments放在正确的registers里,再调用中断0x80使得触发软中断。具体的一些过程可以参考底下的链接。之后CPU则进入kernel mode做下面的工作。
  3. mov 0x0B, eax
    ...
    int 0x80
    Linux syscall过程分析(万字长文) - 腾讯云开发者社区-腾讯云
  4. 作为对该软件中断的响应,CPU则会进入entry.S中system_call的段落中以处理所触发的trap。这里会存储寄存器里的数据在kernel stack中,检查system call number是否有效,根据sys_call_table而找到下一步的routine,进入到所对应的function中进行下一步,最终service routine给到system_call一个结果。最终通过register返回结果后恢复系统到user mode上。
  5. 如果系统调用不成功,则wrapper function返回errno(一般来讲是负数,具体来说-1)。

一般来讲,对system call的调用的开销会大于对普通user mode下程序使用的开销。

3.2 Library Functions

首先并不是所有的library都会涉及到system call, 比如操作string的函数就是只是发生在user mode下的函数。

但是其他的一些在system call之上的library functions则会使用system call去完成一些任务。比如fopen()里面会调用open()来完成打开文件。这里library function的目的是为了提供可兼容以及用户友好的接口。printf()可以一次性输出很多不同类型的不同数据,但是它里面实际使用的system call write()则是只能输出字节块。

3.3 标准C库,GNU C库glibc

C语言库背后可以有多种实现,但是我们这里讨论linux即基于GNU C语言库glibc。关于查阅树莓派上的版本可以如以下操作:

pi@raspberrypi:/lib/arm-linux-gnueabihf $ /lib/arm-linux-gnueabihf/libc.so.6 
GNU C Library (Debian GLIBC 2.28-10+rpi1) stable release version 2.28.
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 8.3.0.
libc ABIs: UNIQUE ABSOLUTE
For bug reporting instructions, please see:
<http://www.debian.org/Bugs/>.

或者可以通过ldd实现

pi@raspberrypi:/lib/arm-linux-gnueabihf $ ldd --version
ldd (Debian GLIBC 2.28-10+rpi1) 2.28
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.

还有一种可能性即在写程序过程中直接从header file中读取,libc-version.h如下

pi@raspberrypi:/usr/include/arm-linux-gnueabihf/gnu $ cat libc-version.h
/* Interface to GNU libc specific functions for version information.
   Copyright (C) 1998-2018 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, see
   <http://www.gnu.org/licenses/>.  */

#ifndef _GNU_LIBC_VERSION_H
#define	_GNU_LIBC_VERSION_H	1

#include <features.h>

__BEGIN_DECLS

/* Return string describing release status of currently running GNU libc.  */
extern const char *gnu_get_libc_release (void) __THROW;

/* Return string describing version of currently running GNU libc.  */
extern const char *gnu_get_libc_version (void) __THROW;

__END_DECLS

#endif	/* gnu/libc-version.h */

测试程序

#include <gnu/libc-version.h>
#include <stdio.h>

int main()
{
  printf("version is %s\n", gnu_get_libc_version());
  printf("release is %s\n", gnu_get_libc_release());
  printf("__GLIBC__ = %d\n", __GLIBC__);
  printf("__GLIBC_MINOR__=%d\n",__GLIBC_MINOR__);
}

结果可得到

pi@raspberrypi:~/sysprog $ ./test
version is 2.28
release is stable
__GLIBC__ = 2
__GLIBC_MINOR__=28

平时会用到第三方程序的时候版本是非常重要的,这时候可以检查它是否符合我们所需要的版本。当然也可以在程序当中使用__GLIBC__以及__GLIBC_MINOR__的#ifdef 判定来决定它的pre-compile。

3.4 system call和library functions的错误处理

几乎每一个system call和library function都会返回一个处理结果。这些状态值应该每次都被检查,出现错误时要么做对应的处理,要么将它在某个地方展示出来。

而且相比于多花的一点写检查程序的时间,如果软件出了问题,在debug的时候会花费巨大时间来检查。

处理system call errors

一般来说, 如果在有错误的情况下,system call function会返回-1作为标志。我们则可以利用这一点来写错误处理的代码。

为了不必要的问题,建议如果可以的话,在terminal里可以输入man open (举例open system call)。在这里可以找到所对应的return value等等信息。

RETURN VALUE
       open(), openat(), and creat() return the new file descriptor, or -1  if
       an error occurred (in which case, errno is set appropriately).

在上述return value中,我们可以通过-1知道有错误,但是具体是什么我们并不知道,这个时候上述的errno就是我们了解具体问题的来源。它是一个全局整型变量,可以通过include <errno.h>来获得它的声明访问,以及它众多的错误类型定义。

ERRORS
       open(), openat(), and creat() can fail with the following errors:

       EACCES The  requested access to the file is not allowed, or search per‐
              mission is denied for one of the directories in the path  prefix
              of  pathname,  or the file did not exist yet and write access to
              the parent directory is not  allowed.   (See  also  path_resolu‐
              tion(7).)

       EDQUOT Where  O_CREAT  is  specified,  the file does not exist, and the
              user's quota of disk blocks or inodes on the filesystem has been
              exhausted.

       EEXIST pathname already exists and O_CREAT and O_EXCL were used.

       EFAULT pathname points outside your accessible address space.

       EFBIG  See EOVERFLOW.

       EINTR  While  blocked  waiting  to  complete  an  open of a slow device
              (e.g., a FIFO; see fifo(7)), the call was interrupted by a  sig‐
              nal handler; see signal(7).

       EINVAL The  filesystem  does  not support the O_DIRECT flag.  See NOTES
              for more information.

       EINVAL Invalid value in flags.

       EINVAL O_TMPFILE was specified  in  flags,  but  neither  O_WRONLY  nor
              O_RDWR was specified.

       EINVAL O_CREAT  was  specified in flags and the final component ("base‐
              name") of the new file's pathname is invalid (e.g., it  contains
              characters not permitted by the underlying filesystem).

       EISDIR pathname refers to a directory and the access requested involved
              writing (that is, O_WRONLY or O_RDWR is set).

       EISDIR pathname refers to an existing directory, O_TMPFILE and  one  of
              O_WRONLY or O_RDWR were specified in flags, but this kernel ver‐
              sion does not provide the O_TMPFILE functionality.

       ELOOP  Too many symbolic links were encountered in resolving pathname.

       ELOOP  pathname was a symbolic link, and flags specified O_NOFOLLOW but
              not O_PATH.

       EMFILE The per-process limit on the number of open file descriptors has
              been reached (see the  description  of  RLIMIT_NOFILE  in  getr‐
              limit(2)).

       ENAMETOOLONG
              pathname was too long.

       ENFILE The system-wide limit on the total number of open files has been
              reached.

       ENODEV pathname refers to a device special file  and  no  corresponding
              device  exists.   (This is a Linux kernel bug; in this situation

       ENOENT O_CREAT is not set and the named file does not exist.  Or, a di‐
              rectory  component  in  pathname does not exist or is a dangling
              symbolic link.

       ENOENT pathname refers to a nonexistent directory, O_TMPFILE and one of
              O_WRONLY or O_RDWR were specified in flags, but this kernel ver‐
              sion does not provide the O_TMPFILE functionality.

       ENOMEM The named file is a FIFO, but memory for the FIFO  buffer  can't
              be  allocated  because the per-user hard limit on memory alloca‐
              tion for pipes has been reached and the  caller  is  not  privi‐
              leged; see pipe(7).

       ENOMEM Insufficient kernel memory was available.

       ENOSPC pathname  was  to  be created but the device containing pathname
              has no room for the new file.

       ENOTDIR
              A component used as a directory in pathname is not, in  fa
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值