UNIX环境高级编程-第三版

Unix环境高级编程-第三版

之前学习了《Linux系统编程》对于常见的概念和函数都有了基础的认知,这里准备通过这本书,深入学习系统API相关内容。笔记内容会有所倾向,不会严格反应书本内容。

基础概念

+----------+
|应用程序   |
+------+   |
| Shell|   |
+------+   |
| 内核  |   |
+------+   |
| Libs |   |
+------+---+

内核的接口称为系统调用(system call)。公用函数库(libs)建立在系统调用基础上,应用程序既可以使用公共函数库,也可以系统调用。Shell是比较特殊的应用程序,为其他程序提供一个接口。Linux是GNU操作系统使用的内核,一般称为GNU/Linux操作系统,更常见的叫法为Linux。

登陆

用户在登陆Unix系统时,输入用户名和口令,系统在口令文件/etc/passwd中查看。口令文件是由7个冒号分割的字段组成,依次是:登录名、加密口令、用户ID、数字组ID、注释字段、起始目录、Shell程序。
如:sar:x:205:105:Stephen Rago:/home/sar:/bin/ksh.目前,所有系统的加密口令移动到单独文件中了。

Shell是一个命令行解释权,它读取用户输入,然后执行命令。sh是Unix默认shell, csh是BSD默认shell, bash是sh的改进,支持csh的特色,所有Linux都有。

文件和目录

文件名的最大长度是255个字符(characters),文件路径的最大长度是4096字符(characters), 即可以包含16级的最大文件长度的路径。

API说明

#include 
#include 

DIR *opendir(const char *name);
DIR *fdopendir(int fd);

struct dirent {
    ino_t          d_ino;       /* Inode number */
    off_t          d_off;       /* Not an offset; see below */
    unsigned short d_reclen;    /* Length of this record */
    unsigned char  d_type;      /* Type of file; not supported
                                    by all filesystem types */
    char           d_name[256]; /* Null-terminated filename */
};
struct dirent *readdir(DIR *dirp);

#include 

pid_t getpid(void);
pid_t getppid(void);
pid_t fork(void);

extern char **environ;

int execl(const char *path, const char *arg, ...
                /* (char  *) NULL */);
int execlp(const char *file, const char *arg, ...
                /* (char  *) NULL */);
int execle(const char *path, const char *arg, ...
                /*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

#include 
int fgetc(FILE *stream);
char *fgets(char *s, int size, FILE *stream);
int getc(FILE *stream);
int getchar(void);
int ungetc(int c, FILE *stream);

#include 
// 这一类的api都是为了让父进程等待子进程,并且获取子进程状态变化
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);

char* strerror(int errnum); // 映射消息的文本信息
void perror(const char* msg); // 输出一条出错消息,然后返回

模仿的Demo:

/* 模拟ls */
#include "apue.h"
#include 
int main(int argc, char *argv[]) {
    DIR *dp;
    struct dirent *dirp;
    if( argc != 2 ){
        err_quit("usage: ls ");
    }
    if( (dp = opendir(argv[1])) == NULL ){
        err_sys("can't open %s", argv[1]);
    }
    while( (dirp = readdir(dp))!=NULL ){
        printf("%s -> %d %d\n", dirp->d_name, dirp->d_reclen, dirp->d_type);
    }
    closedir(dp);
    return 0;
}
// xls ./

/* 无缓冲IO */
#include "apue.h"
#define BUFFSIZE 4096

int main(){
    int n;
    char buf[BUFFSIZE];
    while( (n=read(STDIN_FILENO, buf, BUFFSIZE)) >0 ){
        if( write(STDOUT_FILENO, buf, n)!=n ){
            err_sys("write error!");
        }
    }
    if(n<0)
        err_sys("read error!");
    return 0;
}

// xcp < in.txt > out.txt

/* 标准IO */
// 库提供缓冲管理
#include "apue.h"

int main(){
    int c;
    while( (c=getc(stdin))!=EOF ){
        if( putc(c, stdout) == EOF ){
            err_sys("output error");
        }
    }
    if( ferror(stdin) ){
        err_sys("input error");
    }
    return 0;
}
// xcp2 < in.txt > out.txt

/* 类似shell功能 */
#include "apue.h"
#include 

int main(){
    char buf[MAXLINE];
    pid_t pid;
    int status;
    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 ){
            // child
            execlp(buf, buf, (char*)0); // 这个实现不是很完美
            err_ret("couldn't execute: %s", buf);
            exit(127);
        }
        // parent
        if( (pid = waitpid(pid, &status, 0)) < 0 ){
            err_sys("waitpid error");
        }  
        printf("%% ");
    } 
    return 0;
}

用户组

除了用户ID和组ID,大多数Unix还允许用户属于另外其他的组,这个功能从4.2BSD开始,最多允许用户属于16个其他组。
查询用户属于的组cat /etc/group | grep root, 分别为用户组name,用户组口令,用户组ID,用户。使用指令groups可获取到当前用户所在的组。

头文件标准

ISO C标准定义

头文件 说明
验证程序断言
复数算术运算支持
字符分类和映射支持
出错码
浮点环境
浮点常量及特性
整形格式变换
赋值、关系、一元操作宏
实现常量
本地化类别及定义
数学函数、类型、常量
非局部goto
信号
可变长度参数表
布尔类型和值
标准定义
整形
标准IO库
实用函数
字符串操作
通用类型数学宏
时间和日期
扩充的多字符和宽字符支持
宽字符分类和映射支持

