之前学习了《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操作是所有操作中最常用的一个,一般只需要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);
#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中正常行为:
异常终止有3中方式:
一个进程可以最多登记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
创建一个新的会话。
会话和进程组还有一些其他特性:
#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
创建一个守护进程:
#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/*
系统日志:
#include
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
非阻塞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多路转接:
/* 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);
#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); // 刷洗缓冲区到文件
管道、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]);
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上。