这一章讲述了关于system calls入门知识以及在执行过程中的具体细节。无论在system call还是使用library function的情况下,我们都应该检查调用的返回状态以确认是否成功。
目录
3.4 system call和library functions的错误处理
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为例)

- 应用软件会调用execve function,这是一个在提供在C library中的函数(这个时候还没有真正进入内核空间)
- wrapper函数execve会根据trap所要求的形式,比如设置eax寄存器为trap number 11(0x0B),将所需对应位置的arguments放在正确的registers里,再调用中断0x80使得触发软中断。具体的一些过程可以参考底下的链接。之后CPU则进入kernel mode做下面的工作。
Linux syscall过程分析(万字长文) - 腾讯云开发者社区-腾讯云mov 0x0B, eax ... int 0x80- 作为对该软件中断的响应,CPU则会进入entry.S中system_call的段落中以处理所触发的trap。这里会存储寄存器里的数据在kernel stack中,检查system call number是否有效,根据sys_call_table而找到下一步的routine,进入到所对应的function中进行下一步,最终service routine给到system_call一个结果。最终通过register返回结果后恢复系统到user mode上。
- 如果系统调用不成功,则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 fact, a
directory, or O_DIRECTORY was specified and pathname was not a
directory.
ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no
process has the FIFO open for reading.
ENXIO The file is a device special file and no corresponding device
exists.
EOPNOTSUPP
The filesystem containing pathname does not support O_TMPFILE.
EOVERFLOW
pathname refers to a regular file that is too large to be
opened. The usual scenario here is that an application compiled
on a 32-bit platform without -D_FILE_OFFSET_BITS=64 tried to
open a file whose size exceeds (1<<31)-1 bytes; see also
O_LARGEFILE above. This is the error specified by POSIX.1; in
kernels before 2.6.24, Linux gave the error EFBIG for this case.
EPERM The O_NOATIME flag was specified, but the effective user ID of
the caller did not match the owner of the file and the caller
was not privileged.
EPERM The operation was prevented by a file seal; see fcntl(2).
EROFS pathname refers to a file on a read-only filesystem and write
access was requested.
ETXTBSY
pathname refers to an executable image which is currently being
executed and write access was requested.
ETXTBSY
pathname refers to a file that is currently in use as a swap
file, and the O_TRUNC flag was specified.
ETXTBSY
pathname refers to a file that is currently being read by the
kernel (e.g. for module/firmware loading), and write access was
requested.
EWOULDBLOCK
The O_NONBLOCK flag was specified, and an incompatible lease was
held on the file (see fcntl(2)).
The following additional errors can occur for openat():
EBADF dirfd is not a valid file descriptor.
ENOTDIR
pathname is a relative pathname and dirfd is a file descriptor
referring to a file other than a directory.
下面是errno.h的存储位置以及内容。
pi@raspberrypi:/usr/include $ cat errno.h
/* Copyright (C) 1991-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/>. */
/*
* ISO C99 Standard: 7.5 Errors <errno.h>
*/
#ifndef _ERRNO_H
#define _ERRNO_H 1
#include <features.h>
/* The system-specific definitions of the E* constants, as macros. */
#include <bits/errno.h>
/* When included from assembly language, this header only provides the
E* constants. */
#ifndef __ASSEMBLER__
__BEGIN_DECLS
/* The error code set by various library functions. */
extern int *__errno_location (void) __THROW __attribute_const__;
# define errno (*__errno_location ())
# ifdef __USE_GNU
/* The full and simple forms of the name with which the program was
invoked. These variables are set up automatically at startup based on
the value of argv[0]. */
extern char *program_invocation_name;
extern char *program_invocation_short_name;
#include <bits/types/error_t.h>
# endif /* __USE_GNU */
__END_DECLS
#endif /* !__ASSEMBLER__ */
#endif /* errno.h */
利用return -1我们可以初步得到错误的判断。但如果想要在错误的情况下获得更细节的原因或者相对应更细节的处理,则可以使用errno global variable来做到这一点。
fd = open(pathname,flags,mode); /* system call to open a file */
if(fd = -1)
{
/* Code to handle the error */
if (errno == EEXIST)
{
/* corresponding error handling */
fprintf(stderr, "already exist \n"); /* can either add some error message */
}
...
}但是,有的system call在成功调用的情况下也会返回 -1,比如getpriority(),它的return描述如下:
RETURN VALUE
On success, getpriority() returns the calling thread's nice value, which may be a negative number. On error, it returns-1 and sets errnoto indicate the cause of the error. Since a successfulcall to getpriority() can legitimately return the value -1, it is necessary to clear the external variable errno prior to the call, then check it afterward to determine if -1 is an error or a legitimate value.setpriority() returns 0 on success. On error, it returns -1 and sets errno to indicate the cause of the error.
除过上述打印errno错误信息可以使用fprintf,也可以用 perror(char* msg)来打印问题在哪里,这里的好处是不需要像fprintf一样显性打印具体错误,这个会由perror根据现在errno中的值来自动输出。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main()
{
errno = EEXIST;
perror("hallo");
exit(EXIT_FAILURE);
printf("Not exit!!");
}
运行结果
pi@raspberrypi:~/sysprog $ ./test
hallo: File exists
另外我们也发现有很多类型的错误,如果我们只想打印出来是什么错误的话,除了使用perror以外就还有一种方法,可以只提取出来错误是什么(也就是上面结果的File exists,而不用统一格式msg: 错误)并且按照自己的方法做自己想要做的事情。也就是使用*char strerror(int errornum)
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
errno = 5;
printf("error is %s\n", strerror(errno));
}
运行结果
pi@raspberrypi:~/sysprog $ ./test
error is Input/output error
在CSDN中找到了一篇不错的文章,将所有errno error类型都打印出来,平时可以做为一个对照表参阅。
当然所有的error define都可以在/usr/include/asm-generic 中找到。其中基础常用error定义在errno_base.h:
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _ASM_GENERIC_ERRNO_BASE_H
#define _ASM_GENERIC_ERRNO_BASE_H
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
#endif
其余的数字定义在errno.h(并非之前的/usr/include/errno.h,这个errno.h以带目录的方式引用了下面的这个errno.h)。如headerfile中自己所述,都是一些额外特殊的错误类型:
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _ASM_GENERIC_ERRNO_H
#define _ASM_GENERIC_ERRNO_H
#include <asm-generic/errno-base.h>
#define EDEADLK 35 /* Resource deadlock would occur */
#define ENAMETOOLONG 36 /* File name too long */
#define ENOLCK 37 /* No record locks available */
/*
* This error code is special: arch syscall entry code will return
* -ENOSYS if users try to call a syscall that doesn't exist. To keep
* failures of syscalls that really do exist distinguishable from
* failures due to attempts to use a nonexistent syscall, syscall
* implementations should refrain from returning -ENOSYS.
*/
#define ENOSYS 38 /* Invalid system call number */
#define ENOTEMPTY 39 /* Directory not empty */
#define ELOOP 40 /* Too many symbolic links encountered */
#define EWOULDBLOCK EAGAIN /* Operation would block */
#define ENOMSG 42 /* No message of desired type */
#define EIDRM 43 /* Identifier removed */
#define ECHRNG 44 /* Channel number out of range */
#define EL2NSYNC 45 /* Level 2 not synchronized */
#define EL3HLT 46 /* Level 3 halted */
#define EL3RST 47 /* Level 3 reset */
#define ELNRNG 48 /* Link number out of range */
#define EUNATCH 49 /* Protocol driver not attached */
#define ENOCSI 50 /* No CSI structure available */
#define EL2HLT 51 /* Level 2 halted */
#define EBADE 52 /* Invalid exchange */
#define EBADR 53 /* Invalid request descriptor */
#define EXFULL 54 /* Exchange full */
#define ENOANO 55 /* No anode */
#define EBADRQC 56 /* Invalid request code */
#define EBADSLT 57 /* Invalid slot */
#define EDEADLOCK EDEADLK
#define EBFONT 59 /* Bad font file format */
#define ENOSTR 60 /* Device not a stream */
#define ENODATA 61 /* No data available */
#define ETIME 62 /* Timer expired */
#define ENOSR 63 /* Out of streams resources */
#define ENONET 64 /* Machine is not on the network */
#define ENOPKG 65 /* Package not installed */
#define EREMOTE 66 /* Object is remote */
#define ENOLINK 67 /* Link has been severed */
#define EADV 68 /* Advertise error */
#define ESRMNT 69 /* Srmount error */
#define ECOMM 70 /* Communication error on send */
#define EPROTO 71 /* Protocol error */
#define EMULTIHOP 72 /* Multihop attempted */
#define EDOTDOT 73 /* RFS specific error */
#define EBADMSG 74 /* Not a data message */
#define EOVERFLOW 75 /* Value too large for defined data type */
#define ENOTUNIQ 76 /* Name not unique on network */
#define EBADFD 77 /* File descriptor in bad state */
#define EREMCHG 78 /* Remote address changed */
#define ELIBACC 79 /* Can not access a needed shared library */
#define ELIBBAD 80 /* Accessing a corrupted shared library */
#define ELIBSCN 81 /* .lib section in a.out corrupted */
#define ELIBMAX 82 /* Attempting to link in too many shared libraries */
#define ELIBEXEC 83 /* Cannot exec a shared library directly */
#define EILSEQ 84 /* Illegal byte sequence */
#define ERESTART 85 /* Interrupted system call should be restarted */
#define ESTRPIPE 86 /* Streams pipe error */
#define EUSERS 87 /* Too many users */
#define ENOTSOCK 88 /* Socket operation on non-socket */
#define EDESTADDRREQ 89 /* Destination address required */
#define EMSGSIZE 90 /* Message too long */
#define EPROTOTYPE 91 /* Protocol wrong type for socket */
#define ENOPROTOOPT 92 /* Protocol not available */
#define EPROTONOSUPPORT 93 /* Protocol not supported */
#define ESOCKTNOSUPPORT 94 /* Socket type not supported */
#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */
#define EPFNOSUPPORT 96 /* Protocol family not supported */
#define EAFNOSUPPORT 97 /* Address family not supported by protocol */
#define EADDRINUSE 98 /* Address already in use */
#define EADDRNOTAVAIL 99 /* Cannot assign requested address */
#define ENETDOWN 100 /* Network is down */
#define ENETUNREACH 101 /* Network is unreachable */
#define ENETRESET 102 /* Network dropped connection because of reset */
#define ECONNABORTED 103 /* Software caused connection abort */
#define ECONNRESET 104 /* Connection reset by peer */
#define ENOBUFS 105 /* No buffer space available */
#define EISCONN 106 /* Transport endpoint is already connected */
#define ENOTCONN 107 /* Transport endpoint is not connected */
#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */
#define ETOOMANYREFS 109 /* Too many references: cannot splice */
#define ETIMEDOUT 110 /* Connection timed out */
#define ECONNREFUSED 111 /* Connection refused */
#define EHOSTDOWN 112 /* Host is down */
#define EHOSTUNREACH 113 /* No route to host */
#define EALREADY 114 /* Operation already in progress */
#define EINPROGRESS 115 /* Operation now in progress */
#define ESTALE 116 /* Stale file handle */
#define EUCLEAN 117 /* Structure needs cleaning */
#define ENOTNAM 118 /* Not a XENIX named type file */
#define ENAVAIL 119 /* No XENIX semaphores available */
#define EISNAM 120 /* Is a named type file */
#define EREMOTEIO 121 /* Remote I/O error */
#define EDQUOT 122 /* Quota exceeded */
#define ENOMEDIUM 123 /* No medium found */
#define EMEDIUMTYPE 124 /* Wrong medium type */
#define ECANCELED 125 /* Operation Canceled */
#define ENOKEY 126 /* Required key not available */
#define EKEYEXPIRED 127 /* Key has expired */
#define EKEYREVOKED 128 /* Key has been revoked */
#define EKEYREJECTED 129 /* Key was rejected by service */
/* for robust mutexes */
#define EOWNERDEAD 130 /* Owner died */
#define ENOTRECOVERABLE 131 /* State not recoverable */
#define ERFKILL 132 /* Operation not possible due to RF-kill */
#define EHWPOISON 133 /* Memory page has hardware error */
#endif
处理库函数错误
库函数错误被分为三类
- 一些库函数表现近似于system call,会以-1作为返回值表示错误,并且辅以errno知悉错误具体是什么。比如remove() (使用的是unlink(),rmdir())。
- 一些库函数返回非-1的值表示错误,但是依然会设置errno表示具体错误。比如fopen在错误情况下返回NULL指针,并且会设具体errno的值表示具体错误。
- 另一类库函数完全不会设置errno。对于这类函数需要查看它的手册以了解它的用法。
3.5 使用这本书的一些文件
该书列出了一些后面所有程序都会用到的一些common function和header files,这些文件可以在这里找到并下载下来
List of source code files, by chapter, from "The Linux Programming Interface"
至于对于每个文件的测试和描述,等到后面使用到的时候再在这里补充完全。
3.6 移植问题
3.6.1 Feature Test Macros
在glibc当中,我们不仅有ISO C的定义,有的时候为了满足对BSD或者System V的可以执行,那么我们需要有能力仅使用这些系统所需要的定义(比如constants,相对应的function prototype等等)。 这个时候我们有两种选择可以做,第一种是在使用所有header file之前定义一个feature test macros,比如#define _BSD_SOURCE 1,这样子相应的代码段将会在编译中被使用。
还有一种方式即使用cc -D在compiler中,这样子可以针对所有文件定义需要的#define,比如cc -D_POXIS_SOURCE test.c
则在test.c 中应当使用
#ifdef _POXIS_SOURCE
... /* handling only in case _BSD_SOURCE is defined*/
#elif ... /* other possibilities explicit*/
#else ... /* other possibilities implicit*/
#endif比如还是在我们之前使用的test.c当中我这样子使用了_POXIS_SOURCE。
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
errno = 0;
printf("error is %s\n", strerror(errno));
#ifdef _POSIX_SOURCE_FAKE
printf("I am using codes in _POSIX_SOURCE_FAKE!\n");
#else
printf("I am NOT using codes in _POSIX_SOURCE_FAKE!!!\n");
#endif
}
在执行过程当中可以看到,cc -D_POSIX_SOURCE_FAKE等同于在 source code 里开头定了一个#define _POSIX_SOURCE_FAKE,自然当compile到#ifdef _POSIX_SOURCE_FAKE的时候compiler会自动认为这个已经定义。这么做的最大的好处就是,直接在source code里面定义到后期很难找到定义的位置,而使用makefile作为编译系统的话,就会方便管理很多,尤其是对于大型工程而言。
pi@raspberrypi:~/sysprog $ cc -o test test.c
pi@raspberrypi:~/sysprog $ ./test
error is Success
I am NOT using codes in _POSIX_SOURCE_FAKE!!!
pi@raspberrypi:~/sysprog $ cc -D_POSIX_SOURCE_FAKE -o test test.c
pi@raspberrypi:~/sysprog $ ./test
error is Success
I am using codes in _POSIX_SOURCE_FAKE!
更具体关于什么情况下使用什么macro,请参阅Feature Test Macros (The GNU C Library)
事实上,/usr/include/features已经通过#define _BSD_SOURCE 类似的macro定义了很多default使用cc时已有的macro。事实上
cc -D_POSIX_SOURCE -D_POSIX_C_SOURCE=199506 -D_DEFAULT_SOURCE test.c
/*_DEFAULT_SOURCE is collection of _BSD_SOURCE and _SVID_SOURCE*/
等同于
cc test.c3.6.2 System Data Types
如果直接使用int或者long来存储一些系统信息比如 processID,userID等等会碰到一些问题比如
- 不同的UNIX实现会使用int long对应着不同的bytes
- 即使同一种UNIX,不同的版本的迭代也有可能改变int long所对应的bytes
所以在SUSv3的实现下,为了取得types的兼容性,则在type类型中利用typedef抽象化了类型。
比如process ID在linux x86-32体系下定义为:
typedef int pid_t无论 pid_t真正对用的是那种类型,pid_t才是我们在程序当中应该使用的类型。否则如果使用int的话,那么在移植软件到另一个系统里之后,我们要花费多少时间来重新改写这些类型呢?
大多数的标准系统数据类型都以_t结尾,并且大部分这些system types都定义在 <sys/types.h>。在树莓派上,也就是在这里可以找到它所有的内容:
pi@raspberrypi:/usr/include/arm-linux-gnueabihf/sys $ cat types.h
/* Copyright (C) 1991-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/>. */
/*
* POSIX Standard: 2.6 Primitive System Data Types <sys/types.h>
*/
#ifndef _SYS_TYPES_H
#define _SYS_TYPES_H 1
#include <features.h>
__BEGIN_DECLS
#include <bits/types.h>
#ifdef __USE_MISC
# ifndef __u_char_defined
typedef __u_char u_char;
typedef __u_short u_short;
typedef __u_int u_int;
typedef __u_long u_long;
typedef __quad_t quad_t;
typedef __u_quad_t u_quad_t;
typedef __fsid_t fsid_t;
# define __u_char_defined
# endif
typedef __loff_t loff_t;
#endif
#ifndef __ino_t_defined
# ifndef __USE_FILE_OFFSET64
typedef __ino_t ino_t;
# else
typedef __ino64_t ino_t;
# endif
# define __ino_t_defined
#endif
#if defined __USE_LARGEFILE64 && !defined __ino64_t_defined
typedef __ino64_t ino64_t;
# define __ino64_t_defined
#endif
#ifndef __dev_t_defined
typedef __dev_t dev_t;
# define __dev_t_defined
#endif
#ifndef __gid_t_defined
typedef __gid_t gid_t;
# define __gid_t_defined
#endif
#ifndef __mode_t_defined
typedef __mode_t mode_t;
# define __mode_t_defined
#endif
#ifndef __nlink_t_defined
typedef __nlink_t nlink_t;
# define __nlink_t_defined
#endif
#ifndef __uid_t_defined
typedef __uid_t uid_t;
# define __uid_t_defined
#endif
#ifndef __off_t_defined
# ifndef __USE_FILE_OFFSET64
typedef __off_t off_t;
# else
typedef __off64_t off_t;
# endif
# define __off_t_defined
#endif
#if defined __USE_LARGEFILE64 && !defined __off64_t_defined
typedef __off64_t off64_t;
# define __off64_t_defined
#endif
#ifndef __pid_t_defined
typedef __pid_t pid_t;
# define __pid_t_defined
#endif
#if (defined __USE_XOPEN || defined __USE_XOPEN2K8) \
&& !defined __id_t_defined
typedef __id_t id_t;
# define __id_t_defined
#endif
#ifndef __ssize_t_defined
typedef __ssize_t ssize_t;
# define __ssize_t_defined
#endif
#ifdef __USE_MISC
# ifndef __daddr_t_defined
typedef __daddr_t daddr_t;
typedef __caddr_t caddr_t;
# define __daddr_t_defined
# endif
#endif
#if (defined __USE_MISC || defined __USE_XOPEN) && !defined __key_t_defined
typedef __key_t key_t;
# define __key_t_defined
#endif
#if defined __USE_XOPEN || defined __USE_XOPEN2K8
# include <bits/types/clock_t.h>
#endif
#include <bits/types/clockid_t.h>
#include <bits/types/time_t.h>
#include <bits/types/timer_t.h>
#ifdef __USE_XOPEN
# ifndef __useconds_t_defined
typedef __useconds_t useconds_t;
# define __useconds_t_defined
# endif
# ifndef __suseconds_t_defined
typedef __suseconds_t suseconds_t;
# define __suseconds_t_defined
# endif
#endif
#define __need_size_t
#include <stddef.h>
#ifdef __USE_MISC
/* Old compatibility names for C types. */
typedef unsigned long int ulong;
typedef unsigned short int ushort;
typedef unsigned int uint;
#endif
/* These size-specific names are used by some of the inet code. */
#include <bits/stdint-intn.h>
#if !__GNUC_PREREQ (2, 7)
/* These were defined by ISO C without the first `_'. */
typedef unsigned char u_int8_t;
typedef unsigned short int u_int16_t;
typedef unsigned int u_int32_t;
# if __WORDSIZE == 64
typedef unsigned long int u_int64_t;
# else
__extension__ typedef unsigned long long int u_int64_t;
# endif
typedef int register_t;
#else
/* For GCC 2.7 and later, we can use specific type-size attributes. */
# define __u_intN_t(N, MODE) \
typedef unsigned int u_int##N##_t __attribute__ ((__mode__ (MODE)))
__u_intN_t (8, __QI__);
__u_intN_t (16, __HI__);
__u_intN_t (32, __SI__);
__u_intN_t (64, __DI__);
typedef int register_t __attribute__ ((__mode__ (__word__)));
/* Some code from BIND tests this macro to see if the types above are
defined. */
#endif
#define __BIT_TYPES_DEFINED__ 1
#ifdef __USE_MISC
/* In BSD <sys/types.h> is expected to define BYTE_ORDER. */
# include <endian.h>
/* It also defines `fd_set' and the FD_* macros for `select'. */
# include <sys/select.h>
#endif /* Use misc. */
#if (defined __USE_UNIX98 || defined __USE_XOPEN2K8) \
&& !defined __blksize_t_defined
typedef __blksize_t blksize_t;
# define __blksize_t_defined
#endif
/* Types from the Large File Support interface. */
#ifndef __USE_FILE_OFFSET64
# ifndef __blkcnt_t_defined
typedef __blkcnt_t blkcnt_t; /* Type to count number of disk blocks. */
# define __blkcnt_t_defined
# endif
# ifndef __fsblkcnt_t_defined
typedef __fsblkcnt_t fsblkcnt_t; /* Type to count file system blocks. */
# define __fsblkcnt_t_defined
# endif
# ifndef __fsfilcnt_t_defined
typedef __fsfilcnt_t fsfilcnt_t; /* Type to count file system inodes. */
# define __fsfilcnt_t_defined
# endif
#else
# ifndef __blkcnt_t_defined
typedef __blkcnt64_t blkcnt_t; /* Type to count number of disk blocks. */
# define __blkcnt_t_defined
# endif
# ifndef __fsblkcnt_t_defined
typedef __fsblkcnt64_t fsblkcnt_t; /* Type to count file system blocks. */
# define __fsblkcnt_t_defined
# endif
# ifndef __fsfilcnt_t_defined
typedef __fsfilcnt64_t fsfilcnt_t; /* Type to count file system inodes. */
# define __fsfilcnt_t_defined
# endif
#endif
#ifdef __USE_LARGEFILE64
typedef __blkcnt64_t blkcnt64_t; /* Type to count number of disk blocks. */
typedef __fsblkcnt64_t fsblkcnt64_t; /* Type to count file system blocks. */
typedef __fsfilcnt64_t fsfilcnt64_t; /* Type to count file system inodes. */
#endif
/* Now add the thread types. */
#if defined __USE_POSIX199506 || defined __USE_UNIX98
# include <bits/pthreadtypes.h>
#endif
__END_DECLS
#endif /* sys/types.h */
不同于书上所述在sys/types.h 里pid_t会直接对应一个int 或 long类型,而是它还有进一步的typedef-->bits/types.h-->bits/typesizes.h
bits/types.h
pi@raspberrypi:/usr/include/arm-linux-gnueabihf/bits $ cat types.h
/* bits/types.h -- definitions of __*_t types underlying *_t types.
Copyright (C) 2002-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/>. */
/*
* Never include this file directly; use <sys/types.h> instead.
*/
#ifndef _BITS_TYPES_H
#define _BITS_TYPES_H 1
#include <features.h>
#include <bits/wordsize.h>
/* Convenience types. */
typedef unsigned char __u_char;
typedef unsigned short int __u_short;
typedef unsigned int __u_int;
typedef unsigned long int __u_long;
/* Fixed-size types, underlying types depend on word size and compiler. */
typedef signed char __int8_t;
typedef unsigned char __uint8_t;
typedef signed short int __int16_t;
typedef unsigned short int __uint16_t;
typedef signed int __int32_t;
typedef unsigned int __uint32_t;
#if __WORDSIZE == 64
typedef signed long int __int64_t;
typedef unsigned long int __uint64_t;
#else
__extension__ typedef signed long long int __int64_t;
__extension__ typedef unsigned long long int __uint64_t;
#endif
/* Smallest types with at least a given width. */
typedef __int8_t __int_least8_t;
typedef __uint8_t __uint_least8_t;
typedef __int16_t __int_least16_t;
typedef __uint16_t __uint_least16_t;
typedef __int32_t __int_least32_t;
typedef __uint32_t __uint_least32_t;
typedef __int64_t __int_least64_t;
typedef __uint64_t __uint_least64_t;
/* quad_t is also 64 bits. */
#if __WORDSIZE == 64
typedef long int __quad_t;
typedef unsigned long int __u_quad_t;
#else
__extension__ typedef long long int __quad_t;
__extension__ typedef unsigned long long int __u_quad_t;
#endif
/* Largest integral types. */
#if __WORDSIZE == 64
typedef long int __intmax_t;
typedef unsigned long int __uintmax_t;
#else
__extension__ typedef long long int __intmax_t;
__extension__ typedef unsigned long long int __uintmax_t;
#endif
/* The machine-dependent file <bits/typesizes.h> defines __*_T_TYPE
macros for each of the OS types we define below. The definitions
of those macros must use the following macros for underlying types.
We define __S<SIZE>_TYPE and __U<SIZE>_TYPE for the signed and unsigned
variants of each of the following integer types on this machine.
16 -- "natural" 16-bit type (always short)
32 -- "natural" 32-bit type (always int)
64 -- "natural" 64-bit type (long or long long)
LONG32 -- 32-bit type, traditionally long
QUAD -- 64-bit type, always long long
WORD -- natural type of __WORDSIZE bits (int or long)
LONGWORD -- type of __WORDSIZE bits, traditionally long
We distinguish WORD/LONGWORD, 32/LONG32, and 64/QUAD so that the
conventional uses of `long' or `long long' type modifiers match the
types we define, even when a less-adorned type would be the same size.
This matters for (somewhat) portably writing printf/scanf formats for
these types, where using the appropriate l or ll format modifiers can
make the typedefs and the formats match up across all GNU platforms. If
we used `long' when it's 64 bits where `long long' is expected, then the
compiler would warn about the formats not matching the argument types,
and the programmer changing them to shut up the compiler would break the
program's portability.
Here we assume what is presently the case in all the GCC configurations
we support: long long is always 64 bits, long is always word/address size,
and int is always 32 bits. */
#define __S16_TYPE short int
#define __U16_TYPE unsigned short int
#define __S32_TYPE int
#define __U32_TYPE unsigned int
#define __SLONGWORD_TYPE long int
#define __ULONGWORD_TYPE unsigned long int
#if __WORDSIZE == 32
# define __SQUAD_TYPE __quad_t
# define __UQUAD_TYPE __u_quad_t
# define __SWORD_TYPE int
# define __UWORD_TYPE unsigned int
# define __SLONG32_TYPE long int
# define __ULONG32_TYPE unsigned long int
# define __S64_TYPE __quad_t
# define __U64_TYPE __u_quad_t
/* We want __extension__ before typedef's that use nonstandard base types
such as `long long' in C89 mode. */
# define __STD_TYPE __extension__ typedef
#elif __WORDSIZE == 64
# define __SQUAD_TYPE long int
# define __UQUAD_TYPE unsigned long int
# define __SWORD_TYPE long int
# define __UWORD_TYPE unsigned long int
# define __SLONG32_TYPE int
# define __ULONG32_TYPE unsigned int
# define __S64_TYPE long int
# define __U64_TYPE unsigned long int
/* No need to mark the typedef with __extension__. */
# define __STD_TYPE typedef
#else
# error
#endif
#include <bits/typesizes.h> /* Defines __*_T_TYPE macros. */
__STD_TYPE __DEV_T_TYPE __dev_t; /* Type of device numbers. */
__STD_TYPE __UID_T_TYPE __uid_t; /* Type of user identifications. */
__STD_TYPE __GID_T_TYPE __gid_t; /* Type of group identifications. */
__STD_TYPE __INO_T_TYPE __ino_t; /* Type of file serial numbers. */
__STD_TYPE __INO64_T_TYPE __ino64_t; /* Type of file serial numbers (LFS).*/
__STD_TYPE __MODE_T_TYPE __mode_t; /* Type of file attribute bitmasks. */
__STD_TYPE __NLINK_T_TYPE __nlink_t; /* Type of file link counts. */
__STD_TYPE __OFF_T_TYPE __off_t; /* Type of file sizes and offsets. */
__STD_TYPE __OFF64_T_TYPE __off64_t; /* Type of file sizes and offsets (LFS). */
__STD_TYPE __PID_T_TYPE __pid_t; /* Type of process identifications. */
__STD_TYPE __FSID_T_TYPE __fsid_t; /* Type of file system IDs. */
__STD_TYPE __CLOCK_T_TYPE __clock_t; /* Type of CPU usage counts. */
__STD_TYPE __RLIM_T_TYPE __rlim_t; /* Type for resource measurement. */
__STD_TYPE __RLIM64_T_TYPE __rlim64_t; /* Type for resource measurement (LFS). */
__STD_TYPE __ID_T_TYPE __id_t; /* General type for IDs. */
__STD_TYPE __TIME_T_TYPE __time_t; /* Seconds since the Epoch. */
__STD_TYPE __USECONDS_T_TYPE __useconds_t; /* Count of microseconds. */
__STD_TYPE __SUSECONDS_T_TYPE __suseconds_t; /* Signed count of microseconds. */
__STD_TYPE __DADDR_T_TYPE __daddr_t; /* The type of a disk address. */
__STD_TYPE __KEY_T_TYPE __key_t; /* Type of an IPC key. */
/* Clock ID used in clock and timer functions. */
__STD_TYPE __CLOCKID_T_TYPE __clockid_t;
/* Timer ID returned by `timer_create'. */
__STD_TYPE __TIMER_T_TYPE __timer_t;
/* Type to represent block size. */
__STD_TYPE __BLKSIZE_T_TYPE __blksize_t;
/* Types from the Large File Support interface. */
/* Type to count number of disk blocks. */
__STD_TYPE __BLKCNT_T_TYPE __blkcnt_t;
__STD_TYPE __BLKCNT64_T_TYPE __blkcnt64_t;
/* Type to count file system blocks. */
__STD_TYPE __FSBLKCNT_T_TYPE __fsblkcnt_t;
__STD_TYPE __FSBLKCNT64_T_TYPE __fsblkcnt64_t;
/* Type to count file system nodes. */
__STD_TYPE __FSFILCNT_T_TYPE __fsfilcnt_t;
__STD_TYPE __FSFILCNT64_T_TYPE __fsfilcnt64_t;
/* Type of miscellaneous file system fields. */
__STD_TYPE __FSWORD_T_TYPE __fsword_t;
__STD_TYPE __SSIZE_T_TYPE __ssize_t; /* Type of a byte count, or error. */
/* Signed long type used in system calls. */
__STD_TYPE __SYSCALL_SLONG_TYPE __syscall_slong_t;
/* Unsigned long type used in system calls. */
__STD_TYPE __SYSCALL_ULONG_TYPE __syscall_ulong_t;
/* These few don't really vary by system, they always correspond
to one of the other defined types. */
typedef __off64_t __loff_t; /* Type of file sizes and offsets (LFS). */
typedef char *__caddr_t;
/* Duplicates info from stdint.h but this is used in unistd.h. */
__STD_TYPE __SWORD_TYPE __intptr_t;
/* Duplicate info from sys/socket.h. */
__STD_TYPE __U32_TYPE __socklen_t;
/* C99: An integer type that can be accessed as an atomic entity,
even in the presence of asynchronous interrupts.
It is not currently necessary for this to be machine-specific. */
typedef int __sig_atomic_t;
#undef __STD_TYPE
#endif /* bits/types.h */
bits/typesizes.h
pi@raspberrypi:/usr/include/arm-linux-gnueabihf/bits $ cat typesizes.h
/* bits/typesizes.h -- underlying types for *_t. Generic version.
Copyright (C) 2002-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 _BITS_TYPES_H
# error "Never include <bits/typesizes.h> directly; use <sys/types.h> instead."
#endif
#ifndef _BITS_TYPESIZES_H
#define _BITS_TYPESIZES_H 1
/* See <bits/types.h> for the meaning of these macros. This file exists so
that <bits/types.h> need not vary across different GNU platforms. */
#define __DEV_T_TYPE __UQUAD_TYPE
#define __UID_T_TYPE __U32_TYPE
#define __GID_T_TYPE __U32_TYPE
#define __INO_T_TYPE __ULONGWORD_TYPE
#define __INO64_T_TYPE __UQUAD_TYPE
#define __MODE_T_TYPE __U32_TYPE
#define __NLINK_T_TYPE __UWORD_TYPE
#define __OFF_T_TYPE __SLONGWORD_TYPE
#define __OFF64_T_TYPE __SQUAD_TYPE
#define __PID_T_TYPE __S32_TYPE
#define __RLIM_T_TYPE __ULONGWORD_TYPE
#define __RLIM64_T_TYPE __UQUAD_TYPE
#define __BLKCNT_T_TYPE __SLONGWORD_TYPE
#define __BLKCNT64_T_TYPE __SQUAD_TYPE
#define __FSBLKCNT_T_TYPE __ULONGWORD_TYPE
#define __FSBLKCNT64_T_TYPE __UQUAD_TYPE
#define __FSFILCNT_T_TYPE __ULONGWORD_TYPE
#define __FSFILCNT64_T_TYPE __UQUAD_TYPE
#define __FSWORD_T_TYPE __SWORD_TYPE
#define __ID_T_TYPE __U32_TYPE
#define __CLOCK_T_TYPE __SLONGWORD_TYPE
#define __TIME_T_TYPE __SLONGWORD_TYPE
#define __USECONDS_T_TYPE __U32_TYPE
#define __SUSECONDS_T_TYPE __SLONGWORD_TYPE
#define __DADDR_T_TYPE __S32_TYPE
#define __KEY_T_TYPE __S32_TYPE
#define __CLOCKID_T_TYPE __S32_TYPE
#define __TIMER_T_TYPE void *
#define __BLKSIZE_T_TYPE __SLONGWORD_TYPE
#define __FSID_T_TYPE struct { int __val[2]; }
#define __SSIZE_T_TYPE __SWORD_TYPE
#define __SYSCALL_SLONG_TYPE __SLONGWORD_TYPE
#define __SYSCALL_ULONG_TYPE __ULONGWORD_TYPE
#define __CPU_MASK_TYPE __ULONGWORD_TYPE
#ifdef __LP64__
/* Tell the libc code that off_t and off64_t are actually the same type
for all ABI purposes, even if possibly expressed as different base types
for C type-checking purposes. */
# define __OFF_T_MATCHES_OFF64_T 1
/* Same for ino_t and ino64_t. */
# define __INO_T_MATCHES_INO64_T 1
/* And for rlim_t and rlim64_t. */
# define __RLIM_T_MATCHES_RLIM64_T 1
#else
# define __RLIM_T_MATCHES_RLIM64_T 0
#endif
/* Number of descriptors that can fit in an `fd_set'. */
#define __FD_SETSIZE 1024
#endif /* bits/typesizes.h */
再回到/bits/types.h 可以找到定义
bits/types.h:#define __U32_TYPE unsigned int
这只是一个pid_t为例,其余的system types均可以通过这样的方式找到他们的最底层定义。
以下是书中列出之后会用到的system types合集:


对于printf的使用来说,最好将interger类的都cast到%ld也就是long类型上。当然除了off_t类型使用long long的需要格外注意以外。毕竟printf不是一个能够在runtime中自动识别类型的function。
另外就是structure类型的一些system types,很多时候他们的不兼容来源于顺序的不一样,这个时候建议初始化的时候使用explicit方式:
struct sembuf s;
s.sem_num = 3;
s.sem_op = -1;
s.sem_flg = SEM_UNDO;
或者
struct sembuf s = {.sem_num = 3, .sem_op = -1, .sem_flg = SEM_UNDO};
最好不要使用
struct sembuf s = {3, -1, SEM_UNDO}; 这样子对于未来想要做移植的程序很危险如果不是SUSv3明确定义的macro,在使用的过程中也要注意,因为有可能别的UNIX系统并没有实现这个macro。
3.8 练习
3-1. When using the Linux-specific reboot() system call to reboot the system, the second argument, magic2, must be specified as one of a set of magic numbers (e.g., LINUX_REBOOT_MAGIC2). What is the significance of these numbers? (Converting them to hexadecimal provides a clue.)
答案参考这里Linux系统调用reboot中魔术参数背后鲜为人知的趣味_smstong的博客-CSDN博客
这个算一个彩蛋。