POSIX.1标准 包含了iso c标准库

头文件 说明
异步IO
cpio归档值
目录项
动态链接库
文件控制
文件名匹配类型
路径名模式匹配与生成
组文件
代码集变换实用程序
语言信息常量
货币类型与函数
网络数据库操作
消息类
投票函数
线程
口令文件
正则表达式
执行调度
信号量
字符串操作
tar归档
终端IO
符号常量
字扩充类型
因特网定义
套接字本地接口
因特网地址族
传输协议定义
存储管理
select函数
套接字接口
文件状态
文件系统信息
进程时间
基本系统数据类型
UNIX套接字定义
系统名
进程控制

POSIX.1 2008 可选头文件

头文件 说明
消息显示结构
文件树漫游
路径名管理函数
数据库操作
搜索表
系统出错日志记录
用户账户数据库
IPC
XSI消息队列
资源操作
XSI信号量
XSI共享存储
时间类型
矢量IO操作
消息队列
实时spawn接口

IO

IO操作是所有操作中最常用的一个,一般只需要5个函数open/read/write/lseek/close,这些函数都是不带缓冲的IO。实际使用时,更多时候需要使用dup/fcntl/sync/fsync/ioctl配合使用。

文件IO基础操作比较简单,不做说明。

特殊的,当fork等手段产生多个进程读写同一文件时,会导致数据丢失或者异常,为此,设计了原子性操作函数:pread/pwrite,作用等同lseek然后read/write,但是这个过程不会被打断。

#include 

int dup(int oldfd);
int dup2(int oldfd, int newfd);
// 等效于 dcntl(fd, F_DUPFD, 0), dup是原子操作

// sync/fsync/fdatasync 

#include 
int fcntl(int fd, int cmd, ... /* arg */ ); // 改变fd的属性

#include 
int ioctl(int fd, unsigned long request, ...); // IO杂物箱,其他函数不能做的都要它来做

因为大部分情况下,针对串口、摄像头等设备,都需要使用ioctl设置参数,所以,这里需要注意ioctl的request参数,它根据设备不同,参数也不同。

类别 常量名 头文件 ioctl数
盘标号 DIOxxx 4
文件IO FIOxxx 14
磁带IO MTIOxxx 11
套接字IO SIOxxx 73
终端IO TIOxxx 43

/dev/fd/x等价与复制描述符n。

文件和目录

之前了解了基础的IO操作,这里主要关注文件系统和文件的性质。

#include 
#include 
#include 
int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);

#include            /* Definition of AT_* constants */
#include 
int fstatat(int dirfd, const char *pathname, struct stat *statbuf, int flags);

struct stat {
    dev_t     st_dev;         /* ID of device containing file */
    ino_t     st_ino;         /* Inode number */
    mode_t    st_mode;        /* File type and mode */
    nlink_t   st_nlink;       /* Number of hard links */
    uid_t     st_uid;         /* User ID of owner */
    gid_t     st_gid;         /* Group ID of owner */
    dev_t     st_rdev;        /* Device ID (if special file) */
    off_t     st_size;        /* Total size, in bytes */
    blksize_t st_blksize;     /* Block size for filesystem I/O */
    blkcnt_t  st_blocks;      /* Number of 512B blocks allocated */

    /* Since Linux 2.6, the kernel supports nanosecond
        precision for the following timestamp fields.
        For the details before Linux 2.6, see NOTES. */

    struct timespec st_atim;  /* Time of last access */
    struct timespec st_mtim;  /* Time of last modification */
    struct timespec st_ctim;  /* Time of last status change */

#define st_atime st_atim.tv_sec      /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};

这类函数都返回文件的信息结构。其中,文件类型可以通过对st_mode宏S_ISREG/S_ISDIR/S_ISCHR/S_ISBLK/S_ISFIFO/S_ISLNK/S_ISSOCK 判断文件类型。在IPC中对整个结构体判断IPC对象类型S_TYPEISMQ/S_TYPEISSEM/S_TYPEISSHM.

st_mode中也保存了对文件的访问权限位。除了常见的权限,其实每个文件都有9个访问权限。

st_mode屏蔽 含义
S_IRUSR 用户读
S_IWUSR 用户写
S_IXUSR 用户执行
S_IRGRP 组读
S_IWGRP 组写
S_IXGRP 组执行
S_IROTH 其他读
S_IWOTH 其他写
S_IXOTH 其他执行

当create文件或者目录时,默认使用进程的有效用户ID和组ID,如果想要测试是否有权限,如下:

#include 
int access(const char *pathname, int mode);

// 如果需要改变权限,如下
#include 
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);

// 改变用户ID或者组ID
#include 
int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char *pathname, uid_t owner, gid_t group);

// 文件截断
#include 
#include 
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);

// 链接
int link(const char *oldpath, const char *newpath);
int unlink(const char *pathname);

int remove(const char *pathname); // 通用文件与文件夹

int rename(const char *oldpath, const char *newpath);

int symlink(const char *target, const char *linkpath);

ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);

// 文件
// st_atim 访问时间/st_mtim 内容修改时间/st_ctim 属性更改时间
int futimens(int fd, const struct timespec times[2]);
int utimensat(int dirfd, const char *pathname, const struct timespec times[2], int flags);

// 目录
int mkdir(const char *pathname, mode_t mode);
int rmdir(const char *pathname);

