APUEv3 - 重读笔记

重读APUE,获益良多。

第一章,UNIX基础知识

操作系统也是一种软件,它控制计算机硬件资源,提供程序运行环境。通常我们称之为 内核,内核提供的接口被称为系统调用公用函数库构建在系统调用接口之上,应用程序 既可以使用公用函数库也可以使用系统调用。

UNIX操作系统体系结构

第二章,UNIX标准及其实现

  • POSIX - Portable Operating System Interface 可移植操作系统接口

  • Single UNIX Specification POSIX.1的超集

  • sysconf, pathconf, fpathconf 可以打印出系统各个限制

  • 基本系统数据类型如 size_t, clock_t, ssize_t

第三章,文件I/O(unbuffered I/O)

  • STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO<unistd.h>中定义

  • open

#include <fcntl.h>

int open(const char *path, int oflag, ... /* mode_t mode */)`
int openat(int fd, const char *path, int oflag, ... /* mode_t mode */)

openat函数和open的区别在于,多了一个fd参数,后面的章节还有一批类似的函数, 将不再另外说明和列出。openat函数中: 1, 如果path参数指定的绝对路径名,将忽略fd参数 2, path参数指定的是相对路径名,fd参数将作为相对路径名的起始地址 3, path参数指定了相对路径名,fd参数为特殊值AT_FDCWD,路径从当前目录开始

open函数有很多种mode,详见APUEv3 P50。

  • creat
#include <fcntl.h>

int creat(const char *path, mode_t mode);

creat 函数只能以只写方式打开文件,相当于 open(path, O_WRONLY | O_CREAT | O_TRUNC, mode)

  • close
#include <unistd.h>

int close(int fd);
  • lseek
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);

lseek执行成功时返回新的文件偏移量,其中 whence 可以为:

- `SEEK_SET` 偏移量为据文件开始处offset字节
- `SEEK_CUR` 偏移量为当前值+offset, offset可正可负
- `SEEK_END` 偏移量为文件长度+offset, offset可正可负
  • read
#include <unistd.h>

ssize_t read(int fd, void *buf, size_t nbytes);

read成功返回读到的字节数,若到文件尾端则返回0

  • write
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t nbytes);

write函数成功时返回值通常和nbytes相同,否则表示出错。出错原因通常为磁盘已满 或者是达到了文件长度限制。

  • 文件共享

打开文件的内核数据结构示意图

  • dup和dup2
#include <unistd.h>

int dup(int fd);
int dup2(int fd, int fd2);

dup返回当前可用的最小的文件描述符,dup2返回fd2,如果fd2已打开,则先关闭再打开。 若失败则返回-1。

  • sync
#include <unistd.h>

int fsync(int fd);
int fdatasync(int fd);
void sync(void);

其中sync不等待实际写磁盘操作结束,fsync和fdatasync则会等待。

  • fcntl
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* int arg */);

fcntl 有以下五种功能,详见 APUEv3 P66:

- 复制已有描述符(cmd=`F_DUPFD`/`F_DUPD_CLOEXEC`)
- 获取/设置文件描述符标志(cmd=`F_GETFD`/`F_SETFD`)
- 获取/设置文件状态标志(cmd=`F_GETFL`/`F_SETFL`)
- 获取/设置异步I/O所有权(cmd=`F_GETOWN`/`F_SETOWN`)
- 获取/设置记录锁(cmd=`F_GETLK`/`F_SETLK`/`F_SETLKW`)
  • ioctl

io操作杂物箱

第四章 文件和目录

  • stat, fstat, fstatat, lstat
#include <sys/stat.h>

int stat(const char *restrict pathname, struct stat *restrict buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *restrict pathname, struct stat *restrict buf);
int fstat(int fd, const char *restrict pathname, struct stat *restrict buf, int flag);

stat函数会返回给定命名文件有关的信息结构。

  • 文件类型

    • 普通文件,可以是文本文件或者二进制文件,UNIX内核并不做区分,但二进制可执 行文件是遵循了某种特定的格式,这样内核才可以理解并执行;普通文本文件的内容 将由理解该文本的应用程序执行。

    • 目录文件,这种文件包含了其他文件的名字和指向这些文件有关信息的指针。内核 可以直接写目录文件,进程需要调用相关函数。

    • 块特殊文件,此类文件提供对设备(如磁盘)的带缓冲的访问,每次访问固定长度。

    • 字符特殊文件,此类文件提供不带缓冲的访问。

    系统中的设备要么是字符特殊文件,要么是块特殊文件。

    • FIFO,也叫命名管道。

    • 套接字(socket)。

    • 符号链接,此类文件指向另外一个文件。

文件类型信息包含在stat结构中的 st_mode中,具体测试用的宏参见 APUEv3 P76。

  • 设置用户ID和设置组ID

一个进程相关的ID有至少6个:

实际用户ID
                             我们实际上是谁
实际组ID

---------------------------------------------

有效用户ID

有效组ID                    用于文件访问权限检查

附属组ID

---------------------------------------------

保存的设置用户ID
                             由exec函数保存
保存的设置组ID

通常来说,有效用户ID会等于实际用户ID,但是可以通过设置用户ID来使得有效用户ID 等于文件所有者ID,例如passwd程序,文件所有者为root,并且设置了用户ID:

我们忽略讨论组ID的情况,因为和用户ID一致,自己转换一下就行。

$ ll /usr/bin/passwd
-rwsr-xr-x 1 root root 52528 Oct 29 23:54 /usr/bin/passwd*

此处文件所有者为root,文件的访问权限为root可读可写可执行,当普通用户执行此 文件时,便会以root的角色来执行,所以才能成功更改密码。所以执行上面的命令时, 上述表格里:实际用户ID为jiajun,因为我登陆的时候就是jiajun,有效用户ID为root, 因为设置用户ID设置被设置了。

  • 文件访问权限

文件访问权限包含 文件拥有者读写执行权限,文件所在组读写执行权限,其他组读写执行 权限,详见 APUEv3 P79。

  • access, faccessat
#include <unistd.h>

int access(const char *pathname, int mode);
int faccessat(int fd, const char *pathname, int mode, int flag);

用于测试文件访问权限。

  • umask
#include <sys/stat.h>

mode_t umask(mode_t cmask);

对应于open和creat函数,umask函数中 cmask 设置为1的相应位一定被关闭。

  • chmod, fchmod, fchmodat
#include <sys/stat.h>

int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
int fchmodat(int fd, const char *pathname, mode_t mode, int flag);

更改文件对应权限

  • 粘着位

对目录设置了粘着位之后(我忽略了粘着位的历史意义而直接讲现在的意义),只有 对该目录具有写权限的用户并且满足下列条件之一才能删除或重命名该目录下的文件:

- 拥有此文件
- 拥有此目录
- 是超级用户
  • chown, fchown, fchownat, lchown
#include <unistd.h>

int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int fchownat(int fd, const char *pathname, uid_t owner, gid_t group, int flag);
int lchown(const char *pathname, uid_t owner, gid_t group);
  • link, linkat, unlink, unlinkat, remove
#include <unistd.h>

int link(const char *existingpath, const char *newpath);
int linkat(int efd, const char *existingpath, int nfd, const char *newpath, int flag);
int unlink(const char *pathname);
int unlinkat(int fd, const char *pathname, int flag);

#include <stdio.h>

int remove(const char *pathname);

对于文件,remove和unlink功能一致,对于目录,remove和rmdir一致。

  • rename, renameat
#include <stdio.h>

int rename(const char *oldname, const char *newname);
int renameat(int oldfd, const char *oldname, int newfd, const char *newname);

用于重命名

  • symlink, symlinkat, readlink, readlinkat
#include <unistd.h>

int symlink(const char *actualpath, const char *sympath);
int symlinkat(const char *actualpath, int fd, const char *sympath);

ssize_t readlink(const char *restrict pathname, char *restrict buf, size_t bufsize);
ssize_t readlinkat(int fd, const char *restrict pathname, char *restrict buf, size_t bufsize);

创建和读取符号链接。

  • mkdir, mkdirat, rmdir
#include <sys/stat.h>

int mkdir(const char *pathname, mode_t mode);
int mkdirat(int fd, const char *pathname, mode_t mode);

#include <unistd.h>
int rmdir(cont char *pathname);

创建和删除目录。其中rmdir是目录的链接计数成为0,当所有打开此目录以及目录中的 文件的进程关闭时,释放此目录占用的空间。

  • opendir, fdopendir, readdir, rewinddor, closedir, telldir, seekdir

详见 APUE v3 P104,主要是知道 DIR 这个结构就好。

  • chdir, fchdir, getcwd
#include <unistd.h>

int chdir(const char *pathname);
int fchdir(int fd);
char *getcwd(char *buf, size_t size);

更改和获取当前工作目录。

第五章 标准I/O库

  • 流和FILE对象
#include <stdio.h>
#include <wchar.h>

int fwide(FILE *fp, int mode);

fwide并不改变已设置的流的定向。根据mode的值有不同执行。fwide无出错返回。

#include <stdio.h>

FILE *fopen(const char *restrict pathname, const char *restrict type);
FILE *freopen(const char *restrict pathname, const char *restrict type, FILE *restrict fp);
FILE *fdopen(int fd, const char *type);

返回一个打开的流,若出错,返回NULL。

#include <stdio.h>

int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);

用于一次读取一个字符。

#include <stdio.h>

int ferror(FILE *fp);  // 判断是否出错
int feof(FILE *fp);  // 判断是否达到EOF

void clearerr(FILE *fp);  // 清楚FILE对象中维护的出错标志和文件结束标志
int ungetc(int c, FILE *fp);  // 把字符压送回流
#include <stdio.h>

int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);

输出一个字符。

#include <stdio.h>

char *fgets(char *restrict buf, int n, FILE *restrict fp);
int fpus(consr char *restrict str, FILE *restrict fp);

读和写一行。

#include <stdio.h>

size_t fread(void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp);
size_t fwrite(const void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp);

一次读写一个完整的结构。但是这通常只能用于同一台机器上,因为不同的机器可能二进制 表示不同(例如字节序等)。

#include <stdio.h>

long ftell(FILE *fp);
int fseek(FILE *fp, long offset, int whence);
void rewind(FILE *fp);

off_t ftello(FILE *fp);
int fseeko(FILE *fp, off_t offset, int whence);

int fgetpos(FILE *restrict fp, fpos_t *restrict pos);
int fsetpos(FILE *fp, const fpos_t *pos);

用于定位流,不常用,详见 APUEv3 P126。

  • 缓冲

标准I/O提供三种缓冲方式:全缓冲,行缓冲,无缓冲。

#include <stdio.h>

void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);

可以更改缓冲类型。

  • 格式化输入输出
#include <stdio.h>

int printf(const char *restrict format, ...);
int fprintf(FILE *restrict fp, const char *restrict format, ...);
int dprintf(int fd, const char *restrict format, ...);
int sprintf(char *restrict buf, const char *restrict format, ...);
int snprintf(char *restrict buf, size_t n, const char *restrict format, ...);

int scanf(const char *restrict format, ...);
int fscanf(FILE *restrict fp, const char *restrict format, ...);
int sscanf(const char *restrict buf, const char *restrict format, ...);
  • 返回文件描述符
#include <stdio.h>

int fileno(FILE *fp);
  • 临时文件
#include <stdio.h>

char *tmpnam(char *ptr);
FILE *tmpfile(void);

#include <stdlib.h>

char *mkdtemp(char *template);  // 创建临时文件夹,失败则返回NULL
int mkstemp(char *template);  // 创建临时文件,失败则返回-1
  • 内存流

使用FILE指针访问内存,但是不存储在真实的文件里

#include <stdio.h>

FILE *fmemopen(void *restrict buf, size_t size, const char *restrict type);
FILE *open_memstream(char **bufp, size_t *sizep);  // 面向字节

#include <wchar.h>

FILE *open_wmemstream(wchar_t **bufp, size_t *sizep);  // 面向宽字符

第六章 系统数据文件和信息

  • 口令文件,阴影文件,组文件

    • /etc/passwd
    • /etc/shadow
    • /etc/group
#include <pwd.h>

struct passwd *getpwuid(uid_t uid);  // 根据uid拿,例如ls
struct passwd *getpwnam(const char *name);  // 根据用户名拿,例如login

struct passwd *getpwent(void);  // 返回整个passwd的迭代器
void setpwent(void);
void endpwent(void);
#include <shadow.h>

struct spwd *getspnam(const char *name);
struct spwd *getspent(void);  // 返回迭代器

void setspent(void);
void endspent(void);
#include <grp.h>

struct group *getgrgid(gid_t gid);
struct group *getgrnam(const char *name);

struct group *getgrent(void);  // 返回迭代器
void setgrent(void);
void endgrent(void);

附属组详见 APUEv3 P147。

  • 系统标识
#include <sys/utsname.h>

int uname(struct utsname *name);

#include <unistd.h>

int gethostname(char *name, int namelen);
  • 系统时间

详见 APUEv3 P151。

第七章 进程环境

  • 进程终止

有8中方式使进程终止:

> 正常终止
- 从main返回
- 调用exit
- 调用 `_exit` 或 `_Exit`
- 最后一个线程从其启动例程返回
- 最后一个线程调用`pthread_exit`
> 异常中止
- 调用abort
- 接到一个信号
- 最后一个线程对取消请求作出响应
  • exit
#include <stdlib.h>

void exit(int status);
void _Exit(int status);

#include <unistd.h>

void _exit(int status);

exit先执行一些清理操作,例如关闭流等,然后再返回到内核,而 _exit _Exit 则直接进入内核

#include <stdlib.h>

int atexit(void (*func)(void));

注册一个退出函数,在退出的时候将会调用。exit函数调用它们的顺序与注册顺序相反。

一个C程序是如何启动和终止的

  • 命令行参数,进程环境

命令行参数会传递给main中的 int argc, char *argv[]。其中argc为参数长度,argv 为参数列表,argv[0]为该程序名称。

每个程序都会接到一个环境表,extern char **environ;

#include <stdlib.h>

char *getenv(const char *name);
int putenv(char *str);
int setenv(const char *name, const char *value, int rewrite);
int unsetenv(const char *name);
#include <sys/resource.h>

int getrlimit(int resource, struct rlimit *rlptr);
int setrlimit(int resource, const struct rlimit *rlptr);

获取资源限制值。

  • C程序的存储空间布局

C程序的存储空间布局

- 正文段,通常是只读的,存储CPU执行指令
- 初始化数据段 包含了程序中明确赋初值的变量
- 为初始化数据段,也叫bss(block started by symbol)段,存储未初始化的变量,
内核通常将其赋值为0或NULL
- 栈,向低地址增长
- 堆,向高地址增长
  • 共享库,动态链接,静态链接

共享库使得可执行文件中不再需要包含公共的运行库,而只需要在需要的时候动态链接 即可。动态链接和静态链接的区别是:

- 静态链接的可执行文件执行速度快,因为省去了动态链接的时间,不会出现共享库
版本兼容问题,缺点是占用空间大,所有的软件都包含自己的一份
- 动态链接反之。
  • 内存管理
#include <stdlib.h>

void *malloc(size_t size);
void *calloc(size_t nobj, size_t size);
void *realloc(void *ptr, size_t new_size);

void free(void *ptr);

上面三个函数成功则返回内存地址,出错则返回NULL。

标准库中包含的内存管理函数都是调用系统调用,如sbrk等。

  • 跳转

goto 语句可以提供函数内跳转,setjmplongjmp提供函数间跳转。注意后者 恢复时不一定能恢复CPU寄存器内的值和自动变量等。

第八章 进程控制

  • 每个进程都有一个非负整数表示唯一的进程ID,UNIX下1号进程通常为init,现在的 Linux普遍为systemd.
[email protected] ~: ps -p 1
  PID TTY          TIME CMD
    1 ?        00:00:00 systemd
#include <unistd.h>

pid_t getpid(void);
pid_t getppid(void);
uid_t getuid(void);
uid_t geteuid(void);
gid_t getgid(void);
gid_t getegid(void);
  • fork, vfork
#include <unistd.h>

pid_t fork(void);

执行fork后,fork出来的新进程,此函数返回0,原来的进程,此函数返回新进程的pid。 fork出的父子进程共享同一套文件描述符。子进程会继承很多父进程的属性,详见 APUEv3 P185。

#include <sys/types.h>
#include <unistd.h>

pid_t vfork(void);

vfork 返回值与fork一致,但是vfork创建出的新进程目的就是exec一个新的程序,所以 不会将父进程的地址空间完全复制到子进程中;而且vfork保证子进程先执行。

  • wait, waitpid

当一个进程终止时,内核就会向其父进程发送 SIGCHLD 信号,如果该进程的父进程在 此之前就已经退出了,那么将由init进程为它收尸。

#include <sys/wait.h>

pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

wait使其调用者阻塞,waitpid可以不阻塞。

  • exec函数族
#include <unistd.h>

int execl(const char *pathname, const char *arg0, ... /* (char *)0 */);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */);
int execvp(const char *filename, char *const argv[]);
int fexecve(int fd, char *const argv[], char *const envp[]);

前四个去函数路径名作为参数,后两个函数取文件名作为参数,最后一个取文件描述符 作为参数。带l的表示传的参数是列表,最后用(char *)0结尾,带v的表示argv,带 e的表示要传一个参数列表进去。这七个函数最后都会调用execve:

exec函数族之间的关系

  • 更改用户ID和组ID
#include <unistd.h>

int setuid(uid_t uid);
int setgid(gid_t gid);
int seteuid(uid_t uid);
int setegid(gid_t gid);

第九章 进程关系

  • 进程组

进程组是一个或多个进程的集合,每个进程组有一个唯一的进程组ID。

#include <unistd.h>

pid_t getpgrp(void);
int setpgid(pid_t pid, pid_t pgid);

setpgid可以加入一个现有的进程组或者创建一个新的进程组。

  • 会话

会话是一个或多个进程组的集合,进程使用 setsid 建立一个新的会话。用户登录一次 系统就形成一个会话。一个会话可以有多个进程组,但是只能有一个前台进程组。

#include <unistd.h>

pid_t setsid(void);
pid_t getsid(pid_t pid);
  • 控制终端,详见APUEv3 P235。

参考资料:http://www.cnblogs.com/JohnABC/p/4079669.html

第十章 信号

每个信号都有一个名字,以SIG开头,详见 APUEv3 P250。

  • signal

原始的signal函数信号可能会丢失,但是现在的linux底层都是sigaction实现的signal。

#include <signal.h>

void (*signal(int signo, void (*func)(int)))(int);

可重入函数:https://zh.wikipedia.org/wiki/%E5%8F%AF%E9%87%8D%E5%85%A5

#include <signal.h>

int kill(pid_t pid, int signo);
int raise(int signo);

#include <unistd.h>

int pause(void); // 挂起进程直至捕捉到一个信号
  • alarm
#include <unistd.h>

unsigned int alarm(unsigned int seconds);

alarm函数设置一个定时器,当定时器超时时,产生SIGALRM信号

  • 信号集
#include <signal.h>

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);

第十一章, 第十二章 线程

一个进程的所有信息对该进程的所有线程都是共享的,包括可执行程序的代码, 程序的全局内存和堆内存,栈以及文件描述符。

#include <pthread.h>

int pthread_equal(pthread_t tid1, pthread_t tid2);
pthread_t pthread_self(void);
int pthread_create(
    pthread_t *restrict tidp, const pthread_attr_t *restrict attr,
    void *(*start_rtn)(void *), void *restrict arg
);
void pthread_exit(void *rval_ptr);
int pthread_join(pthread_t thread, void **rval_ptr);
int pthread_detach(pthread_t tid);
int pthread_cancel(pthread_t tid);

// 注册线程清理处理程序
void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);
  • 互斥量

互斥量的本质上是一把锁,在访问共享资源前加锁,之后释放锁。

  • 读写锁

读写锁与互斥量相似,但是允许更高的并行性。

  • 自旋锁

自旋锁与互斥量相似,但是他不通过休眠时进程阻塞,而是一直忙等待,阻塞。 自旋锁适用于以下情况:所被持有的时间段,而且线程不希望在重新调度上花费太多的 成本。

  • fork
#include <pthread.h>

int pthread_atfork(
    void (*prepare)(void), void (*parent)(void), void (*child)(void)
);

线程在fork之后同时会获得互斥量,读写锁,条件变量等。可以注册fork之后对线程 的操作。prepare在fork之前调用,parent在fork之后父进程调用,child在fork之后 子进程调用。

第十三章 守护进程

daemon是一种长期生存的进程,它们常常在系统启动时启动,在系统关闭时才关闭( 例如sshd)。参照第九章,守护进程不应该有控制终端。

  • 编程规则

    • 首先调用umask将文件模式创建屏蔽字设置为一个已知值,通常为0。因为守护 进程通常要创建某些文件。
    • 调用fork,然后使父进程exit。这样子进程继承了父进程的进程组ID,但是却 不是该进程组的组长进程(因为一个进程只能属于一个进程)
    • 调用setsid创建一个新会话,这样该进程便成为了新会话的手进程,并且是 新会话里新进程组的组长进程,而且没有控制终端
    • 将当前工作目录更改为根目录或指定目录
    • 关闭不再需要的文件描述符,例如0,1,2。
  • 惯例

    • 若守护进程使用所文件,那么该文件通常存储在 /var/run 目录中且命名方式为 name.pid,例如 /var/run/sshd.pid
    • 若守护进程支持配置选项,那么配置文件通常放在 /etc 下,命名方式为 name.conf,例如 /etc/dhcpcd.conf
    • 守护进程可以使用命令行启动
    • 若配置文件更改,守护进程不会监控配置文件是否已经更改,需外部restart该进程
  • 日志

#include <syslog.h>

void openlog(cons char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
int etlogmask(int maskpri);

第十四章 高级I/O

  • 低速系统调用

低速系统调用是可能会使进程永远阻塞的一类系统调用,包括:

- 如果某些文件类型不存在(如读管道,终端设备和网络设备)的数据不存在,读操
作可能会使调用者永远阻塞
- 如果数据不能被相同的文件类型立即接受(如管道中无空间),写操作可能会使
调用者永远阻塞
- 某种条件发生之前打开某些文件类型可能会发生阻塞,例如只写模式打开FIFO,
那么如果没有其他进程用已读模式打开该FIFO时也要等待
- 对已经加上强制性记录锁的文件进行读写
- 某些ioctl操作
- 某些进程间通信函数
  • 非阻塞I/O

有两种方式指定为非阻塞I/O

- 对于open,指定 `O_NONBLOCK`
- 使用fcntl,设置 `O_NONBLOCK`
  • 记录锁

对于有些进程如数据库,进程有时需要保证他正在单独写一个文件,于是有了记录锁这个 概念。但记录锁并不是记录,而是锁定文件的某个范围。详见 APUEv3 P392。

  • I/O 多路复用

man epoll

  • POSIX 异步I/O

略,见 APUEv3 P411。

  • 存储映射I/O

存储映射I/O是将一个磁盘文件映射到存储空间中的一个缓冲区上的技术。

#include <sys/mman.h>

void *mmap(void *addr, size_t len, int prot, int flag, int fd, off_t off);
int mprotext(void *addr, size_t len, int prot);  // 更改现有映射的权限
int msync(void *addr, size_t len, int flags);  // 将内存中的修改冲刷到文件上
int munmap(void *addr, size_t len);  // 解除映射

epoll就使用了mmap,达到内核态和用户态使用同一块内存的目的,从而节省了拷贝数据 的开销。

第十五章,第十六章,第十七章 进程间通信

  • 管道
#include <unistd.h>

int pipe(int fd[2]);

经由参数返回两个文件描述符,fd[0]是为读而打开,fd[1]是为写而打开。fd[1]是fd[0] 的输入。

当一个过滤程序及产生某个过滤程序的输入,又读取该过滤程序的输出时,它就变成了 协同进程(coprocess)。

  • popen, pclose
#include <stdio.h>

FILE *popen(const char *cmdstring, const char *type);
int pclose(FILE *fp);

该函数先执行fork,然后exec执行cmdstring,并且返回一个标准I/O文件指针。

  • FIFO

管道的缺点是只能在父子进程之间进行通信,于是有了FIFO,也被称为命名管道。

#include <sys/stat.h>

int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);
  • 消息队列
#include <sys/msg.h>

int msgget(key_t key, int flag);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag);
ssize_t msgrcv(int msqid, void *pre, size_t nbytes, long type, int flag);
  • 信号量

信号量是一个计数器,用于为多个进程提供共享数据对象的访问。

#include <sys/sem.h>

int semget(key_t key, int nsems, int flag);
int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);
int semop(int semid, struct sembuf semoparray[], size_t nops);

- 共享存储

共享存储允许两个或多个进程共享一个特定的存储区。使用共享存储时要注意的是:
在多个进程之间同步访问一个给定的存储区,若服务器进程正在将数据放入共享存储区,
则它做完这一操作之前客户进程不应当去取这些数据。

```c
#incllude <sys/shm.h>

