[单刷APUE系列]第一章——Unix基础知识[2]

原文来自静雅斋,转载请注明出处。javascript

一些想说的话

很是感谢一些朋友看这个文章,关于更新的问题,笔者会尽快整理撰写,由于以前的笔记都残缺不全了,因此是从新开始看原著,而后一边看一边写的,因此可能会稍微有点慢。文章里面的代码可能和原著有一些差异,可是都是我的认为应当修改的,若是有问题,敬请指正。java

错误处理

在实际开发过程当中,很多朋友可能有一个习惯,当函数出错的时候,返回一个负值或者是一个null指针,Unix系统函数也是差很少,不过它会多作一步——将整形变量errno设置为表明特定信息的值,例如open系统函数,成功返回一个非负的文件描述符,出错则返回-1,而且会将errno设置为特定的错误信息,这样开发者就能根据错误信息断定输出错误信息。
咱们能够看一看open函数的系统手册node

NAME
     open, openat -- open or create a file for reading or writing

SYNOPSIS
     #include <fcntl.h>

     int
     open(const char *path, int oflag, ...);

     int
     openat(int fd, const char *path, int oflag, ...);
......
RETURN VALUES
     If successful, open() returns a non-negative integer, termed a file descriptor.  It returns -1 on failure, and sets errno to indicate the
     error.
ERRORS
     The named file is opened unless:

     [EACCES]           Search permission is denied for a component of the path prefix.

     [EACCES]           The required permissions (for reading and/or writing) are denied for the given flags.

     [EACCES]           O_CREAT is specified, the file does not exist, and the directory in which it is to be created does not permit writing.

     [EACCES]           O_TRUNC is specified and write permission is denied.

     [EAGAIN]           path specifies the slave side of a locked pseudo-terminal device.

     [EDQUOT]           O_CREAT is specified, the file does not exist, and the directory in which the entry for the new file is being placed
                        cannot be extended because the user's quota of disk blocks on the file system containing the directory has been
                        exhausted.

     [EDQUOT]           O_CREAT is specified, the file does not exist, and the user's quota of inodes on the file system on which the file is
                        being created has been exhausted.

     [EEXIST]           O_CREAT and O_EXCL are specified and the file exists.

     [EFAULT]           Path points outside the process's allocated address space.

     [EINTR]            The open() operation is interrupted by a signal.

     [EINVAL]           The value of oflag is not valid.

     [EIO]              An I/O error occurs while making the directory entry or allocating the inode for O_CREAT.

     [EISDIR]           The named file is a directory, and the arguments specify that it is to be opened for writing.

     [ELOOP]            Too many symbolic links are encountered in translating the pathname.  This is taken to be indicative of a looping sym-
                        bolic link.

     [EMFILE]           The process has already reached its limit for open file descriptors.

     [ENAMETOOLONG]     A component of a pathname exceeds {NAME_MAX} characters, or an entire path name exceeded {PATH_MAX} characters.

     [ENFILE]           The system file table is full.

     [ELOOP]            O_NOFOLLOW was specified and the target is a symbolic link.

     [ENOENT]           O_CREAT is not set and the named file does not exist.

     [ENOENT]           A component of the path name that must exist does not exist.

     [ENOSPC]           O_CREAT is specified, the file does not exist, and the directory in which the entry for the new file is being placed
                        cannot be extended because there is no space left on the file system containing the directory.

     [ENOSPC]           O_CREAT is specified, the file does not exist, and there are no free inodes on the file system on which the file is
                        being created.

     [ENOTDIR]          A component of the path prefix is not a directory.

     [ENXIO]            The named file is a character-special or block-special file and the device associated with this special file does not
                        exist.

     [ENXIO]            O_NONBLOCK and O_WRONLY are set, the file is a FIFO, and no process has it open for reading.

     [EOPNOTSUPP]       O_SHLOCK or O_EXLOCK is specified, but the underlying filesystem does not support locking.

     [EOPNOTSUPP]       An attempt is made to open a socket (not currently implemented).

     [EOVERFLOW]        The named file is a regular file and its size does not fit in an object of type off_t.

     [EROFS]            The named file resides on a read-only file system, and the file is to be modified.

     [ETXTBSY]          The file is a pure procedure (shared text) file that is being executed and the open() call requests write access.

     [EBADF]            The path argument does not specify an absolute path and the fd argument is neither AT_FDCWD nor a valid file descrip-
                        tor open for searching.

     [ENOTDIR]          The path argument is not an absolute path and fd is neither AT_FDCWD nor a file descriptor associated with a direc-
                        tory.复制代码