#include 
DIR *opendir(const char *name);
DIR *fdopendir(int fd);
struct dirent *readdir(DIR *dirp);
void rewinddir(DIR *dirp);
int closedir(DIR *dirp);
long telldir(DIR *dirp);
void seekdir(DIR *dirp, long loc);

int chdir(const char *path);
int fchdir(int fd);

char *getcwd(char *buf, size_t size);
char *getwd(char *buf);
char *get_current_dir_name(void);

流和FILE对象

#include 
#include 

int fwide(FILE *stream, int mode); // 设置流的定向,决定是单字符还是宽字符模式
void setbuf(FILE *restrict stream, char *restrict buf); // 修改缓冲机制
int setvbuf(FILE *restrict stream, char *restrict buf, int type, size_t size);
int fflush(FILE *stream); // 强制冲洗一个流
fopen/freopen/fdopen // 打开、重新打开、获取描述符
int fclose(FILE* fp); // 关闭一个流

getc/fgetc/getchar // 一次读取一个字节
ferror/feof // 获取流错误码
clearerr // 清除错误码
ungetc // 将字符压回流
putc/fputc/putchar // 输出一个字符

fgets/gets // 读取一行
fputs/puts // 写入一串数据,不一定是一行

fread/fwrite // 读写二进制数据

ftell/fseek/rwind/fgetpos/fsetpos // 读、写指针位置

printf/sprintf/fprintf/dpintf/snprintf // 格式化io
vprintf/vfprintf/vdprintf/vsprintf/vsnprintf // 可变参数的
// %[flags][fldwidth][precision][lenmodifier]convtype

// 百分号后跟随字符意义 flags
// , 整数按照千分位分组
// - 左对齐
// + 总是显示正负号
//   如果第一个字符不是正负号,则添加一个空格
// # 指定另一种转换形式,比如16进制加0x
// 0 添加前导0填充,不使用空格

// lenmodifier 参数长度
// hh (u)char 类型长度
// h  (u)short
// l  (u)long
// ll  (u)long long
// j  (u)intmax_t
// z  size_t
// t  ptrdiff_t
// L  long double

// convtype 如何解释参数
// d,i  有符号10进制
// o  无符号八进制
// u  无符号十进制
// x,X  无符号16进制
// f,F  双精度浮点
// e,E  指数格式双精度
// g,G  解释为f/F/e/E
// a,A  16进制指数格式双精度
// c  字符,lc是宽字符
// s  字符串,ls是宽字符串
// p  void指针
// n  printf输出字符数目写入到带符号整数中
// %  %自身
// C  宽字符,=lc
// S  宽字符串,=ls

// convtype 还存在显式指定第n个参数的格式化,%n$,与上面的不能同时使用

scanf/fscanf/sscanf // 格式化输入
// %[*][fldwidth][m][lenmodifier]convtype
// d  有符号10进制
// i  有符号10进制,基数由输入格式确定
// o  无符号八进制
// u  有符号十进制,基数10
// x,X  无符号16进制
// f,F  双精度浮点
// e,E  指数格式双精度
// g,G  解释为f/F/e/E
// a,A  16进制指数格式双精度
// c  字符,lc是宽字符
// s  字符串,ls是宽字符串
// p  void指针
// n  printf输出字符数目写入到带符号整数中
// %  %自身
// C  宽字符,=lc
// S  宽字符串,=ls
// [ 匹配列出的字符序列,以]结束
// [^ 匹配列出字符以外的序列,以]终止

#include 
#include 

int main(int argc, char *argv[]) {
	printf("Hello!\n");
	unsigned int n = 0;
	printf("%2$.2f %1$4.2d -%3$n\n", -12, -3.2567,&n);
	printf("%*s%d\n",n,"",n);
	return 0;
}
// %n$ 从1开始,指明第几个参数
// %n 指定一个uint地址,存储%n所在位置的序号
// %*s 需要2个参数,第一个是类似fildwidth,第二个是%s的值,效果与%.2f类似

int fileno(FILE *stream); 
char *tmpnam(char *s); // 产生一个与现有文件名不同的有效路径名字符串,最多TMP_MAX
FILE *tmpfile(void); // 产生一个临时二进制文件(wb+),关闭或程序结束是自动删除
char *mkdtemp(char *template);

// 与上述不同的是,产生的临时文件不会主动删除,需要自己unlink
int mkstemp(char *template);
int mkostemp(char *template, int flags);
int mkstemps(char *template, int suffixlen);
int mkostemps(char *template, int suffixlen, int flags);

// 内存流
FILE *fmemopen(void *buf, size_t size, const char *mode);

FILE *open_memstream(char **ptr, size_t *sizeloc);
#include 
FILE *open_wmemstream(wchar_t **ptr, size_t *sizeloc);

系统数据文件

// /etc/passwd
#include 
struct passwd {
    char   *pw_name;       /* username */
    char   *pw_passwd;     /* user password */
    uid_t   pw_uid;        /* user ID */
    gid_t   pw_gid;        /* group ID */
    char   *pw_gecos;      /* user information */
    char   *pw_dir;        /* home directory */
    char   *pw_shell;      /* shell program */
};
struct passwd *getpwnam(const char *name);
struct passwd *getpwuid(uid_t uid);
struct passwd *getpwent(void);
void setpwent(void);
void endpwent(void);

// demo
#include 
#include 
#include 