int shmget(key-t key, size_t size, int flag); // 获得一个共享存储标志符
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
void *shmat(int shmid, const void *addr, int flag); // 连接该共享存储
int shmdt(const void *addr); // 删除
  • 套接字(socket)
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
int shutdown(int sockfd, int how);

创建和关闭套接字。

  • 字节序

网络字节序为大端,不过Intel的cpu为小端。

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostint32); // 返回以网络字节序表示的32位整数
uint16_t htons(uint16_t hostint16); // 以网络字节序表示的16位整数
uint32_t ntohl(uint32_t netint32); // 以主机字节序表示的32位整数
uint16_t ntohs(uint16_t netint16); // 以主机字节序表示的16位整数
  • 地址格式转换
#include <arpa/inet.h>

const char *inet_ntop(
    int domain, const void *restrict addr, char *restrict str, socklen_t size
); // 将网络字节序的二进制表示转换成文本字符串格式
int inet_pton(
    int domain, const char *restrict str, void *restrict addr
); // 将文本字符串格式转换成网络字节序的二进制表示
  • 地址查询
#include <netdb.h>

// 查询主机信息
struct hostent *gethostent(void); // 返回迭代器
void sethostent(int stayopen);
void endhostent(void);

// 网络名字和网络编号转换
struct netent *getnetbyaddr(uint32_t net, int type);
struct netent *getnetbyname(const char *name);
struct netent *getnetent(void);
void setnetent(int stayopen);
void endnetent(void);

// 服务名和端口号转换
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);
struct servent *getservvent(void);

void setservent(int stayopen);
void endservent(void);
  • 套接字操作
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
int getsockname(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp);
int getpeername(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp);

int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);


// 数据传输
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);


// 套接字选项
int setsockopt(int sockfd, int level, int option, const void *val, socklen_t len);
int getsockopt(int sockfd, int level, int option, void *restrict val, socklen_t *restrict lenp);
  • UNIX域套接字
#include <sys/socket.h>

int socketpair(int domain, int type, int protocol, int sockfd[2]);

创建一堆无命名的相互连接的UNIX域套接字。


附录:

  • C语言的几个关键字的意义:

    • restrict:只用于限定指针:该关键字用于告知编译器,所有修改该指针所指向 内容的操作全部都是基于(base on)该指针的,即不存在其它进行修改操作的途径
    • inline:内联函数,是为了解决C 预处理器宏存在的问题所提出一种解决方案, 用来提高函数使用效率。
    • volatile:提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的 程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。

点击订阅我的Telegram Channel获取实时推送
上一篇:unittest 源代码阅读 下一篇:Mock源码阅读