咱们能够看到大概有32个错误码用于open函数,在文件 中定义了errno和可能赋予的各类常量,在Unix系统中,咱们可使用 man 2 intro来查看全部的出错常量,在Linux系统中,则使用 man 3 errno来查看。
在之前,POSIX和ISO C标准将errno定义为一个外部变量,即 extern int errno;,可是这套定义并不适用于引入了多线程机制的现代化系统,在多线程中,每一个线程虽然共享了进程的地址空间,可是每一个线程都维护了自身内部的errno变量,因此后来的定义就彻底不是如此了,例如原书上举出Linux将其定义为
ios

extern int *__errno_location(void);
#define errno (*__errno_location())复制代码

在BSD系统中定义是长这样的shell

extern int * __error(void);
#define errno (*__error())复制代码

好像两个也没啥区别,对于errno只有两条规则。编程

  1. 若是没有出错,其值不会被进程清除,所以,只有当返回值为错误的时候才去检查errno
  2. 任何状况下,errno都不为0,由于全部的errno常量定义都没有0
    ISO C定义了两个函数
    char *strerror(int errnum);
    void perror(const char *msg);复制代码
    第一个函数传入一个给出的errnum,而后会返回errnum具体对应的出错信息字符串,第二个函数会先打印msg指针指向的字符串,而后根据线程内部维护的errno值自行打印出错信息,一般的格式为:msg指向的字符串,而后一个冒号,一个空格,紧接着是对应errno值的出错信息,最后是一个换行符。
    ```
    #include "include/apue.h"
    #include

int main(int argc, char *argv[])
{
fprintf(stderr, "EACCES: %s\n", strerror(EACCES));
errno = ENOENT;
perror(argv[0]);
exit(0);
}多线程

将其编译运行,能够的获得其输出复制代码

EACCES: Permission denied
./a.out: No such file or directoryless

咱们将argv[0]做为perror参数,让程序名做为错误信息一部分来输出,是一种Unix编程惯例,咱们常常能够看到,当程序运行失败的时候,会出现失败程序的名称,这样就能很方便的分清出错程序是哪个。
### 用户标识
用户id(uid)是一个数值,它向系统标识不一样的用户,uid为0的用户即为root用户,它能对系统随心所欲,在查看不少GNU软件的源代码的时候,咱们常常能够看到这样的代码复制代码

if (getuid() == 0)socket

也就是说一个进程拥有root权限,那么大部分文件权限检查都再也不执行。
组id(gid)是一个数值,用于肯定用户所属用户组。这种机制可以让同组内的不一样成员共享资源,系统维护了一个uid、gid与用户名、组名映射对应的机制,通常状况下就是`/etc/passwd``/etc/group`文件,目前大部分的Unix系统使用32位整形表示uid和gid,咱们能够经过检查uid_t和gid_t来肯定。复制代码

#include "include/apue.h"编程语言

int main(int argc, char *argv[])
{
printf("uid = %lu, gid = %lu\n", getuid(), getgid());
exit(0);
}

运行后就能看到进程的uid和gid属性来,通常都是当前用户的uid和gid
每一个用户除了在`/etc/passwd`中指定了一个gid之外,大多数Unix版本还容许一个用户属于其余一些组,POSIX标准要求系统应该最少支持8个附属组,可是实际上大多数系统都支持至少16个附属组。
注:Mac OS X系统并不是依靠`/etc/passwd`来维护用户列表,这个文件只有系统以单用户模式启动的时候才会使用
### 信号
信号用于通知进程发生了某些状况。例如一个进程执行了除以0的操做,CPU引起中断,内核截获中断,而后发出SIGFPE(浮点异常)信号给进程,进程有三种方式处理信号:
1. 忽略信号。因为不少信号表示硬件异常,例如,除以0或者访问进程地址空间之外的存储单元,由于这些异常引发的后果不明确,因此不推荐使用这种方式。
2. 系统默认方式处理。对于不少信号,系统默认方式就是终止进程,这点很是相似现代编程语言中异常的抛出,例如Node.js对于异常不捕获的操做,就是终止进程
3. 注册自定义的信号处理函数,当接收到信号时调用该程序。

不少状况都会产生信号,终端键盘CTRL+C和CTRL+\一般能产生信号终止当前进程,也可使用kill命令或者kill函数,从当前终端或者进程向另一个进程发送一个信号,固然,想要发送一个信号,咱们必须是接受信号的进程的全部者或者root用户。
回忆一下上一篇文章的shell实例,若是调用程序,而后按下CTRL+C,那么进程将被终止,缘由是代码里并无定义处理信号的函数,因此系统执行默认动做处理进程,对于SIGINT信号,系统默认的动做就是终止进程。
为了可以处理信号,下面对原先的代码进行了更改复制代码

#include "include/apue.h"

#include

  • static void sig_int(int);
    +
    int main(int argc, char *argv[])
    {
    char buf[MAXLINE];
    pid_t pid;

  • if (signal(SIGINT, sig_int) == SIG_ERR)

  • err_sys("signal error");
    +
    printf("%% ");
    while (fgets(buf, MAXLINE, stdin) != NULL) {
    if (buf[strlen(buf) - 1] == '\n')
    buf[strlen(buf) - 1] = '\0';复制代码
    if ((pid = fork()) < 0)
    err_sys("fork error");复制代码
    else if (pid == 0) {
    execlp(buf, buf, NULL);
    err_ret("couldn't excute: %s", buf);
    exit(127);复制代码
    }
    if ((pid = waitpid(pid, NULL, 0)) < 0)
    err_sys("waitpid error");复制代码
    printf("%% ");
    }
    exit(0);
    }
    +
    +void sig_int(int signo)
    +{
  • printf("interrupt\n%% ");
    +}
    很简单,程序调用了signal函数,其中指定了当产生SIGINT信号时要调用的函数的名字,函数名为sig_int
    这里列出一下signal的参考手册复制代码
    void (signal(int sig, void (func)(int)))(int);

or in the equivalent but easier to read typedef'd version:

typedef void (*sig_t) (int);

sig_t signal(int sig, sig_t func);

说实话,笔者估计第一次看到这玩意的朋友没几个懂它是啥意思,特别是第一行函数申明,倒数两行是等价替换的版本。实际上signal函数是一个带有两个参数的函数,第一个参数是整形,第二个参数是一个函数指针,指向接收一个整形参数的函数,也就是信号处理函数,它返回一个带有一个整形参数的函数指针。
### 时间值
Unix系统使用两种不一样的时间值
1. 日历时间,也就是一般所说的Unix时间戳,该值是UTC时间1970年1月1日0时0分0秒以来所经历的秒数累计,早期Unix系统手册使用格林尼治标准时间。系统使用`time_t`类型来存储时间值
2. 进程时间,也被称为CPU时间,用于度量进程使用的CPU资源,进程时间以时钟滴答(clock tick)计算,系统使用`clock_t`类型存储

Unix系统为一个进程维护了三个进程时间值,
* 时钟时间
* 用户CPU时间
* 系统CPU时间

时钟时间也称为真实事件,是进程运行的时间总量,用户CPU时间是执行用户指令所用的时间量,系统CPU时间是指执行内核指令所用的时间量,用户CPU时间与系统CPU时间之和就是CPU时间。
咱们能够经过`time`命令很容易的得到这些值复制代码

cd /usr/include

time -p grep _POSIX_SOURCE /.h > /dev/null
```