int main(int argc, char *argv[]) {
	struct passwd* ptr = NULL;
	while ( (ptr = getpwent())!= NULL ) {
		printf("U/P:%s %s \nID:%d %d \nI:%s \nDIR:%s \nSHELL:%s\n",
			ptr->pw_name, ptr->pw_passwd, ptr->pw_uid, ptr->pw_gid, ptr->pw_gecos,
			ptr->pw_dir, ptr->pw_shell);
			printf("-----------------\n");
	}
	endpwent();
	return 0;
}

// 在.etc/passwd 中,用户密码都是x,因为它被移到单独的文件/etc/shadow中,并且加密了,阴影口令文件
#include 
struct spwd {                                                  
    char *sp_namp;     /* Login name */                        
    char *sp_pwdp;     /* Encrypted password */                
    long  sp_lstchg;   /* Date of last change                  
                          (measured in days since              
                          1970-01-01 00:00:00 +0000 (UTC)) */  
    long  sp_min;      /* Min # of days between changes */     
    long  sp_max;      /* Max # of days between changes */     
    long  sp_warn;     /* # of days before password expires    
                          to warn user to change it */         
    long  sp_inact;    /* # of days after password expires     
                          until account is disabled */         
    long  sp_expire;   /* Date when account expires            
                          (measured in days since              
                          1970-01-01 00:00:00 +0000 (UTC)) */  
    unsigned long sp_flag;  /* Reserved */                     
};                                                             
struct spwd *getspnam(const char *name);
struct spwd *getspent(void);
void setspent(void);
void endspent(void);
struct spwd *fgetspent(FILE *stream);
struct spwd *sgetspent(const char *s);
int putspent(const struct spwd *p, FILE *stream);
int lckpwdf(void);
int ulckpwdf(void);

char *crypt(const char *key, const char *salt); // 密码hash, -lcrypt
// demo, 可以验证自己的密码
#define _GNU_SOURCE
#include 

int main(int argc, char *argv[]) {
    char* pT1 = crypt("my_pwd", "$6$tFoDYuJAMie9hU/s");
    printf("%s\n", pT1);
    return 0;
}

// 组文件 /etc/group
#include 
#include 
struct group {
    char   *gr_name;        /* group name */
    char   *gr_passwd;      /* group password */
    gid_t   gr_gid;         /* group ID */
    char  **gr_mem;         /* NULL-terminated array of pointers
                                to names of group members */
};
struct group *getgrnam(const char *name);
struct group *getgrgid(gid_t gid);
struct group *getgrent(void);
void setgrent(void);
void endgrent(void);

// 4.2BSD 开始,用户有附属组ID
#include 
#include 
#include 
int getgroups(int size, gid_t list[]);
int setgroups(size_t size, const gid_t *list);
int initgroups(const char *user, gid_t group);

其他数据文件:
BSD各网络服务器提供服务的数据文件/etc/services,记录协议信息的/etc/protocols,记录网络信息的/etc/networks

系统标志:

#include 
struct utsname {
   char sysname[];    /* Operating system name (e.g., "Linux") */
   char nodename[];   /* Name within "some implementation-defined
                           network" */
   char release[];    /* Operating system release (e.g., "2.6.28") */
   char version[];    /* Operating system version */
   char machine[];    /* Hardware identifier */
};
int uname(struct utsname *buf);
// cat /etc/lsb-release
/*
Linux 
xinvm
4.15.0-39-generic
#42-Ubuntu SMP Tue Oct 23 15:48:01 UTC 2018
x86_64
*/
int gethostname(char *name, size_t len);
int sethostname(const char *name, size_t len);

时间:

#include 
struct tm {
    int tm_sec;    /* Seconds (0-60) */
    int tm_min;    /* Minutes (0-59) */
    int tm_hour;   /* Hours (0-23) */
    int tm_mday;   /* Day of the month (1-31) */
    int tm_mon;    /* Month (0-11) */
    int tm_year;   /* Year - 1900 */
    int tm_wday;   /* Day of the week (0-6, Sunday = 0) */
    int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */
    int tm_isdst;  /* Daylight saving time */
};
char *asctime(const struct tm *tm);
char *asctime_r(const struct tm *tm, char *buf);

char *ctime(const time_t *timep);
char *ctime_r(const time_t *timep, char *buf);

struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *timep, struct tm *result);

struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);

time_t mktime(struct tm *tm);
// 将时间格式化为字符串,把字符串转化为time_t
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
char *strptime(const char *s, const char *format, struct tm *tm);

进程

进程终止有5中正常行为:

  • main返回
  • exit调用
  • _exit/_Exit调用
  • 最后一个线程返回
  • 最后一个线程调用pthread_exit

异常终止有3中方式:

  • abort调用
  • 接到一个信号
  • 最后一个线程读取消请求做出相应

一个进程可以最多登记32个函数,当exit时自动调用,这些函数称之为终止处理程序,他们通过atexit函数注册。调用与登记的顺序相反。

参数表:int main(int argc, char* argv[])(一般第一个是自身,第二个开始正常使用,最后一个是NULL)
环境表:extern char **environ; 最后一个字符串指针是NULL。name=value形式存储.

char *getenv(const char *name);
int putenv(char *string);
int setenv(const char *name, const char *value, int overwrite);
int unsetenv(const char *name);
int clearenv(void);

存储空间分配:

#include 

void *malloc(size_t size);
void free(void *ptr);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
void *reallocarray(void *ptr, size_t nmemb, size_t size);

C语言中,goto是不能跨越函数的,所以有了setjmp/longjmp.

#include 

int setjmp(jmp_buf env); // 设置跳转点环境
int sigsetjmp(sigjmp_buf env, int savesigs);

void longjmp(jmp_buf env, int val); // 跳转,全局、静态、易逝变量保持不变
void siglongjmp(sigjmp_buf env, int val);

资源限制:getrlimit/setrlimit

exec系列函数

解释器文件(interpreter file):
文本文件起始行:#! pathname [optional-args]

int system(const char *command); // 内部实现了fork/exec/waitpid

#include 
char *getlogin(void);
int getlogin_r(char *buf, size_t bufsize);
#include 
char *cuserid(char *string);

#include 
int nice(int inc); // 调整nice值

#include 
#include 
int getpriority(int which, id_t who);
int setpriority(int which, id_t who, int prio);

#include 
clock_t times(struct tms *buf); // 进程时间

进程组,每个进程除了有进程ID还有一个进程组。同一进程组中的各个进程接收来自同一终端的各种信号。

#include 
#include 

int setpgid(pid_t pid, pid_t pgid);
pid_t getpgid(pid_t pid);

pid_t getpgrp(void);                 /* POSIX.1 version */
pid_t getpgrp(pid_t pid);            /* BSD version */

int setpgrp(void);                   /* System V version */
int setpgrp(pid_t pid, pid_t pgid);  /* BSD version */

会话是多个进程组的集合。使用setsid创建一个新的会话。

会话和进程组还有一些其他特性:

  • 一个会话可以有一个控制终端,它通常是终端设备或者伪终端设备。
  • 建立与终端连接会话的首进程称为控制进程。
  • 一个会话中的几个进程组可分为一个前台进程组以及多个后台进程组。
  • 如果会话有一个控制终端,则它由一个前台进程组,其他的都为后台进程组。
  • 无论何时键入终端中断键(Delete/Ctrl+C),都会中断信号发送到前台进程组所有进程。
  • 如果终端检测到网络或者连接断开,则挂断信号发送到所有控制进程。
#include 
pid_t tcgetpgrp(int fd);
int tcsetpgrp(int fd, pid_t pgrp);

#define _XOPEN_SOURCE 500        /* See feature_test_macros(7) */
#include 
pid_t tcgetsid(int fd);

信号:

#include 
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

int kill(pid_t pid, int sig); // 发送信号给pid, <0 是发送给进程组
int raise(int sig); // 发送信号给自身
unsigned int alarm(unsigned int seconds); // 发送定时器信息,由内核产生信号
int pause(void); // 进程挂起,直到收到一个信号,并且执行了信号处理函数并返回

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); 

int sigpending(sigset_t *set);
int sigsuspend(const sigset_t *mask);
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
void abort(void);

#include 
unsigned int sleep(unsigned int seconds);
int nanosleep(const struct timespec *req, struct timespec *rem);
int clock_nanosleep(clockid_t clock_id, int flags,const struct timespec *request, struct timespec *remain);

void psignal(int sig, const char *s); // print signal info
void psiginfo(const siginfo_t *pinfo, const char *s); // print signal info

extern const char *const sys_siglist[]; // 信号名称与编号的映射数组

线程

线程接口也称之为pthread或者POSIX线程

#include  // link with -pthread

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
pthread_t pthread_self(void);
int pthread_equal(pthread_t t1, pthread_t t2);
void pthread_exit(void *retval);
int pthread_join(pthread_t thread, void **retval);
int pthread_cancel(pthread_t thread); // 发送取消指令,不等待完成
void pthread_cleanup_push(void (*routine)(void *), void *arg); // 退出时清理函数
void pthread_cleanup_pop(int execute);
int pthread_detach(pthread_t thread); // 清理,之后不能使用join

// demo1 
#include 
#include 
#include 
#include 
#include 

pthread_t ntid;

void printids(const char* s){
    pid_t pid;
    pthread_t tid;
    pid = getpid();
    tid = pthread_self();
    printf("%s pid %u tid %lu (0x%lx) \n", s, (unsigned long)pid, (unsigned long)tid, (unsigned long)tid);
}

void *thr_fn(void* arg) {
    printids("new thread:");
    return (void*)NULL;
}

int main(){
    int err;
    err = pthread_create(&ntid, NULL, thr_fn, NULL);
    if( err != NULL ){
        perror("can't create thread");
    }
    printids("main thread:");
    sleep(1);
    return 0;
}

// Demo 2
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define handle_error_en(en, msg) \
    do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)

struct thread_info {    /* Used as argument to thread_start() */
    pthread_t thread_id;        /* ID returned by pthread_create() */
    int       thread_num;       /* Application-defined thread # */
    char     *argv_string;      /* From command-line argument */
};

/* Thread start function: display address near top of our stack,
  and return upper-cased copy of argv_string */

static void *thread_start(void *arg)
{
    struct thread_info *tinfo = arg;
    char *uargv, *p;
    
    printf("Thread %d: top of stack near %p; argv_string=%s\n",
           tinfo->thread_num, &p, tinfo->argv_string);
    
    uargv = strdup(tinfo->argv_string);
    if (uargv == NULL)
        handle_error("strdup");
    
    for (p = uargv; *p != '\0'; p++)
        *p = toupper(*p);
    
    return uargv;
}