某些shell并不运行/usr/bin/time程序,而是使用内置函数测量

系统调用和库函数

全部的操做系统都提供了服务的入口点,由此程序能够向内核请求服务。Unix标准规定内核必须提供定义良好、数量有限、直接进入内核的入口点,这些入口点被称为系统调用,咱们能够在Unix系统参考手册第二节中找到全部提供的系统调用,这些系统调用是用C语言定义的,开发者能够很是方便的使用C函数调用它们。可是这并非说系统调用必定是C语言写的,Unix参考手册第三节是C语言通用函数库,它们是ISO C标准定义的标准C语言函数库和一系列Unix提供的函数库,可是它们不是系统调用。
从操做系统实现者角度来看,系统调用和库函数调用彻底是两码事,可是对于开发者来讲,二者并无什么差异,都是以C函数的形式出现,可是,必须知道,库函数是能够替换的,可是系统调用是没法被替代的。
例如内存管理malloc函数族,它是一种通用存储器管理器,它本身的描述是这样的

The malloc(), calloc(), valloc(), realloc(), and reallocf() functions allocate memory.  The allocated memory is aligned such that it can
be used for any data type, including AltiVec- and SSE-related types.  The free() function frees allocations that were created via the preceding allocation functions.复制代码

而Unix系统内部的分配内存的系统调用是sbrk和'brk',它们并不分配变量内存,它们只是根据字节数改变segment size,正如系统手册上写的

The brk and sbrk functions are historical curiosities left over from earlier days before the advent of virtual memory management.  The
brk() function sets the break or lowest address of a process's data segment (uninitialized data) to addr (immediately above bss). Data addressing is restricted between addr and the lowest stack pointer to the stack segment. Memory is allocated by brk in page size pieces; if addr is not evenly divisible by the system page size, it is increased to the next page boundary.复制代码

malloc只是实现了类型内存分配,可是分配内存用的仍是sbrk系统调用,若是有兴趣,咱们彻底能够自行实现内存的分配,可是咱们不可能越过系统调用。换言之,系统调用分配了空间,而malloc只是在用户层次管理分配的内存空间。

相关文章
相关标签/搜索