int main(int argc, char *argv[])
{
    int s, tnum, opt, num_threads;
    struct thread_info *tinfo;
    pthread_attr_t attr;
    int stack_size;
    void *res;
    
    /* The "-s" option specifies a stack size for our threads */
    
    stack_size = -1;
    while ((opt = getopt(argc, argv, "s:")) != -1) {
        switch (opt) {
        case 's':
            stack_size = strtoul(optarg, NULL, 0);
            break;
            
        default:
            fprintf(stderr, "Usage: %s [-s stack-size] arg...\n",
                    argv[0]);
            exit(EXIT_FAILURE);
        }
    }
    
    num_threads = argc - optind;
    
    /* Initialize thread creation attributes */
    
    s = pthread_attr_init(&attr);
    if (s != 0)
        handle_error_en(s, "pthread_attr_init");
    
    if (stack_size > 0) {
        s = pthread_attr_setstacksize(&attr, stack_size);
        if (s != 0)
            handle_error_en(s, "pthread_attr_setstacksize");
    }
    
    /* Allocate memory for pthread_create() arguments */
    
    tinfo = calloc(num_threads, sizeof(struct thread_info));
    if (tinfo == NULL)
        handle_error("calloc");
    
    /* Create one thread for each command-line argument */
    
    for (tnum = 0; tnum < num_threads; tnum++) {
        tinfo[tnum].thread_num = tnum + 1;
        tinfo[tnum].argv_string = argv[optind + tnum];
        
        /* The pthread_create() call stores the thread ID into
          corresponding element of tinfo[] */
        
        s = pthread_create(&tinfo[tnum].thread_id, &attr,
                           &thread_start, &tinfo[tnum]);
        if (s != 0)
            handle_error_en(s, "pthread_create");
    }
    
    /* Destroy the thread attributes object, since it is no
      longer needed */
    
    s = pthread_attr_destroy(&attr);
    if (s != 0)
        handle_error_en(s, "pthread_attr_destroy");
    
    /* Now join with each thread, and display its returned value */
    
    for (tnum = 0; tnum < num_threads; tnum++) {
        s = pthread_join(tinfo[tnum].thread_id, &res);
        if (s != 0)
            handle_error_en(s, "pthread_join");
        
        printf("Joined with thread %d; returned value was %s\n",
               tinfo[tnum].thread_num, (char *) res);
        free(res);      /* Free memory allocated by thread */
    }
    
    free(tinfo);
    exit(EXIT_SUCCESS);
}

进程原语与线程原语十分相似,这里做一个对比:

进程原语 线程原语 描述
fork pthread_create 创建新的控制流
exit pthread_exit 从现有的控制流退出
waitpid pthread_join 从控制流中得到退出状态
atexit pthread_cleanup_push 注册退出控制流时的函数
getpid pthread_self 获取控制流ID
abort pthread_cancel 请求控制流的非正常退出

线程同步:

// 互斥锁
#include 

int pthread_mutex_destroy(pthread_mutex_t *mutex); // 动态分配必须destroy
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 静态分配

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

// 读写锁
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER;

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abstime);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abstime);

// 条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

// 自旋锁
// 自旋锁与互斥量类似,但是不通过休眠阻塞,而是在获取锁之前一直处于忙等(自旋)阻塞状态,它一般用于:锁持有时间短,而且线程不想再重新调度上花太多成本。
pthread_spin_init/destroy/lock/trylock/unlock

// 屏蔽
// 屏蔽barrier是用户协调多个线程并行工作的同步机制,屏蔽允许每个线程等待,直到所有线程到达某个点。
pthread_barrier_init/destroy/wait

线程中的属性:

// 线程属性
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
int pthread_attr_getguardsize(const pthread_attr_t *attr, size_t *guardsize);

// 同步属性
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr, int *pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
int pthread_mutexattr_getrobust(const pthread_mutexattr_t *attr, int *robustness);
int pthread_mutexattr_setrobust(const pthread_mutexattr_t *attr, int robustness);

int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type); // 更改锁状态

// 读写锁属性
pthread_rwlockattr_init/destory/setpshared/getpshared

// 条件变量属性
pthread_condattr_init/destory/setpshared/getpshared/setclock/getclock

// 屏蔽属性
pthread_barrierattr_init/destory/setpshared/getpshared


// 线程安全方式管理FILE对象
void flockfile(FILE *filehandle);
int ftrylockfile(FILE *filehandle);
void funlockfile(FILE *filehandle);

int getc_unlocked(FILE *stream);
int getchar_unlocked(void);
int putc_unlocked(int c, FILE *stream);
int putchar_unlocked(int c);

void clearerr_unlocked(FILE *stream);
int feof_unlocked(FILE *stream);
int ferror_unlocked(FILE *stream);
int fileno_unlocked(FILE *stream);
int fflush_unlocked(FILE *stream);
int fgetc_unlocked(FILE *stream);
int fputc_unlocked(int c, FILE *stream);
size_t fread_unlocked(void *ptr, size_t size, size_t n, FILE *stream);
size_t fwrite_unlocked(const void *ptr, size_t size, size_t n, FILE *stream);
char *fgets_unlocked(char *s, int n, FILE *stream);
int fputs_unlocked(const char *s, FILE *stream);
// wchar版本省略

线程特定数据:
默认情况下,所有的数据是共享的,我们期望没个线程改变自己特定的数据而不影响其他线程。

int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
int pthread_key_delete(pthread_key_t key);

void *pthread_getspecific(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void *value);
       
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
pthread_once_t once_control = PTHREAD_ONCE_INIT;

创建的键存储在keyp指向的内存中,这个键可以被进程中的所有线程使用,但是每个线程把这个键与不同的线程特定数据地址关联。通过specific函数设置关联内容。
如果每个线程都调用pthread_once,系统就能保证对init_routine只调用一次。

线程与信号
每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的。进程的信号是投递到单个线程的,如果一个信号与硬件故障有关,则该信号会被发送到引起该事件的线程中,而其他信号被发送到任意线程中。

int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
int sigwait(const sigset_t *set, int *sig); // 多个线程同时等待则由操作系统决定
int pthread_kill(pthread_t thread, int sig);

线程与Fork
当线程调用fork,则为子进程创建了整个进程地址空间的副本,在子进程内部只存在一个线程,它是由父进程中调用fork的线程副本构成的。
fork后的子线程与父线程共享锁等内容,为避免死锁等,子进程fork后可以立即调用exec函数中一个,这样旧的地址空间会被丢弃。
在多线程中,为避免不一致的问题,POSIX声明,在fork返回和子进程调用exec函数之间,子进程只能带哦用一部信号安全的函数。
要清除锁状态,可以通过pthread_atfork函数处理fork后程序。

线程与IO
多线程环境下,pread/pwrite函数是很有用的,因为进程公用相同的文件描述符,多线程同时读写就可以变为原子操作。

守护进程

守护进程是生存周期长的一种进程,常在系统引导时启动,系统关闭时终止,他们没有终端,只在后台运行。
ps -axj

  • 父进程ID为0的是内核进程,系统引导时启动。他们放在方括号名字中。
  • 进程ID=1通常是init,它是系统守护进程,主要负责启动特定的系统服务。
  • rpcbind是提供rpc服务,将程序号映射为网络端口号的服务。
  • inetd侦听系统网络接口,取得来自网络的对各种网络服务进程的请求。
  • nfsd,nfsiod,lockd,rpciod,rpc.idmapd,rpc.statd,rpc.mountd提供NFS文件系统支持。
  • cron是定期安排命令执行。
  • cupsd是打印假脱机服务
  • sshd是远程登陆和执行服务

创建一个守护进程:

#include 
#include 
#include 
#include 
#include 
#include 

int main(){
    int i,fd0,fd1,fd2;
    pid_t pid;
    struct rlimit r1;
    struct sigaction sa;
    // 1. clear file creation mask
    umask(0);
    // 2. Get Maximum number of file description
    getrlimit(RLIMIT_NOFILE, &rl);
    // 3. Become a seesion leader to lose tty
    if( (pid=fork()) < 0 ){
        perror("fork error");
    }else if( pid != 0 ){
        exit(0); // parent process exit
    }
    setsid();

	/* ensure future opens won't allocate controlling ttys */
	
    // 4. change current working directory
    chdir("/");

    // 5. close all opened file description
    if( rl.rlim_max == RLIM_INFINITY ){
        rl.rlim_max = 1024;
    }
    for(i=0;i<rl.rlim_max;i++){
        close(i);    
    }
    // 6. attach 0,1,2 to /dev/null
    fd0 = open("/dev/null",O_RDWR);
    fd1 = dup(0);
    fd2 = dup(0);

    // 7. init log file
    openlog("xxxx",LOG_CONS, LOG_DAEMON);

    return 0;
}

单例守护进程:
每个守护进程创建一个固定名字的文件,并且在这个文件上加锁,那么只允许创建一个这样的锁。其他进程再尝试都会失败,这就可以保证只有一个副本运行。

一些遵循的惯例:

  • 若使用锁文件,一般在/var/run/xxx.pid
  • 若支持配置文件,一般在/etc/xxx.conf.
  • 守护进程可使用命令行启动,它脚本一般在/etc/rc*或/etc/init.d/*
  • 守护进程启动时读取配置,中途配置改变时,一般不处理。一般使用SIGHUP重读配置。

系统日志:

#include 

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

高级IO

非阻塞IO、记录锁、IO多路转接(select/poll),异步IO、readv/writev、存储映射IO(mmap)。

非阻塞IO: flags = fcntl(fd,F_GETFL,0); flags |= O_NONBLOCK; fcntl(fd,F_SETFL,flags);
记录锁:当程序使用文件某部分时,阻止其他进程修改同一区域。 fcntl F_RDLCK/F_WRLCLK/F_UNLCK
IO多路转接:

  • select/pselect 告诉内核我们关心的描述符和条件,等待select返回,告诉我们哪个描述符哪个条件好了。
/* According to POSIX.1-2001, POSIX.1-2008 */
#include 

/* According to earlier standards */
#include 
#include 
#include 
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

#include 
int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);
  • poll与select类似,但是将条件聚合为数组,每个元素指明我们感兴趣的文件描述符和条件
#include 
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include 
#include 
int ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *tmo_p, const sigset_t *sigmask);

异步IO:系统不告诉我们描述符的状态,需要我们主动查询。

#include  // Link with -lrt
int aio_read(struct aiocb *aiocbp);
int aio_write(struct aiocb *aiocbp);
int aio_fsync(int op, struct aiocb *aiocbp);
int aio_error(const struct aiocb *aiocbp);
aio_return/suspend/cancel/listio

readv/writev: 散步度、聚集写

#include 
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);
ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset);
ssize_t preadv2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);
ssize_t pwritev2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);

存储映射IO:将磁盘文件映射到缓冲上,读写缓冲就相当于读写文件。

#include 
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
int mprotect(void *addr, size_t len, int prot); // 更改映射权限
int pkey_mprotect(void *addr, size_t len, int prot, int pkey);
int msync(void *addr, size_t length, int flags); // 刷洗缓冲区到文件

IPC

管道、FIFO、XSI、消息队列、共享存储、套接字
管道:
历史上是半双工的,只能在由公共父进程的之间使用。

#include 
int pipe(int pipefd[2]);

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include               /* Obtain O_* constant definitions */
#include 
int pipe2(int pipefd[2], int flags);

FILE *popen(const char *command, const char *type); // 创建一个pipe然后fork执行cmdstring
int pclose(FILE *stream);

FIFO:
命名管道,未命名的管道只能在相关进程中使用,但是FIFO可以在不相关的进程中使用。

#include 
#include 
int mkfifo(const char *pathname, mode_t mode);

XSI: (消息队列、信号量、共享存储)
msgget/msgctl/msgsnd/msgrcv/semget/semctl/semop/shmget/shmctl/shmat/shmdt/sem_open/sem_close/sem_unlink/sem_wait/set_post/sem_init/sem_getvalue/

Socket:
只有socket在进程间通信模型中可以跨计算机和网络。

#include 

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

// Name                Purpose                          Man page
// AF_UNIX, AF_LOCAL   Local communication              unix(7)
// AF_INET             IPv4 Internet protocols          ip(7)
// AF_INET6            IPv6 Internet protocols          ipv6(7)
// AF_IPX              IPX - Novell protocols
// AF_NETLINK          Kernel user interface device     netlink(7)
// AF_X25              ITU-T X.25 / ISO-8208 protocol   x25(7)
// AF_AX25             Amateur radio AX.25 protocol
// AF_ATMPVC           Access to raw ATM PVCs
// AF_APPLETALK        AppleTalk                        ddp(7)
// AF_PACKET           Low level packet interface       packet(7)
// AF_ALG              Interface to kernel crypto API

// SOCK_STREAM     Provides sequenced, reliable, two-way, connection-based byte streams.   An  out-
//                of-band data transmission mechanism may be supported.
// 
// SOCK_DGRAM      Supports  datagrams  (connectionless,  unreliable  messages  of  a fixed maximum
//                length).
// 
// SOCK_SEQPACKET  Provides a sequenced, reliable, two-way connection-based data transmission  path
//                for  datagrams of fixed maximum length; a consumer is required to read an entire
//                packet with each input system call.
// 
// SOCK_RAW        Provides raw network protocol access.
// 
// SOCK_RDM        Provides a reliable datagram layer that does not guarantee ordering.
// 
// SOCK_PACKET     Obsolete and should not be used in new programs; see packet(7).

#include 
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
int inet_pton(int af, const char *src, void *dst);

// 地址查询,主机数据库
#include 
extern int h_errno;
struct hostent *gethostbyname(const char *name);

#include        /* for AF_INET */
struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);
void sethostent(int stayopen);
void endhostent(void);
void herror(const char *s);
const char *hstrerror(int err);

//host相关函数认为过时了,使用net替代
int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
void freeaddrinfo(struct addrinfo *res);
int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags);

struct netent *getnetent(void);
struct netent *getnetbyname(const char *name);
struct netent *getnetbyaddr(uint32_t net, int type);
void setnetent(int stayopen);
void endnetent(void);

struct protoent *getprotoent(void);
struct protoent *getprotobyname(const char *name);
struct protoent *getprotobynumber(int proto);
void setprotoent(int stayopen);
void endprotoent(void);

struct servent *getservent(void);
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);
void setservent(int stayopen);
void endservent(void);

// 端口号小于1024需要sudo权限
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen); // 本机地址
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen); // 对方地址
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int s, int backlog);
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
int send(int s, const void *msg, size_t len, int flags); // 仅用于tcp
int sendto(int  s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
int sendmsg(int s, const struct msghdr *msg, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

UNIX域套接字:
UNIX域套接字用于在同一台计算机上运行的进程间通信。它比Socket效率更高。它提供流和数据报表两种接口,就像是Socket与Pipe的混合。

socket(AF_UNIX, SOCK_STREAM, 0)
int socketpair(int domain, int type, int protocol, int sv[2]);

终端IO(TTY)

termios.h

函数 说明
isatty 是否是TTY
tcgetattr 获取属性
tcsetattr 设置属性
cfgetispeed 获取输入速度(波特率)
cfgetospeed 获取输出速度(波特率)
cfsetispeed 设置输入速度
cfsetospeed 设置输出速度
tcdrain 等待所有输出都被传输
tcflow 挂起传输或者接收
tcflush 冲洗未决输入输出
tcsendbreak 发送break字符
tcgetpgrp 获取前台进程组ID
tcsetpgrp 设置前台进程组ID
tcgetsid 得到控制TTY会话进程组ID
getpass // 读入用户在终端上键入的口令
ioctl(TIOCGWINSZ) // 终端大小, TIOCSWINSZ 设置大小

伪终端
网络登陆服务器、窗口系统终端模拟、script程序、expect程序、运行协同进程…
posix_openpt/grantpt/ptsname/unlockpt
``

数据库

dbm在Unix系统中很流行,BSD扩充为ndbm。4.4BSD提供了一个新的库db。

网络打印机

IPP是网络打印机的通信规则。IPP建立在HTTP上。

你可能感兴趣的:(嵌入式)