UNIX环境高级编程笔记

错误提示

strerror

打印错误整型值对应的错误信息

1
2
#include<string.h>
char *strerror(int errnum);
  • 参数一:errnum,传入错误整型值
  • 返回值:返回错误整型值对应的错误信息

perror

根据errno值打印相应的错误信息

1
2
#include <stdio.h>
void perror(const char *msg);
  • 参数一:msg,将msg添加到错误信息中

文件操作

open与openat

openopenat函数通过参数fd进行区分

  • 情况一:参数pathname为绝对路径,参数fd可以被忽略
  • 情况二:参数pathname为相对路径,参数fd为文件描述符,此时fd描述符所在的文件夹为相对路径的起始位置
  • 情况三:参数pathname为相对路径,参数fd AT_FDCWD,此时当前目录为相对路径的起始位置,此时openopenat功能相似

openatopen的改进

  • 使得线程可以操作其他目录下的文件
  • 防止TOCTTOU漏洞
1
2
3
4
#include <fcntl.h>

int open(const char *path, int oflag, ...);
int openat(int fd,const char *path,int oflag,..)
  • path为打开文件的路径加名称
  • oflag为权限

creat

create函数用于创建只写文件

1
2
3
4
5
#include <fcntl.h>
int create(const char *path,mode_t mode);

//等价于
open(path,O_WROLY | O_CREAT | O_TRHUNK,mode);

close

关闭文件

1
2
#include <unistd.h>
int close(int fd);

lseek

搜索文件偏移,该函数执行记录了内核中记录文件偏移的数据,不需要执行I/O操作

1
2
#include <unistd.h>
off_t lseek(int fd,off_t offset,int whence);
  • fd文件描述符
  • offset偏移
  • whence为指定offset的位置
    • whence = SEEK_SET,偏移从文件起始位置计算
    • whence = SEEK_CUR,偏移从文件当前偏移开始计算
    • whence = SEEK_END,偏移从文件末尾开始计算

lseek函数的两个功能

获取当前文件的偏移

1
2
off_t currpos;
currpos = lseek(fd, 0, SEEK_CUR);

判断当前文件是否可以搜索偏移

pipeFIFO以及socket无法使用lseek函数找偏移量

1
2
3
4
5
6
7
8
9
10
11
#include <unistd.h>

int
main(int argc,char *argv[])
{
if (lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)
printf("cannot seek\n");
else
printf("seek OK\n");
exit(0);
}

read

1
2
#include <unistd.h>
int read(int fd, char *buf, unsigned nbytes);

read函数有以下五种情况读取字节少于参数指定的字节

  • 读到文件尾,返回0
  • 从终端设备读取数据,一次只返回一行
  • 从网络读取的数据大小取决于网络的缓冲区
  • 从管道或FIFO读取数据时取决于管道内的数据大小
  • 被信号所中断

write

将数据写入打开的文件里

1
2
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);

如果写入字节数超过了文件大小或者磁盘大小则会发生错误。

I/O效率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "apue.h"
#define BUFFSIZE 4096
int
main(void)
{
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");
exit(0);
}

如何选择文件输入输出的字节的大小

image-20230209225704187

buffsize为4096的效果最好

pread和pwrite

preadpwrite函数相当于先调用lseek函数后调用writeread函数,但是两个操作视作为原子操作并且当前的文件偏移不会改变

1
2
3
#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
ssize_t pwrite(int fd, void *buf, size_t nbytes, off_t offset);

dup和dup2

dupdup2函数用于复制文件描述符,并且会清空close-on-exec标志位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <unistd.h>

int dup(int oldfd);
/*
dup(oldfd)
fcntl(oldfd,F_DUPFD,0)
*/



int dup2(int oldfd,int newfd);
/*
dup2(oldfd,newfd)
close(oldfd)
fcntl(oldfd,FDUPFD,newfd)
*/
// success, these system calls return the new descriptor. On
error, -1 is returned, and errno is set appropriately

dup函数复制参数fd的描述符,返回最小未被使用的文件描述符

dup2函数将参数oldfd的描述符复制到newfd的描述符

  • oldfd是非法文件描述符,那么dup2函数调用失败,newfd不会被关闭
  • oldfdnewfd指向同一个文件描述符,则不会执行任何操作,并返回newfd
  • newfd已经被使用,那么dup2会先关闭newfd,然后再使用

sysnc、fsync和fdatasync

磁盘写入需要经过内核缓存区再写入磁盘中,该行为被称之为延迟写入。为了确保缓冲区内的数据与写入磁盘内的数据一致性,因此提供了sysncfsyncfdatasync函数

1
2
3
4
#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
void sync(void);
  • sync函数只是简单的将修改后的缓冲区数据写入队列中,并不等待磁盘的写入操作
  • fsync函数则是需要等待fd文件描述符指定的文件写入完后
  • fdatasync函数与fsync函数类似,但是只影响数据部分

fcntl

用于修改以及打开的文件属性

1
2
3
#include <fcntl.h>

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

ioctl

上述不能操作的文件都可以使用iotcl函数进行操作

1
2
3
4
5
#include <unistd.h>
#include <sys/ioctl.h>

int ioctl(int fd, int request, ...);
//返回值:若出错,返回-1;若成功,返回其他值

每个设备驱动程序可以定义它自己专用的一组ioctl命令,系统则为不同种类的设备提供通用的ioctl命令。

类别 常量名 头文件 ioctl数
盘标号 DIOxxx <sys/disklabel.h> 4
文件I/O FIOxxx <sys/filio.h> 14
磁盘I/O MTIOxxx <sys/mtio.h> 11
套接字I/O STOxxx <sys/sockio.h> 73
终端I/O TIOxxx <sys/ttycom.h> 43

文件与目录

stat函数

1
2
3
4
5
6
7
8
#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 fstatat(int fd, const char *restrict pathname,struct stat *restrict buf,int flag);

//获取成功返回0,否则返回-1或者error
  • stat函数
    • 参数一:文件路径
    • 参数二:存储stat结构的缓存区
  • fstat函数
    • 参数一:文件描述符
    • 参数二:存储stat结构的缓存区
  • lstat函数
    • 参数一:文件路径
    • 参数二:存储stat结构的缓存区
    • stat的区别是,当pathname传入的是符号链接,那么会返回符号链接的信息而不是链接文件的信息
  • fstatat函数
    • 参数一:文件描述符,若该参数使用了标志位AT_FDCWDpathname则采用相对路径
    • 参数二:pathname,如果该参数是绝对路径,则忽略fd参数
    • 参数三:存储stat结构的缓存区
    • 参数四:若该标志位设置了AT_SYMLINK_NOFOLLOWfstatat函数返回符号链接的信息,否则返回链接的原文件
  • stat结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct stat {
mode_t st_mode; /* file type & mode (permissions) 文件类型*/
ino_t st_ino; /* i-node number (serial number) 序列数字*/
dev_t st_dev; /* device number (file system) 设备号*/
dev_t st_rdev; /* device number for special files 设备号指定的文件*/
nlink_t st_nlink; /* number of links 链接号*/
uid_t st_uid; /* user ID of owner 用户ID*/
gid_t st_gid; /* group ID of owner 组ID*/
off_t st_size; /* size in bytes, for regular files 文件大小*/
struct timespec st_atim; /* time of last access 最后访问时间*/
struct timespec st_mtim; /* time of last modification 最后修改时间*/
struct timespec st_ctim; /* time of last file status change 文件状态修改时间*/
blksize_t st_blksize; /* best I/O block size */
blkcnt_t st_blocks; /* number of disk blocks allocated */
};

文件类型

  • 常规文件
  • 目录文件
  • 块特殊文件:每个块作为单位处理
  • 字节特殊文件:每个字节作为单位处理
  • FIFO:管道文件
  • Socket:网络通信文件
  • 符号链接

image-20221123143919618

image-20221123143925606

文件访问权限

image-20221123145941766

access和faccessat函数

用于判断当前进程对文件的权限

1
2
3
4
#include <unistd.h>

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

image-20221123154536642

  • access函数
    • 参数一:文件路径
    • 参数二:权限
  • faccessat函数
    • 参数一:文件描述符,该参数标志了AT_FDCWD后,pathname为相对路径
    • 参数二:文件路径,若该参数是绝对路径则fd参数忽略
    • 访问权限

umask

设置默认创建文件的权限

1
2
3
#include <sys/stat.h>

mode_t umask(mode_t cmask);

image-20221123173523830

chomd,fchmod和fchmodat

用于改变文件的访问权限

1
2
3
4
5
#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);
  • chmod函数
    • 参数一:文件路径
    • 参数二:权限
  • fchmod函数
    • 参数一:文件描述符
    • 参数二:权限
    • chmod函数区别为,该函数用于修改打开了的文件
  • fchmodat函数
    • 参数一:文件描述符,如果fd含有AT_FDCWD,那么文件路径为相对路径
    • 参数二:文件路径,如果该参数是绝对路径,则忽略fd参数
    • 参数三:权限
    • 参数四:标志位,设置AT_SYMLINK_NOFOLLOW则不返回符号链接

image-20221123174632032

chown,fchown,fchownat,和lchown

chown函数允许我们更改文件的用户ID和组ID

1
2
3
4
5
#include <unistd.h>
int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd,uid_t owner,gidd_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);
  • chown函数
    • 参数一:文件路径
    • 参数二:用户id
    • 参数三:组id
  • fchown函数
    • 参数一:文件描述符
    • 参数二:用户id
    • 参数三:组id
    • chown的区别是fchown函数用于修改已经打开了的文件,并且不能修改符号链接文件
  • fchownat函数
    • 参数一:文件描述符,若该值含有AT_FDCWD标志位,则pathname采用相对路径
    • 参数二:文件路径,若该值采用绝对路径则忽略文件描述符
    • 参数三:用户id
    • 参数四:组id
    • 参数五:标志,若该标志设置为AT_SYMLINK_NOFOLLOW则更改符号链接本身的所有者,否者更改符号链接指向的文件
  • lchown函数用于修改符号链接的文件的权限

truncate和ftruncate

用于截断文件

1
2
3
4
#include <unistd.h>

int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);

创建现有文件的链接

1
2
3
4
#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);
  • linkat函数
    • efdexistingpath指出被链接的文件,nfdnewpath为链接后的文件
    • 若使用绝对路径则忽略文件描述符
    • 若使用相对路径,则相对于文件描述符
    • 若文件描述含有AT_FDCWD标志,那么相当于当前目录
    • flag设置为AT_SYMLINK_FOLLOW则链接符号链接本身,否则指向符号链接指向的文件

解除链接

1
2
3
4
#include <unistd.h>

int unlink(const char *pathname);
int unlinkat(int fd,const char *pathname,int flag);
  • unlinkat函数
    • flag参数设置了AT_REMOVEDIR标志后,unlinkat函数用于删除目录

删除文件

1
2
3
#include <stdio.h>

int remove(const char *pathname);

rename 和 renameat

修改文件或目录名称

1
2
3
#include <stdio.h>
int rename(const char *oldname, const char *rewname);
int renameat(int oldfd,const char *oldname,int newfd,const char *newname);
1
2
3
4
#include <unistd.h>

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

readlink和readlinkat

用于打开与读取符号链接本身

1
2
3
4
#include <unistd.h>

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);

文件时间

image-20221125105002804

futimens,utimensat和utimes

用于修改文件的访问时间与修改时间

1
2
3
#include <sys/stat.h>
int futimnes(int fd, const struct timespec times[2]);
int utimensat(int fd, const char *path, const struct timespec times[2],int flag);

以秒或微妙修改文件时间

1
2
#include <sys/time.h>
int utimes(const char *pathname, const struct timeval times[2]);

mkdir,mkdirat和rmdir

mkdirmkdriat函数创建目录

1
2
3
#include <sys/stat.h>
int mkdir(const char *pathname, mode_t mode);
int mkdirt(int fd,const char *pathname, mode_t mode);

rmdir函数删除目录

1
2
#include <unistd.h>
int rmdir(const char *pathname);

目录读写操作

1
2
3
4
5
6
7
8
9
10
11
12
#include <dirent.h>
DIR *opendir(const char *pathhname);//打开指定目录
DIR *fdopendir(int fd);//将文件描述符转化为目录结构体
//两个函数返回值:若成功,返回指针;若出错,返回NULL
struct dirent *readdir(DIR *dp);
//返回值:若成功,返回指针;若在目录尾或出错,返回NULL
void rewinddir(DIR *dp);
int closedir(DIR *dp);
//返回值:若成功,返回0;若出错,返回-1
long telldir(DIR *dp);
//返回值:与dp关联的目录中的当前位置
void seekdir(DIR *dp, long loc);

dirent

1
2
ino_t d_ino;                      //i-node数字
char d_name[]; //文件名

chdir和fchdir

更改当前的工作目录

1
2
3
4
5
#include <unistd.h>

int chdir(const char *pathname);

int fchidr(int fd);

getcwd

获取当前目录的路径

1
2
#include <unistd.h>
char *getcwd(char *buf, size_t size);

标准I/O库

fwide

用于设置流的定向,流的定向绝对了流是读或写,是单字节或多字节,若流的定向已经被决定则不能修改

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <wchar.h>

int fwide(FILE *fp, int mode);
/*
返回值
正数为宽字节
负数为单字节
0为未定向
*/

Buffering

有三种缓冲类型

  • Fully buffered:全缓冲,直到缓冲区满才执行I/O操作,可以通过flush函数刷新缓冲区,执行I/O操作
  • Line buffered:直到遇到回车符才会执行I/O操作
  • Unbuffered:不缓存,直接输出

ISO C对缓冲类型的要求

  • 标准输入与标准输出是完全缓存的,除非它们与相关设备交互
  • 标准错误流是不缓存的

缓冲知识点扩展

参考链接不带缓存I/O和标准I/O(带缓存)之间的区别

printf.c

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>

int
main(void)
{
printf("This Line Should be Cached...");
sleep(3);
printf("\nThis Line Should be Cached Again");
sleep(3);
printf("This Line Should Not be Cached Agin\n");
sleep(3);
}

标准输出流默认为行缓冲输出,因此只有遇到回车符时才会将缓冲区的字符打印,因此第一条输出语句不带回车符时屏幕上不会有任何信息,而是等待回车符出现后才输出到屏幕上。

image-20230210005801166

普通情况下,使用printf函数,不带回车符,程序也会将字符串打印在屏幕上,这是因为main函数会经过exit函数退出,exit函数会刷新缓冲区,使得字符串打印在屏幕上,但是通过_Exit函数则不会打印,则不会刷新。

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <stdlib.h>

int
main(void)
{
printf("This Line Should be Cached...");
_Exit(0);
}

image-20230210010057022

当标准输出被重定向其余文件时,会从行缓冲转化为全缓存,那么只有缓冲区满或者通过fflushfclose函数函数刷新缓冲区时才会在屏幕上打印字符串。

FILE文件结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct _IO_FILE {
int _flags;
#define _IO_file_flags _flags



char* _IO_read_ptr; //当前读的偏移
char* _IO_read_end; //读缓冲区的末尾
char* _IO_read_base; //读缓冲区的起始
char* _IO_write_base; //写缓冲区的起始
char* _IO_write_ptr; //当前写的偏移
char* _IO_write_end; //写缓冲区的末尾
char* _IO_buf_base; //缓冲区的起始
char* _IO_buf_end; //缓冲区的末尾

char *_IO_save_base;
char *_IO_backup_base;
char *_IO_save_end;

struct _IO_marker *_markers;

struct _IO_FILE *_chain;

int _fileno; //FILE对应的文件描述符
};

FILE结构测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
char buf[5];
char output[5] = "abcd";
FILE *myfile = fopen("./test.txt","r+");
printf("before reading\n");
printf("read buffer base %p\n", myfile->_IO_read_base);
printf("read buffer length %d\n", myfile->_IO_read_end - myfile->_IO_read_base);
printf("write buffer base %p\n", myfile->_IO_write_base);
printf("write buffer length %d\n", myfile->_IO_write_end - myfile->_IO_write_base);
printf("buf buffer base %p\n", myfile->_IO_buf_base);
printf("buf length %d\n", myfile->_IO_buf_end - myfile->_IO_buf_base);
fgets(buf, 5, myfile);
//fputs(output, myfile);
printf("\n");
printf("after reading\n");
printf("before reading\n");
printf("read buffer base %p\n", myfile->_IO_read_base);
printf("read buffer length %d\n", myfile->_IO_read_end - myfile->_IO_read_base);
printf("write buffer base %p\n", myfile->_IO_write_base);
printf("write buffer length %d\n", myfile->_IO_write_end - myfile->_IO_write_base);
printf("buf buffer base %p\n", myfile->_IO_buf_base);
printf("buf length %d\n", myfile->_IO_buf_end - myfile->_IO_buf_base);
return 0;
}

image-20230210020351980

在读之前结构体还未初始化,只开辟了一段空间对FILE结构体进行存储

image-20230210020411853

image-20230210020520822

可以看到即使代码中写的是读取5字节,但是缓冲区中还是存在着42字节的数据,因此带缓存的I/O是一次性读取1024个字节大小的数据(_IO_buf的大小)到读缓存区中,接着从缓存区中取出5个字节到指定的变量中。

image-20230210021036552

全缓冲

image-20230210135358969

setbuf和setvbuf

用于设置是否缓存以及缓存的方式

1
2
3
#include <stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf); //用于打开与关闭缓冲
int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);

setvbuf函数可以指定缓存的方式,若mode选择_IONBF则采用无缓存模式

image-20221128145121567

打开流

fopen、freopen和fdopen

1
2
3
4
5
#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
  • fopen函数用打开指定路径的文件,与open函数的区别为open函数为系统调用而fopen函数为更高层的函数
  • freopen函数为将文件描述符与指定路径的文件联系起来
  • fdopen函数用于打开管道或者socket文件,因为常规的I/O无法打开,则可以借助fdopen函数将上述文件描述符与标准I/O联系起来

image-20221128152516263

fclose

关闭流

1
2
#include <stdio.h>
int fclose(FILE *fp)

对流进行读写

  • 每次输入/输出
  • 每次读取一行
  • 直接I/O

一次一字符输入函数

1
2
3
4
5
#include <stdio.h>

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

ferror和feof

用于判断读失败是由于到达文件末尾,还是读文件出错

1
2
3
4
5
#include <stdio.h>

int ferror(FILE *fp); //读文件错误
int feof(FILE *fp); //遇到文件末尾
void clearerr(FILE *fp); //清除错误位

ungetc

用于将字符压回流中,但是只能压入一个字符

1
2
3
#include <stdio.h>

int ungetc(int c, FILE *fp);

一次一字符输出函数

1
2
3
4
#include <stdio.h>
int puc(int c, FILE *fp);
int fputc(int c,FILE *fp);
int putchar(int c);

一次一行输入函数

1
2
3
#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf)

一次一行输出函数

1
2
3
#include <stdio.h>
int fputs(const char *restrict str, FILE *restrict fp);
int puts(const char *str);

二进制流I/O

可以每次读取/写入一个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
size_t fread(void *resrict 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);

/*
1.以类型为单位读取数组
float data[10];

if (fwrite(&data[2],sizeof(float),4,fp) != 4)
err_sys("fwrite error");


2. 读写结构体
struct{
short count;
long total;
char name[NAMESIZE];
}item;
if (fwrite(&item, sizeof(item),1,fp) != 1)
err_sys("fwrite error");
*/

ftell、fseek和rewind

1
2
3
4
5
6
7
#include <stdio.h>

long ftell(FILE *fp);//返回当前流的偏移

int fseek(FILE *fp,long offset, int whence);//设置当前流的偏移

void rewind(FILE *fp)//将流的偏移定位到开头

ftello和fseeko

1
2
3
#include <stdio.h>
off_t ftello(FILE *fp); //返回当前流的偏移
int fseeko(FILE *fp, off_t offset, int whence);//设置当前流的偏移

fgetops和fsetops

1
2
3
4
#include <stdio.h>

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

格式化的输出

1
2
3
4
5
6
7
8
9
10
#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, ...); //往指定文件描述符写
return:number of characters output if OK,negative value if output error
int sprintf(char *restrict buf, const char *restrict format, ...); //往指定数组写,并在末尾添加截断符
return:number of characters stored in array if OK,negative value if encoding error
int snprintf(char *restrict buf, size_t n, const char *restrict format, ...);//往指定数组写并规定了写入的字符数
return:number of characters that would have been stored in array if buffer was large enough,negative value if encoding error

格式化字符串的格式

image-20221202161004903

image-20221202161020838

flags

字符 描述
+ 总是表示有符号数值的’+’或’-‘号,缺省情况是忽略正数的符号。仅适用于数值类型。
空格 使得有符号数的输出如果没有正负号或者输出0个字符,则前缀1个空格。如果空格与’+’同时出现,则空格说明符被忽略。
- 左对齐。缺省情况是右对齐。
# 对于’g’与’G’,不删除尾部0以表示精度。对于’f’, ‘F’, ‘e’, ‘E’, ‘g’, ‘G’, 总是输出小数点。对于’o’, ‘x’, ‘X’, 在非0数值前分别输出前缀0, 0x, and 0X表示数制。
0 如果width选项前缀以0,则在左侧用0填充直至达到宽度要求。例如printf(“%2d”, 3)输出” 3”,而printf(“%02d”, 3)输出”03”。如果0与-均出现,则0被忽略,即左对齐依然用空格填充。

fldwidth

指定宽度

image-20221202163106776

precision

指定精度

image-20221202163122069

lenmodifier

字符 描述
hh 对于整数类型,printf期待一个从char提升的int尺寸的整型参数。
h 对于整数类型,printf期待一个从short提升的int尺寸的整型参数。
l 对于整数类型,printf期待一个long尺寸的整型参数。 对于浮点类型,printf期待一个double尺寸的整型参数。 对于字符串s类型,printf期待一个wchar_t指针参数。 对于字符c类型,printf期待一个wint_t型的参数。
ll 对于整数类型,printf期待一个long long尺寸的整型参数。Microsoft也可以使用I64
L 对于浮点类型,printf期待一个long double尺寸的整型参数。
z 对于整数类型,printf期待一个size_t尺寸的整型参数。
j 对于整数类型,printf期待一个intmax_t尺寸的整型参数。
t 对于整数类型,printf期待一个ptrdiff_t尺寸的整型参数。

convtype

转换类型,必选字段

字符 描述
d,i 有符号十进制数值int。’%d’与’%i’对于输出是同义;但对于scanf()输入二者不同,其中%i在输入值有前缀0x或0时,分别表示16进制或8进制的值。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
u 十进制unsigned int。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
f,F double型输出10进制定点表示。’f’与’F’差异是表示无穷与NaN时,’f’输出’inf’, ‘infinity’与’nan’;’F’输出’INF’, ‘INFINITY’与’NAN’。小数点后的数字位数等于精度,最后一位数字四舍五入。精度默认为6。如果精度为0且没有#标记,则不出现小数点。小数点左侧至少一位数字。
e,E double值,输出形式为10进制的([-]d.ddd e[+/-]ddd). E版本使用的指数符号为E(而不是e)。指数部分至少包含2位数字,如果值为0,则指数部分为00。Windows系统,指数部分至少为3位数字,例如1.5e002,也可用Microsoft版的运行时函数_set_output_format 修改。小数点前存在1位数字。小数点后的数字位数等于精度。精度默认为6。如果精度为0且没有#标记,则不出现小数点。
g,G double型数值,精度定义为全部有效数字位数。当指数部分在闭区间 [-4,精度] 内,输出为定点形式;否则输出为指数浮点形式。’g’使用小写字母,’G’使用大写字母。小数点右侧的尾数0不被显示;显示小数点仅当输出的小数部分不为0。
x,X 16进制unsigned int。’x’使用小写字母;’X’使用大写字母。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
o 8进制unsigned int。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
s 如果没有用l标志,输出null结尾字符串直到精度规定的上限;如果没有指定精度,则输出所有字节。如果用了l标志,则对应函数参数指向wchar_t型的数组,输出时把每个宽字符转化为多字节字符,相当于调用wcrtomb函数。
c 如果没有用l标志,把int参数转为unsigned char型输出;如果用了l标志,把wint_t参数转为包含两个元素的wchart_t数组,其中第一个元素包含要输出的字符,第二个元素为null宽字符。
p void *型
a,A double型的16进制表示,”[−]0xh.hhhh p±d”。其中指数部分为10进制表示的形式。例如:1025.010输出为0x1.004000p+10。’a’使用小写字母,’A’使用大写字母。(C++11流使用hexfloat输出16进制浮点数)
n 不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。
% ‘%’字面值,不接受任何flags, width, precision or length。

printf家族变体

用于自定义printf函数的时候使用

1
2
3
4
5
6
7
#include <stdarg.h>
#include <stdio.h>

int vprintf(const char *restrict format, va_llist arg);
int vfprintf(FILE *restrict fp, const char *restrict format, va_list arg);
int vdprintf(int fd, const char *restrict buf,const char *restrict format, va_list arg);
int vsnprintf(char *restrict buf, size_t n,const char *restrict format, va_list arg);

格式化输入

1
2
3
4
5
6
#include <stdio.h>

int scanf(const char *restrict format, ...);
int fscanf(FILE *restrict fp, const char *restrict format, ...);
int sscanf(const char r*restrict buf, const char *restrict format, ...);
//return:返回输入字符数

image-20221202165922105

*:用于跳过当前的输入

fldwidth:指定字段宽度

lenmodifier:字段大小

convtyoe:转换类型

image-20221202170746161

scanf家族变体

1
2
3
4
5
6
#include <stdarg.h>
#include <stdio.h>

int vscanf(const char *restrict format, va_list arg);
int vfscanf(FILE *restrict fp, const char *restrict format, va_list arg);
int vsscanf(const char *restrict buf, const char *restrict format, va_list arg);

fileno

1
2
3
#include <stdio.h>
int fileno(FILE *fp);
return:流相关联文件描述符

tmpname和tempfile

1
2
3
4
#include <stdio.h>

char *tmpname(char *ptr); //返回临时文件的路径,若ptr不为空,则将文件路径存储到ptr指向的空间中
FILE *tmpfile(void); //返回文件的指针

mkdtemp和mkstemp

1
2
3
#include <stdlib.h>
char *mkdtemp(char *template); //用于创建唯一名称的目录,返回临时文件的目录名称
int mkstemp(char *template); //用于创建唯一名称的常规文件,文件描述符,使用该函数创建的临时文件不会自动删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include <errno.h>
#include <sys/stat.h>
#include <stdlib.h>

void make_temp(char *template);

int
main()
{
char good_template[] = "/tmp/dirXXXXXX"; //栈上存放的是字符串本身的值
char *bad_template = "/tmp/dirXXXXXX"; //栈上存放的是指针值

printf("tring to create first temp file...\n");
make_temp(good_template);
printf("trying to create second temp file...\n");
make_temp(bad_template);
exit(0);
}

void
make_temp(char *template)
{
int fd;
struct stat sbuf;

if ((fd = mkstemp(template)) < 0)
fprintf(stderr,"%s","can't create temp file");
printf("temp name = %s\n",template);
close(fd);
if (stat(template,&sbuf) < 0){
if(errno == ENOENT)
printf("file doesn't exit\n");
else
printf("stat failed");
} else{
printf("file exists\n");
unlink(template);
}
}

内存流

用于创建内存流

1
2
#include <stdio.h>
FILE *fmemopen(void *restrict buf, size_t size, const char *restrict type);

用于创建内存流

1
2
3
4
5
#include <stdio.h>
FILE *open_memstream(char **bufp, size_t *sizep);
#include <wchar.h>
FILE *open_wmemstream(wchar_t **bufp, size_t *sizep);

密码文件

image-20221205153518991

每个成员使用冒号隔开

image-20221205153647054

getpwuid和getpwname

通过用户id与用户名获取密码

1
2
3
4
#include <pwd.h>

struct passwd *getpwuid(uid_t uid);
struct passwd *getpwnam(const char *name);

用于遍历整个密码文件

1
2
3
4
5
6
#include <pwd.h>

struct passwd *getpwent(void); //调用getpwent返回密码文件中的下一个条目

void setpwent(void); //将文件设置到开头位置
void endpwent(void); //用于关闭文件

6-2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <pwd.h>
#include <stddef.h>
#include <string.h>

struct passwd *
getpwname(const char *name)
{
struct passwd *ptr;
setpwent();
while((ptr = getpwent()) != NULL)
if (strcmp(name, ptr->pw_name) == 0)
break;
endpwent();
return(ptr);
}

影子密码文件

image-20221205164222088

用于获取影子密码文件的函数

1
2
3
4
5
6
7
#include <shadow.h>

struct spwd *getspname(const char *name);
struct spwd *getspent(void);

void setspent(void);
void endspent(void);

组文件

image-20221205164801102

1
2
3
#include <grp.h>
struct group *getgrgid(gid_t gid);
struct group *getgrname(const char *name);

用于遍历整个组文件

1
2
3
4
#include <grp.h>
struct group *getgrent(void);
void setgrent(void);
void endgrent(void);

补充组ID

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <unistd.h>

int getgroups(int gidsetsize, gid_t grouplist[]);//获取目前用户所属的组ID

#include <grp.h>
#include <unistd.h>

int setgroups(int ngroups, const gid_t grouplist[]);
//设置当前用户的补充组ID
#include <grp.h>
#include <unistd.h>

int initgroups(const char *username, gid_t basegid);//从组文件(/etc/group)中读取一项数据,若该组数据的成员中有参数user时,便将参数group组识别码加入到此数据中。

其他系统文件

image-20221207165325358

登录记录

utmp用于跟踪过当前登录的所有用户

image-20221207165629576

系统识别

获取当前主机与操作系统的信息

1
2
3
#include <sys/utsname.h>

int uname(struct utsname *name)

image-20221207170044044

获取TCP/IP网络上的主机名称

1
2
3
#include <unistd.h>

int gethosstname(char *name,int namelen);

时间与日期

获取当前时间和日期

1
2
3
#include <time.h>

time_t time(time_t *calptr);

image-20221207171327943

用于获取指定时钟的时间

1
2
3
#include <sys/time.h>

int clock_gettime(clockid_t clock_id, struct timespec *tsp);

用于获取指定精度时间

1
2
3
#include <sys/time.h>

int clock_getres(clockid_t clock_id, struct timespec *tsp);

用于设置特定的时间

1
2
3
#include <sys/time.h>

int clock_settime(clockid_t clock_id, const struct timespec *tsp);

time函数更高的精度获取时间

1
2
#include <sys/time.h>
int gettimeofday(struct timeval *restrict tp, void *restrict tzp);

时间之间的关系

image-20221207172705110

image-20221207172748969

时间转化

1
2
3
4
#include <time.h>

struct tm *gmtime(const time_t *calptr);//将日历时间转化为本地时间
struct tm*localtime(const time_t *calptr)//将日历时间转化为UTC表示的分解时间

函数mktime获取一个表示为本地时间的分解时间,并将其转换为time_t值

1
2
3
#include <time.h>

time_t mktime(struct tm*tmptr);

以格式化字符串的形式转换时间

1
2
3
4
5
#include <time.h>

size_t strftime(charr *restrict buf,size_t maxsize,
const char *restrict format,const struct tm*restrict tmptr);
size_t strftime_l(char *restrict buf,size_t maxsize,const char *restrict format,const struct tm*restrict tmptr, locale_t locale);

image-20221207173819203

将字符串转化为中断时间

1
2
3
#include <time.h>

char *strptime(const char *restrict buf, const char *restrict format, struct tm *restrct tmptr);

image-20221207174854679

Exit函数

exit、_Exit和_exit函数

_exit_Exit会马上返回内核,而exit会执行清理过程再返回内核

1
2
3
4
5
6
7
8
9
#include <stdlib.h>

void exit(int status);

void _Exit(int status);

#include <unistd.h>

void _exit(int status);

atexit

一个进程可以设置至少32个退出时自动调用的函数,通过atexit函数可以进行注册,调用顺序与执行顺序相反

1
2
3
#include <stdlib.h>

int atexxit(void (*func)(void));

image-20221212233431839

内存分配

malloc、calloc和realloc函数

1
2
3
4
5
#include <stdlib.h>

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

环境变量

环境变量的形式为name = value

getenv

1
2
3
4
#include <stdlib.h>

char *getenv(const char *name);
Returns value;

putenv、setenv和unsetenv

1
2
3
4
5
#include <stdlib.h>
int putenv(char *str);//接受name=value的字符串形式,若存在则删除原有的

int setenv(const char *name, const char *value,int rewrite);
int unsetenv(const char *name);//删除任何名称的定义

setjmp和longjmp

1
2
3
4
#include <setjmp.h>

int setjmp(jmp_buf env);
void longjmp(jmp_buf env,int cal);

getrlimit和setrlimit

通过这两个函数可以查询和设置资源限制

1
2
3
4
#include <sys/resource.h>

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

获取进程id

1
2
3
4
5
6
7
8
#include <unistd.h>

pid_t getpid(void); //返回调用进程号
pid_t getppid(void); //返回调用进程的父进程
uid_t getuid(void); //返回调用进程的真实uid
uid_t geteuid(void); //返回调用进程的有效uid
gid_t getgid(void); //返回调用进程的真实组id
gid_t getegid(void); //返回调用进程的有效组id

fork函数

现有进程可以通过调用fork函数来创建新进程。

1
2
3
4
5
#include <unistd.h>

pid_t fork(void);
//返回0为子进程
//返回非0则为父进程,返回值为子进程号

wait和waitpid

等待子进程完成执行

1
2
3
4
5
#include <sys/wait.h>

pid_t wait(int *statloc);
pid_t waitpid(pid_t pid,int *statloc, int options);
//成功返回子进程id
描述
WIFEXITED(status) 如果子进程正常结束则返回True
WIFSIGNALED(status) 如果子进程异常结束则返回True,通过WTERMSIG(status)可以获取信号的序号,通过WCOREDUMP(status)可以判断是否生成core文件
WIFSTOPPED(status) 如果子进程被停止返回True。通过WSTOPSIG(status)可以获取导致子进程停止的信号序号
WIFCONTINUED(status) 如果在作业控制停止后继续的子进程返回True

waitid函数

1
2
#include <sys/wait.h>
int waitid(idtype_t idtype, id_t id, siginfo_t *infop,int options);

类型

  • P_PID:等待特定的进程id,id用于标识特定的进程id
  • P_GID:等待特定的进程组id,id用于标识特定的组id
  • P_ALL:等待热河子进程,id可以被忽略

选项

  • WCONTINUED:等待先前已停止并已继续且其状态尚未报告的进程
  • WEXITED:等待已退出的进程
  • WNOHANG:如果没有可用的子退出状态,则立即返回而不是阻止
  • WSTOPPED:等待已停止且状态尚未报告的进程

wait3和wait4

1
2
3
4
5
6
7
f
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>

pid_t wait3(int *statloc,int options, struct rusage *resage);
pid_t wait4(pid_t pid,int *statloc,int options, struct rusage *rusage);

exec函数

exec用磁盘上的一个新程序替换了当前进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <unistd.h>
/*
exec为执行进程的函数
l代表参数列表,使用空指针作为末尾
v代表参数向量
e代表环境变量
p代表文件名
*/
int execl(const char *pathname, const char *arg0, ...);
int execv(const char *pathname,char *const argv[]);
int execle(const char *pathname, const char *arg0,...);
int execve(const char r*pathname,char *const argv[],char *const envp[]);
int execlp(const char *filename,const char*arg0,...);
int execvp(const char *filename, char *const argv[]);
int fexecve(int fd,char *consst argv[],char *const envp[]);

system函数

1
2
3
#include <stdlib.h>

int system(const char *cmdstring);

用户识别

1
2
#include <unistd.h>
char *getlogin(void);

进程调度

获取调度优先级

1
2
#include <unistd.h>
int nice(int incr);

获取一组进程的调度优先级

1
2
#include <sys/resource.h>
int getpriority(int which, id_t who);

设置进程的调用优先级

1
2
#include <sys/resource.h>
int setpriority(int which, id_t who, int value);

进程时间

1
2
#include <sys/times.h>
clock_t times(struct tms *buf);

进程组

获取进程组id

1
2
3
#include <unistd.h>

pid_t getpgrp(void); //返回调用进程的进程组id
1
2
3
#include <unistd.h>

pid_t getpgid(pid_t pid); //pid为0则代表当前进程

加入或创建组

1
2
3
#include <unistd.h>

int setpgid(pid_t pid,pid_t pgid);

会话

setsid

建立会话

1
2
3
#include <unistd.h>

pid_t setsid(void); //返回进程组ID

getsid

获取会话id

1
2
#include <unistd.h>
pid_t getsid(pid_t pid); //返回会话领导的进程组ID

tcgetpgrp,tcsetpgrp and tcgetsid

获取前台进程

1
2
3
#include <unistd.h>
pid_t tcgetpgrp(int fd); //返回与打开终端返回的fd描述符相关的前台进程组
int tcsetpgrp(int fd,pid_t pgrpid);//设置控制终端的进程组ID

通过TTY文件获取会话负责人的进程组ID

1
2
#include <termios.h>
pid_t tcgetsid(int fd); //返回会话领导人的进程组ID

信号

信号处理函数

1
2
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
  • 参数func
    • SIG_IGN忽略信号
    • SIG_DFL默认值操作
    • 函数地址,自定义操作

kill和raise

kill函数用于给进程与进程组发送信号

1
2
3
4
5
#include <signal.h>
int kill(pid_t pid,int signo);
#raise = kill(getpid(),signo);
int raise(int signo);
//若成功,返回0; 若出错,返回-1
  • pid > 0,发送的进程IDpid
  • pid == 0,向该进程组所有进程发送信号
  • pid < 0,向指定的进程组发送信号
  • pid == -1,向系统所有进程发送信号

alarm和pause

alarm允许设置一个计时器,当计时器到期时,生成SIGALRM信号。默认操作是终止进程。

1
2
#include <unistd.h>
unsigned int alarm(unsigned int seconds);

pause函数暂停调用过程,直到捕捉到信号。

1
2
3
#include <unistd.h>

int pause(void);

信号设置

1
2
3
4
5
6
7
#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);//删除单个信号
return 0:if OK, -1 on error
int sigismember(conset sigset_t *set,int signo);//判断signo信号是否被设置,是返回1,否返回0

sigprocmask

用于修改信号掩码

1
2
3
#include <signal.h>

int sigprocmask(int how,const sigset_t *restrict set, sigset_t *restrict oset);
  • oset用于存储当前进程的信号掩码
  • set若不为空,则how参数指示当前信号掩码如何修改
  • how参数选项如下
    • SIG_BLOCKset指向需要阻塞的信号
    • SIG_UNBLOCKset指向需要解除的信号
    • SIG_SETMASK:新信号的掩码被set替换

sigpending

sigpending函数用于返回被阻塞的信号或者等待被调用的信号集,返回的信号集存放在set

1
2
3
#include <signal.h>
int sigpending(sigset_t *set);
Ruturns:0 if OK, -1 on error

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

static void sig_quit(int);

int
main(void)
{
sigset_t newmask, oldmask, pendmask;
if (signal(SIGQUIT, sig_quit) == SIG_ERR) //注册信号
{
fprintf(stderr,"can't catch SIGQUIT");
exit(-1);
}
sigemptyset(&newmask); //清空信号集
sigaddset(&newmask, SIGQUIT); //增加SIGQUIT信号
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) //阻止newmask中的信号集,当前进程的信号集存放在oldmask中
{
fprintf(stderr,"SIG_BLOCK error");
exit(-1);
}
sleep(5);
if (sigpending(&pendmask) < 0) //捕获被挂起的信号集,得到的信号集存放在pendmask中
{
fprintf(stderr,"sigpending error");
exit(-1);
}
if (sigismember(&pendmask, SIGQUIT)) //确定SIGQUIT在pendmask中
printf("\nSIGQUIT pending\n");
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) //将信号集还原为oldmask
{
fprintf(stderr,"SIG_SETMASK error");
exit(-1);
}
printf("SIGQUIT unblocked\n");
sleep(5);
exit(0);
}
static void
sig_quit(int signo)
{
printf("caught SIGQUIT\n");
if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
{
fprintf(stderr,"can't reset SIGQUIT");
exit(-1);
}
}

sigaction

用于检查或修改特定信号相关动作

1
2
3
#include <signal.h>
int sigaction(int signo, const struct sigaction *restrict act,struct sigaction *restrict oact);
Retruns:0 if OK, -1 on error
  • signo为正在检查或修改动作的信号
  • act为修改的动作
  • oact为原先的动作
1
2
3
4
5
6
struct sigaction{
void (*sa_handler)(int); //信号处理函数
sigset_t sa_mask; //需要处理的信号集
int sa_flags;
void (*sa_sigaction)(int, siginfo_t *, void *);
};
1
2
3
4
5
6
7
8
9
10
11
struct siginfo {
int si_signo; /* signal number */
int si_errno; /* if nonzero, errno value from errno.h */
int si_code; /* additional info (depends on signal) */
pid_t si_pid; /* sending process ID */
uid_t si_uid; /* sending process real user ID */
void *si_addr; /* address that caused the fault */
int si_status; /* exit value or signal number */
union sigval si_value; /* application-specific value */
/* possibly other fields also */
};
1
2
3
4
5
6
7
ucontext_t *uc_link; /* pointer to context resumed when */
/* this context returns */
sigset_t uc_sigmask; /* signals blocked when this context */
/* is active */
stack_t uc_stack; /* stack used by this context */
mcontext_t uc_mcontext; /* machine-specific representation of */
/* saved context */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <signal.h>

Sigfunc *
signal(int signo, Sigfunc *func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (signo == SIGALRM){
#ifdef SA_INTERRUPT
act.ssa_flags |= SA_IINTERRUPT;
#endif
}else{
act.sa_flags |= SA_RESTART;
}
if (sigaction(signo,&act, &ocat) < 0)
return(SIG_ERR);
retuurn(ocat.sa_handler);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <singal.h>

Sigfunc *
signal_intr(int signo, Sigfunc *func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT;
#endif
if (sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}

sigsetjmp和siglongjmp

1
2
3
4
#include <setjmp.h>
int sigsetjmp(sigjmp_buf env,int savemask);
Returns:0 if called directly,nonzero if returning from a call to siglong jmp
void siglongjmp(sigjmp_buf env,int val);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <stdio.h>
#include <setjmp.h>
#include <time.h>
#include <stdlib.h>
#include <signal.h>

static void sig_usr1(int);
static void sig_alrm(int);
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjump;

int
main(void)
{
if (signal(SIGUSR1, sig_usr1) == SIG_ERR)
{
fprintf(stderr,"signal(SIGUSR1) error");
exit(-1);
}
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
{
fprintf(stderr,"signal(SIGALRM) error");
exit(-1);
}

printf("starting main: ");
if (sigsetjmp(jmpbuf,1)){
printf("ending main: ");
exit(0);
}
canjump = 1;
for ( ; ; )
pause();
}

static void
sig_usr1(int signo)
{
time_t starttime;
if (canjump == 0)
return;
printf("starting sig_usr1: ");
alarm(3);
starttime = time(NULL);
for( ; ; )
if (time(NULL) > starttime + 5)
break;
printf("finishing sig_ussr1: ");
canjump = 0;
siglongjmp(jmpbuf, 1);
}

static void
sig_alrm(int signo)
{
printf("in sig_alrm: ");
}

sigsuspend

恢复信号掩码

1
2
3
#include <signal.h>
int sigsuspend(const sigset_t *sigmask);
Returns:-1 with errno set to EINTR
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>

volatile sig_atomic_t quitflag;

static void
sig_int(int signo)
{
if (signo == SIGINT)
printf("\ninterrupt\n");
else if (signo == SIGQUIT)
quitflag = 1;
}

int
main(void)
{
sigset_t newmask, oldmask, zeromask;
if (signal(SIGINT, sig_int) == SIG_ERR)
{
fprintf(stderr,"signal(SIGINT) error");
exit(-1);
}
if (signal(SIGQUIT, sig_int) == SIG_ERR)
{
fprintf(stderr,"signal(SIGQUIT) error");
exit(-1);
}
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGQUIT);
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) //此时阻塞SIGINT信号
{
fprintf(stderr,"SIG_BLOCK error");
exit(-1);
}
while (quitflag == 0)
sigsuspend(&zeromask);//此时阻塞zeromask里的信号,并挂起进程,等待信号到来唤醒进程
quitflag = 0;
if (sigprocmask(SIG_SETMASK, &oldmask, NULL))
{
fprintf(stderr,"SIG_SETMASK error");
exit(-1);
}
exit(0);
}

abort

终止函数会导致程序异常退出

1
2
#include <stdlib.h>
void abort(void);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void
abort(void)
{
sigset_t mask;
struct sigaction action;
sigaction(SIGABRT,NULL,&action);
if(action.sa_handler == SIG_IGN){
action.sa_handler = SIG_DFL;
sigaction(SIGABRT,&action,NULL);
}
if (action.sa_handler == SIG_DFL)
fflush(NULL);
sigfillset(&mask);
sigdelset(&mask,SIGABRT);
sigprocmask(SIG_SETMASK,&mask,NULL);
kill(getpid(),SIGABRT);

fflush(NULL);
action.sa_handler = SIG_DFL;
sigaction(SIGABRT, &action, NULL);
sigprocmask(SIG_SETMASK, &mask, NULL);
kill(getpid(), SIGABRT);
exit(1);
}

sleep,nanosleep和clock_nanosleep

暂停当前进程

1
2
3
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
Returns:0 or number of unslept seconds
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>


static void
sig_alrm(int signo)
{

}

unsigned int
sleep(unsigned int seconds)
{
struct sigaction newact, oldact;
sigset_t newmask, oldmask, suspmask;
unsigned int unslept;

newcat.sa_handler = sig_alrm;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGALRM, &newact, &oldact);

sigemptyset(&newmask);
sigaddset(&newmask, SIGALRM);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
alarm(seconds);
suspmask = oldmask;
sigdelset(&suspmask, SIGALRM);
sigsuspend(&suspmask);
unslept = alarm(0);
sigaction(SIGALRM, &oldact, NULL);
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return(unslept);

}

纳秒级别休眠

1
2
3
#include <time.h>
int nanosleep(const struct timespec *reqtp, struct timespec *remtp);
Returns: 0 if slept for requested time or -1 on error
  • reqtp为休眠时间
  • remtp为剩余的休眠时间

指定时间类型的休眠函数

1
2
3
#include <time.h>
int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *reqtp, struct timespec *remtp);
Returns: 0 if slept for requeested time or error number on failure
  • clock_id用于指定计算时间延迟所依据的时钟。
  • flags用于标记是绝对延迟还是相对延迟
    • 相对时间:想要休眠多长时间,0为相对时间
    • 绝对时间:休眠到哪个时间点

sigqueue

用于排序的信号

1
2
3
#include <signal.h>
int sigqueue(pid_t pid,int signo, const union sigval value)
Returns: 0 if OK, -1 on error

使用排队信号的条件

  • 使用sigaction函数时,需要使用SA_SIGINFO标志
  • sigaction结构需要使用sa_sigation成员中提供信号处理程序
  • 使用sigqueue函数发送信号

psignal和psiginfo

打印信号对应的字符串

1
2
3
#include <signal.h>
void psignal(int signo, const char *msg);
void psiginfo(const siginfo_t *info,const char *msg)

返回信号对应的字符串,不打印

1
2
#include <string.h>
char *strsignal(int signo);

将信号与字符串进行映射

1
2
3
#include <signal.h>
int sig2str(int signo, char *str);
int str2sig(const char *str, int *signop);

线程

pthread_equal

比较线程id

1
2
3
4
#include <pthread.h>

int pthread_equal(pthread_t tid1, pthread_t tid2);
//返回,0则相等,不为0则不相等

pthread_self

获取线程id

1
2
3
#include <pthread.h>

pthread_t pthread_self(void);

pthread_create

线程创建

1
2
3
4
5
6
#include <pthread.h>

int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(*start_rtn)(void *),
void *restrict arg);
  • tidp被分配的线程ID
  • attr自定义各种线程属性
  • start_trn,函数地址
  • arg运行的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

pthread_t ntid;

void
printids(const char *s)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid %lu 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 *)0);
}

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

pthread_exit

线程退出函数

1
2
3
#include <pthread.h>

void pthread_exit(void *rval_ptr)
  • rval_ptr包含返回代码

pthread_join

用于等待线程的结束

1
2
3
#include <pthread.h>

int pthread_join(pthread_t thread, void **rval_ptr)
  • thread等待的线程id
  • rval_ptr用于存储线程的返回值

pthread_cancel

取消线程

1
2
#include <pthread.h>
int pthread_cancel(pthread_t tid);

pthread_cleanup_push和pthread_cleanup_pop

线程的清理处理函数

1
2
3
#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *),void *arg);
void pthread_cleanup_pop(int execute);//execute = 0则不清理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void
cleanup(void *arg)
{
printf("cleanup: %s\n", (char *)arg);
}
void *
thr_fn1(void *arg)
{
printf("thread 1 start\n");
pthread_cleanup_push(cleanup,"thread 1 first handler");
pthread_cleanup_push(cleanup,"thread 1 second handler");
printf("thread 1 push complete\n");
if (arg)
return ((void *)1);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
return((void *)1);
}

void *
thr_fn2(void *arg)
{
printf("thread 2 start\n");
pthread_cleanup_push(cleanup,"thread 2 first handler");
pthread_cleanup_push(cleanup,"thread 2 second handler");
printf("thread 2 push complete\n");
if (arg)
pthread_exit((void *)2);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
pthread_exit((void *)2);
}

int
main(void)
{
int err;
pthread_t tid1, tid2;
void *tret;

err = pthread_create(&tid1, NULL, thr_fn1,(void *)1);
if (err != 0)
{
fprintf(stderr,"can't create thread 1");
exit(-1);
}
err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);
if (err != 0)
{
fprintf(stderr,"can't create thread 2");
exit(-1);
}
err = pthread_join(tid1, &tret);
if (err != 0)
{
fprintf(stderr,"can't join thread 1");
exit(-1);
}
err = pthread_join(tid2,&tret);
if (err != 0)
{
fprintf(stderr,"can't join thread 2");
exit(-1);
}
exit(0);
}

pthread_detach

分离线程

1
2
#include <pthread.h>
int pthread_detach(pthread_t tid);

线程互斥锁

pthread_mutex_init和pthread_mutex_destroy

1
2
3
4
5
#include <pthread.h>

int pthread_mutext_init(pthread_mutex_t *restrict mutex,const pthreaad_mutexattr_t *retrict attr); //初始化互斥变量
int pthread_mutext_destroy(pthread_mutex_t *mutex);//销毁互斥变量
Both return: 0 if OKk, error number on failure
  • mutex:互斥变量
  • attr:互斥属性

锁定互斥锁

1
2
3
4
5
#include <pthread.h>
int pthread_mutex_loc(pthread_mutex_t *mutex); //锁定互斥锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);//有条件地锁定互斥锁
int pthread_mutex_unlock(pthread_mutex_t *mutex); //解除互斥锁
All return:0 if OK, error number on failure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

struct foo{
int f_count;
pthread_mutex_t f_lock;
int f_id;
};

struct foo *
foo_alloc(int id)
{
struct foo *fp;
if ((fp = malloc(sizeof(struct foo))) != NULL){
fp->f_count = 1;
fp->f_id = id;
if (pthread_mutex_init(&foo->f_lock,NULL) != 0){
free(fp);
return(NULL);
}
}
return(fp);
}

void
foo_hold(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
fp->f_count++;
pthread_mutex_unlock(&fp->f_lock);
}

void
foo_rele(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
if (--fp->f_count == 0){
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_destroy(&fp->f_lock);
free(fp);
fp = NULL;
} else {
pthread_mutex_unlock(&fp->f_lock);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <stdlib.h>
#include <pthread.h>

#define NHASH 29
#define HASH(id) ((((unsigned long)id)%NHASH) //生成HASH值

struct foo *fh[NHASH];

pthread_mutext_t hashlock = PTHREAD_MUTEX_INITALIZER; //初始化互斥锁

struct foo {
int f_count;
pthread_mutext_t f_lock;
int f_id;
struct foo *f_next;
};

struct foo *
foo_alloc(int id)
{
struct foo *fp;
int idx;

if ((fp = malloc(sizeof(struct foo))) != NULL){
fp->f_count = 1;
fp->f_id = id;
if (pthread_mutext_init(&fp->f_lock, NULL) != 0){
free(fp);
return(NULL);
}
idx = HASH(id);
pthread_mutex_lock(&hashlock);
fp->f_next = fh[idx];
fh[idx] = fp; //头插入法
pthread_mutext_lock(&fp->f_lock);
pthread_mutext_unlock(&hashlock);
pthread_mutext_unlock(&fp->f_lock);
}
return(fp);
}

void
foo_hold(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
fp->f_count++;
pthread_mutext_unlock(&fp->f_lock);
}

struct foo *
foo_find(int id)
{
struct foo *fp;
pthread_mutex_lock(&hashlock);
for (fp = fh[HASH(id)]; fp != NULL; fp = fp->f_next){
if (fp->f_id == id){
foo_hold(fp);
break;
}
}
pthread_mutext_unlock(&hashlock);
return(fp);
}

void
foo_rele(struct foo *fp)
{
struct foo *tfp;
int idx;

pthread_mutex_lock(&fp->f_lock);
if (fp->f_count == 1){
pthread_mutext_unlock(&fp->f_lock);
pthread_mutex_lock(&hashlock);
pthread_mutext_lock(&fp->f_lock);
if (fp->f_count != 1){
fp->f_count--;
pthread_mutext_unlock(&fp->f_lock);
pthread_mutext_unlock(&hashlock);
return;
}
idx = HASH(fp->f_id);
tfp = fh[idx];
if (tfp == fp){
fh[idx] = fp->f_next;
} else{
while (tfp->f_next != fp)
tfp = tfp->f_next;
tfp->f_next = fp->f_next;
}
pthread_mutext_unlock(&hashlock);
pthread_mutext_unlock(&fp->f_lock);
pthread_mutext_destroy(&fp->f_lock);
free(fp);
} else{
fp->f_count--;
pthread_mutext_unlock(&fp->f_lock);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <stdlib.h>
#include <pthread.h>

#define NHASH 29
#define HASH(id) (((unsigned long)id) %NHASH))

struct foo *fh[NHASH];
pthread_mutext_t hashlock = PTHREAD_MUTEX_INITALIZER;
struct foo {
int f_count;
pthread_mutext_t f_lock;
int f_id;
struct foo *f_next;
};

struct foo *
foo_alloc(int id)
{
struct foo *fp;
int idx;

if ((fp = malloc(sizeof(struct foo))) != NULL){
fp->f_count = 1;
fp->f_id = id;
if (pthread_mutext_init(&fp-f_lock, NULL) != 0){
free(fp);
return(NULL);
}
idx = HASH(id);
pthread_mutex_lock(&hashlock);
fp->f_next = fh[idx];
fh[idx] = fp;
pthread_mutext_lock(&fp->f_lock);
pthread_mutext_unlock(&hashlock);
pthread_mutext_unlock(&fp->f_lock);
}
return(fp);
}

void
foo_rele(struct foo *fp)
{
struct foo *tfp;
int idx;

pthread_mutex_lock(&hashlock);
if (--fp->f_count == 0){
idx = HASH(fp->f_id);
tfp = fh[idx];
if (tfp == fp){
fh[idx] = fp->f_next;
} else {
while (tfp->f_next != fp)
tfp = tfp->f_next;
tfp->f_next = fp->f_next;
}
pthread_mutext_unlock(&hashlock);
pthread_mutext_destroy(&fp->f_lock);
free(fp);
} else {
pthread_mutext_unlock(&hashlock);
}
}

pthread_mutex_tiimedlock

等待指定时间在加锁

1
2
3
4
5
#include <pthread.h>
#include <time.h>

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict tsptr);
Returns:0 if OK, error number on failure

读写器锁

初始化读写器锁

1
2
3
4
5
6
#include <pthread.h>

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
Both return: 0 if OK, error number on failure

锁读写器锁

1
2
3
4
5
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); //用于读
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); //用于写
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); //解锁
All return: 0 if OK, error number on failure

读写器锁定原语的条件版本

1
2
3
4
#include <pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
Both return: 0 if OK, error number on failure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <stdlib.h>
#include <pthread.h>

struct job {
struct job *j_next;
struct job *j_prev;
pthread_t j_id;
};

struct queue{
struct job *q_head;
struct job *q_tail;
pthread_rwlock_t q_lock;
};

int
queue_init(struct queue *qp)
{
int err;

qp->q_head = NULL;
qp->q_tail = NULL;
err = pthread_rwlock_init(&qb->q_lock,NULL);
if (err != 0)
return(err);
return(0);
}

void job_insert(struct queue *qp, struct job *jp) //队头插入
{
pthread_rwlock_wrlock(&qb->q_lock); //写锁
jp->j_next = qp->q_head;
jp->j_prev = NULL;
if (qb->q_head != NULL) //空队列
qb->q_head->j_prev = jp;
else
qb->q_tail = jp;
qb->q_head = jp;
pthread_rwlock_unlock(&qb->q_lock);
}

void
job_append(struct queue *qp, struct job *jp) //队尾插入
{
pthread_rwlock_wrlock(&qb->q_lock);
jp->j_next = NULL;
jp->j_prev = qp->q_tail;
if (qp->q_tail != NULL)
qp->q_tail->j_next = jp;
else
qp->q_head = jp;
qp->q_tail = jp;
pthread_rwlock_unlock(&qb->q_lock);
}

void
job_remove(struct queue *qp,struct job *jp)
{
pthread_rwlock_wrlock(&qb->q_lock);
if (jp == qb->q_head){
qp->q_head = jp->j_next;
if(qp->q_tail == jp)
qp->q_tail = NULL;
else
jp->j_next->j_prev = jp->j_prev;
} else if (jp == qb->q_tail){
qp->q_tail = jp->j_prev;
jp->j_prev->j_next = jp->j_next;
} else {
jp->j_prev->j_next = jp->j_next;
jp->j_next->j_prev = jp->j_prev;
}
pthread_rwlock_unlock(&qb->q_lock);
}

struct job *
job_find(struct queue *qp, pthread_t id)
{
struct job *jp;
if (pthread_rwlock_rdlock(&qp->q_lock) != 0)
return(NULL);
for (jp = qp->q_head; jp != NULL; jp = jp->j_next)
if (pthread_equal(jp->j_id, id))
break;
pthread_rwlock_unlock(&qp->q_lock);
return(jp);
}

指定时间阻塞锁定读写器

1
2
3
4
5
6
#include <pthread.h>
#include <time.h>

int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict tsptr);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict tsptr);
Both return: 0 if OK, error number on failure

状态变量

利用状态加锁

1
2
3
4
#include <pthread.h>
int pthread_cond_init(pthread_cont_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
Both return: 0 if OK, error number on failure

状态等待

1
2
3
4
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restricct cond, pthread_mutex_t *restrict mutex,const struct timespec *restrict tsptr);
Both return: 0 if OK, error number on failure

获取超时的绝对时间

1
2
3
4
5
6
7
8
9
10
11
12
#include <sys/time.h>
#include <stdlib.h>

void
maketimeout(struct timespec *tsp, long minutes)
{
struct timeval now;
gettimeofday(&now, NULL);
tsp->tv_sec = now.tv_sec;
tsp->tv_nsec = now.tv_usec * 1000;
tsp->tv_sec += minutes * 60;
}

唤醒线程

1
2
3
4
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cont_t *cond);
Both return:0 if OK, error number on failure

自旋锁

1
2
3
4
5
#include <pthread.h>

int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);
Both return:0 if OK, error number on failure
1
2
3
4
5
6
#inlucde <pthread.h>

int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
All return: 0 if OK, error number on failure

屏障

1
2
3
4
5
#include <pthread.h>

int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr,unsigned int count);
int pthread_barrier_destory(pthread_barrier_t *barrier);
Both return: 0 if OK, error number on failure

等待其他线程

1
2
3
#include <pthread.h>
int pthread_barrier_wait(pthread_barrier_t *barrier);
Returns:0 or PTHREAD_BARRIER_SERIAL_THREAD if OK, error number on failure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <pthread.h>
#include <limits.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>

#define NTHR 8
#define NUMNUM 8000000L
#define TNUM (NUMNUM/NTHR) //一百万


long nums[NUMNUM];
long snums[NUMNUM];

pthread_barrier_t b; //屏障变量

#ifdef SOLARIS
#define heapsort qsort
#else
extern int heapsort(void *, size_t, size_t,
int (*)(const void *, const void *)); //堆排序
#endif


int
complong(const void *arg1, const void *arg2) //比较
{
long l1 = *(long *)arg1;
long l2 = *(long *)arg2;
if (l1 == l2)
return 0;
else if (l1 < l2)
return -1;
else
return 1;
}

void *
thr_fn(void *arg)
{
long idx = (long)arg;
heapsort(&nums[idx], TNUM, sizeof(long), complong); //堆排序一百万个数据
pthread_barrier_wait(&b);
return((void *)0);
}

void merge()
{
long idx[NTHR];
long i, minidx, sidx, num;

for(i = 0; i < NTHR; i++)
idx[i] = i *TNUM; //八百万数据
for (sidx = 0; sidx < NUMNUM; sidx++){ //每次比较八个百万数组中的值,获得最小值放到snums数组中
num = LONG_MAX; //最大值
for(i = 0; i < NTHR; i++){
if ((idx[i] < (i+1)*TNUM) && (nums[idx[i]] < num)){
num = nums[idx[i]];
minidx = i;
}
}
snums[sidx] = nums[idx[minidx]];
idx[minidx]++;
}
}

int
main()
{
unsigned long i;
struct timeval start, end;
long long startusec, endusec;
double elapsed;
int err;
pthread_t tid;

srandom(1);
for (i = 0; i < NUMNUM; i++)
nums[i] = random(); //生成八百万个随机的数据

gettimeofday(&start, NULL); //获取时间
pthread_barrier_init(&b, NULL, NTHR+1); //初始化屏障锁
for (i = 0; i < NTHR; i++){
err = pthread_create(&tid, NULL, thr_fn, (void *)(i * TNUM)); //启动八个线程进行堆排序
if (err != 0)
{
fprintf(stderr,"can't create thread");
exit(-1);
}
}
pthread_barrier_wait(&b); //八个线程都准备就绪
merge(); //合并八个线程的数据
gettimeofday(&end,NULL);
startusec = start.tv_sec * 1000000 + start.tv_usec;
endusec = end.tv_sec * 1000000 + end.tv_usec;
printf("sort took %.4f seconds \n",elapsed);
for (i = 0; i < NUMNUM; i++)
printf("%ld\n",snums[i]);
exit(0);
}

线程控制

线程属性

1
2
3
4
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);//用于初始化pathread_attr_t结构体,并使得属性都为默认值
int pthread_attr_destroy(pthread_attr_t *attr);//销毁属性结构体
//Both return:0 if OK, error number on failure

获取线程的分离状态

1
2
3
4
#include <pthread.h>
int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr, int *detachstate);//获取detachstate属性,属性值为PTHREAD_CREATE_DETACHED或PTHREAD_CREATE_JOINABLE
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
//Both return:0 if OK,error number on failure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <pthread.h>

int
makethread(void *(*fn)(void *), void *arg)
{
int err;
pthread_t tid;
pthread_attr_t attr;

err = pthread_attr_init(&attr);
if (err != 0)
return(err);
err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (err == 0)
err = pthread_create(&tid, &attr, fn, arg);
pthread_attr_destroy(&attr);
return(err);
}

线程堆栈属性

设置线程的堆栈

1
2
3
4
5
6
7
8
#include <pthread.h>
int pthread_attr_getstack(const pthread_attr_t *restrict attr,
void **restrict stackaddr,
size_t *restrict stacksize);
int pthread_attr_setstack(pthread_attr_t *attr,
void *stackaddr,
size_t stacksize);
//Both return:0 if OK,error number on failure

获取与设置堆栈大小

1
2
3
4
#include <pthread.h>
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr,size_t *restrict stacksize);
int pthread_attr_setstacksize(pthread_attr_t, size_t stacksize);
//Both return:0 if OK,error number on failure

设置与获取guardsizeguardsize用于设置保护缓存区大小

1
2
3
4
#include <pthread.h>
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,size_t *restrict guaradsize);
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
//Both return:0 if OK,error number on failure

同步属性

锁属性

初始化锁属性

1
2
3
4
#include <pthread.h>
int pthread_mutextattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
//Both return:0 if OK, error number on failure

获取锁共享属性

1
2
3
4
5
6
7
8
#include <pthread.h>

int pthread_mutexattr_getpshared(const pthread_mutexattr_t *
restrict attr,
int *restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,
int pshared);
//Both return:0 if OK, error number on failure

获取健壮互斥体属性

1
2
3
4
5
6
7
#include <pthread.h>
int pthread_mutexattr_getrobust(const pthread_mutexattr_t *
restrict attr,
int *restrict robust);
int pthread_mmutexattr_setrobust(pthread_mutexattr_t *attr,
int robust);
//Both return: 0 if OK, error number in failure

保持互斥锁的状态

1
2
3
#include <pthread.h>c
int pthread_mutex_consistent(pthread_mutex_t * mutex);
//Reutns:0 if OK, error number on failure

mutex类型的可选值

  • PTHREAD_MUTEX_NORMAL:标准互斥类型,不进行任何特殊的错误检查或死锁检测
  • PTHREAD_MUTEX_ERRORCHECK:提供错误检查的互斥类型
  • PTHREAD_MUTEX_RECURSIVE:一种互斥锁类型,允许同一线程在不首先解锁的情况下多次锁定它。递归互斥锁保持一个锁计数,直到它被锁定的次数相同时才被释放。因此,如果您锁定递归互斥锁两次,然后解锁它,则互斥锁将保持锁定状态,直到第二次解锁。
  • PTHREAD_MUTEX_DEFAULT:提供默认特性和行为的互斥类型。实现可以自由地将其映射到其他互斥类型之一

image-20230115180638406

获取与更改锁属性

1
2
3
4
5
#include <pthread.h>
int pthread_mutexattr_gettype(const pthread_mutexattr_t *
restrict attr, int *restrict type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
//Both return:0 if OK,error number on failure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include <pthread.h>
#include <time.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>

int makethread(void *(*)(void *), void *);
struct to_info{
void (*to_fn)(void *); //函数指针
void *to_arg; //函数的参数
struct timespec to_wait; //等待时间
};

#define SECTONSEC 1000000000

#if !defined(CLOCK_REALTIME) || defined(BSD)
#define clock_nanosleep(ID, FL, REQ, REM) nanosleep((REQ), (REM))
#endif

#ifndef CLOCK_REALTIME
#define CLOCK_REALTIME 0
#define USECTONSEC 1000
void
clock_gettime(int id, struct timespec *tsp)
{
struct timeval tv;

gettimeofday(&tv, NULL);
tsp->tv_sec = tv.tv_sec;
tsp->tv_nsec = tv.tv_usec * USECTONSEC;
}
#endif

int
makethread(void *(*fn)(void *), void *arg)
{
int err;
pthread_t tid;
pthread_attr_t attr;

err = pthread_attr_init(&attr); //初始化属性
if (err != 0)
return(err);
err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //将线程的状态设置为分离
if (err == 0)
err = pthread_create(&tid, &attr, fn, arg);
pthread_attr_destroy(&attr);
return(err);
}


void *
timeout_helper(void *arg)
{
struct to_info *tip;

tip = (struct to_info *)arg;
clock_nanosleep(CLOCK_REALTIME, 0, &tip->to_wait, NULL); //纳秒级别休眠
(*tip->to_fn)(tip->to_arg);
free(arg);
return(0);
}

void
timeout(const struct timespec *when, void (*func)(void *), void *arg)
{
struct timespec now;
struct to_info *tip;
int err;

clock_gettime(CLOCK_REALTIME, &now);
if ((when->tv_sec > now.tv_sec) ||
(when->tv_sec == now.tv_sec && when->tv_nsec > now.tv_nsec)){
tip = malloc(sizeof(struct to_info));
if (tip != NULL){
tip->to_fn = func;
tip->to_arg = arg;
tip->to_wait.tv_sec = when->tv_sec - now.tv_sec;
if (when->tv_nsec >= now.tv_nsec){
tip->to_wait.tv_nsec = when->tv_nsec - now.tv_nsec;
} else {
tip->to_wait.tv_sec--;
tip->to_wait.tv_nsec = SECTONSEC - now.tv_nsec + when->tv_nsec;
}
err = makethread(timeout_helper, (void *)tip);
if (err == 0)
return;
else
free(tip);
}
}
}

pthread_mutexattr_t attr;
pthread_mutex_t mutex;

void
retry(void *arg)
{
pthread_mutex_lock(&mutex);
pthread_mutex_unlock(&mutex);
}

int
main(void)
{
int err, condition, arg;
struct timespec when;

if ((err = pthread_mutexattr_init(&attr)) != 0)
{
fprintf(stderr, "pthread_mutexattr_init failed");
exit(-1);
}
if ((err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) != 0) //在同一个线程中可以多次获取同一把锁。并且不会死锁
{
fprintf(stderr, "can't set recursive type");
exit(-1);
}
if ((err = pthread_mutex_init(&mutex, &attr)) != 0)
{
fprintf(stderr, "can't create recursive mutex");
exit(-1);
}

pthread_mutex_lock(&mutex);

if (condition) {
clock_gettime(CLOCK_REALTIME, &when);
when.tv_sec += 10;
timeout(&when, retry, (void *)((unsigned long)arg));
}

pthread_mutex_unlock(&mutex);
exit(0);
}

读写锁属性

读写锁属性初始化

1
2
3
4
5
#include <pthread.h>

int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
//Both return:0 if OK, error number on failure

获取读写锁共享属性

1
2
3
4
5
6
7
8
#include <pthread.h>

int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *
restrict attr,
int *restrict pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr,
int pshared);
//Both return:0 if OK, error number on failure

状态变量属性

状态变量属性初始化

1
2
3
4
5
#include <pthread.h>

int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
//Both return:0 if OK, error number on failure

共享属性

1
2
3
4
5
6
7
8
#include <pthread.h>

int pthread_condattr_getpshared(const pthread_condattr_t *
restrict attr,
int *restrict pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr,
int pshared);
// Both return:0 if OK, error number on failure

获取时钟

1
2
3
4
5
6
7
8
#include <pthread.h>

int pthread_condattr_getclock(const pthread_condattr_t *
restrict attr,
clockid_t *restrict clock_id);
int pthread_condattr_setclock(pthread_condattr_t *attr,
clockid_t clock_id);
//Both return:0 if OK, error number on failure

围栏属性

初始化属性

1
2
3
4
#include <pthread.h>
int pthread_barrierattr_init(pthread_barrierattr_t *attr);
int pthread_barrierattr_destroy(pthread_barrierattr_t *attr);
//Both return:0 if OK, error number on failure

获取共享属性

1
2
3
4
5
6
7
#include <pthread.h>
int pthread_barrierattr_getpshared(const pthread_barrierattr_t *
restrict aattr,
int *reestrict pshared);
int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr,
int pshared);
//Both return:0 if OK,error number on failure

flockfile和ftrylockfile

线程安全方式管理FILE对象的方法,flockfileftrylockfile用于获取与给定FILE对象关联的锁

1
2
3
4
5
6
#include <stdio.h>

int ftrylockfile(FILE *fp);
//Returns:0 if OK, nonzero if lock can't be acquired
void flockfile(FILE *fp);
void funlockfile(FILE *fp);

字符锁

基于字符的标准I/O例程的锁

1
2
3
4
5
6
#include <stdio.h>
int getchar_unlocked(void);
int getc_unlocked(FIILE *fp);
//Both return:the next character if OK,EOF on end of file or error
int putchar_unlocked(int c);
int putc_unlocked(int c, FILE *fp);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>

extern char **environ;
pthread_mutex_t env_mutex;
static phtread_once_t init_done = PTHREAD_ONCE_INIT;


static void
thread_init(void)
{
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //可以反复加锁
pthread_mutex_init(&env_mutex, &attr);
pthread_mutexattr_destroy(&attr);
}

int
getenv_r(const char *name, char *buf, int buflen)
{
int i, len, olen;
pthread_once(&init_done, thread_init); //只执行一次
len = strlen(name);
pthread_mutex_lock(&env_mutex);
for (i = 0; environ[i] = NULL; i++){
if ((strncmp(name, environ[i], len) == 0) &&
(environ[i][len] == '=')){
olen = strlen(&environ[i][len+1]);
if (olen >= buflen){
pthread_mutex_unlock(&env_mutex);
return(ENOSPC);
}
strcpy(buf, &environ[i][len+1]);
pthread_mutex_unlock(&env_mutex);
return(0);
}
}
pthread_mutex_unlock(&env_mutex);
return(ENOENT);

}

线程特定数据

创建数据关联的键

1
2
3
#include <pthread.h>
int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));
//Returns:0 if OK,error number on failure

删除键的关联

1
2
3
#include <pthread.h>
int pthread_key_delete(pthread_key_t key);
//Returns:0 if OK,error number on failure

一次初始化

1
2
3
#include <pthread.h>
pthread_once_t initflag = PTHREAD_ONCE_INIT;
int pthread_once(phtread_once_t *initflag, void (*initfn)(void));

特定数据与键关联

1
2
3
4
5
6
#include <pthread.h>

void *pthread_getspecific(pthread_key_t key);
Returns:thread-specific data value or NULL if no value has benn associated with the key
int pthread_setspecific(pthread_key_t key, const void *value);
Returns:0 if OK,error nnumber on failure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <limits.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>

#define MAXSTRINGSZ 4096

static pthread_key_t key;
static pthread_once_t init_done = PTHREAD_ONCE_INIT;
pthread_mutex_t env_mutex = PTHREAD_MUTEX_INITIALIZER;

extern char **environ;

static void
thread_init(void)
{
pthread_key_create(&key, free);
}

char *
getenv(const char *name)
{
int i, len;
char *envbuf;

pthread_once(&init_done, thread_init);
pthread_mutex_lock(&env_mutex);
envbuf = (char *)pthread_getspecific(key);
if (envbuf == NULL)
{
envbuf = malloc(MAXSTRINGSZ);
if (envbuf == NULL){
pthread_mutex_unlock(&env_mutex);
return(NULL);
}
pthread_setspecific(key, envbuf);
}
len = strlen(name);
for (i = 0; environ[i] != NULL; i++){
if ((strncmp(name, environ[i], len) == 0) &&
(environ[i][len] == '=')){
strncpy(envbuf, &environ[i][len+1], MAXSTRINGSZ-1);
pthread_mutex_unlock(&env_mutex);
return(envbuf);
}
}
pthread_mutex_unlock(&env_mutex);
return(NULL);
}

取消选项

pthread_attr_t结构中不包含两个线程属性,需要通过pthread_cancel函数进行设置

  • 可取消状态
    • PTHREAD_CANCEL_ENABLE
    • PTHREAD_CANCEL_DISABLE
  • 可取消类型
1
2
3
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
//Returns:0 if OK, error number on failure

image-20230116002822460

设置取消点

自定义取消点

1
2
#include <pthread.h>
void pthread_testcancel(void);

设置取消类型

1
2
3
#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);
//Returns:0 if OK, error number on failure

线程与信号

修改进程的信号阻塞行为

1
2
3
#include <signal.h>
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
//Returns:0 if OK,error number on failure

等待信号

1
2
3
#include <signal.h>
int sigwait(const sigset_t *restrict set, int *restrict signop);
//Returns:0 if OK,error number on failure

发送信号

1
2
3
#include <signal.h>
int pthread_kill(pthread_t thread, int signo);
//Returns:0 if OK, error number on failure

线程与fork

线程的fork函数

1
2
3
#include <pthread.h>
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void(*child)(void));
//Returns:0 if OK,error number on failure
  • prepare:获取父级定义的所有锁
  • parent:在父进程中,解锁preparefork获取的锁
  • child:在子进程中,解锁prepparefork获取的锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;

void
prepare(void)
{
int err;

printf("preparing locks...\n");

if ((err = pthread_mutex_lock(&lock1)) != 0)
{
fprintf(stderr, "can't lock lock1 in prepare handler");
exit(-1);
}

if ((err = pthread_mutex_lock(&lock2)) != 0)
{
fprintf(stderr, "can't lock lock2 in prepare handler");
exit(-1);
}

}

void
parent(void)
{
int err;

printf("parent unlocking locks...\n");
if ((err = pthread_mutex_unlock(&lock1)) != 0)
{
fprintf(stderr,"can't unlock lock1 in parent handler");
exit(-1);
}
if ((err = pthread_mutex_unlock(&lock2)) != 0)
{
fprintf(stderr,"can't unlock lock2 in parent handler");
exit(-1);
}
}

void
child(void)
{
int err;

printf("child unlocking locks...\n");
if ((err = pthread_mutex_unlock(&lock1)) != 0)
{
fprintf(stderr, "can't unlock lock1 in child handler");
exit(-1);
}

if ((err = pthread_mutex_unlock(&lock2)) != 0)
{
fprintf(stderr, "can't unlock lock2 in child handler");
exit(-1);
}
}

void *
thr_fn(void *arg)
{
printf("thread started...\n");
pause();
return(0);
}

int
main(int argc, char *argv[])
{
int err;
pid_t pid;
pthread_t tid;

if ((err = pthread_atfork(prepare, parent, child)) != 0)
{
fprintf(stderr, "can't install fork handlers");
exit(-1);
}

if ((err = pthread_create(&tid, NULL, thr_fn, 0)) != 0)
{
fprintf(stderr, "can't create thread");
exit(-1);
}

sleep(2);
printf("parent about to fork...\n");

if ((pid = fork()) < 0)
{
fprintf(stderr, "fork failed");
exit(-1);
}
else if (pid == 0)
{
printf("child returned from fork\n");
exit(-1);
}
else
printf("parent returned from fork\n");
exit(0);
}

image-20230117001028533

线程与I/O

pread与pwrite用于处理线程间读写

守护进程

image-20230118235450382

读取日志信息的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <syslog.h>
void openlog(const char *ident, int option, int facility); //打开日志文件
/*
ident:通常为添加到日志文件中的程序名
option:用于指定各种选项的掩码
facility:用途

*/
void syslog(int priority, const char *format, ...);
/*
priority:优先级
format:格式化字符串参数
*/
void closelog(void); //关闭用于与syslogd守护进程通信的描述符
int setlogmask(int maskpri); //设置进程的日志优先级掩码
//Returns:previous log priority mask value

image-20230129204719364

image-20230129205516099

image-20230129205535605

自定义的日志打印函数

1
2
3
4
#include <syslog.h>
#include <stdarg.h>

void vsyslog(int priority, const char *format, va_list arg);

单独实例守护进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <syslog.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <sys/stat.h>

#define LOCKFILE "/var/run/daemon.pid"
#define LOCKMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

extern int lockfile(int);

int already_running(void)
{
int fd;
char buf[16];
fd = open(LOCKFILE, O_RDWR | O_CREAT, LOCKMODE);
if (fd < 0){
syslog(LOG_ERR, "can't open %s: %s", LOCKFILE, strerror(errno));
exit(1);
}
if (lockfile(fd) < 0){ //锁定文件
if (errno == EACCES || errno == EAGAIN){
close(fd);
return(1);
}
syslog(LOG_ERR,"can't lock %s: %s", LOCKFILE, strerror(errno));
exit(1);
}
ftruncate(fd, 0); //截断文件
sprintf(buf, "%ld", (long)getpid());
write(fd, buf, strlen(buf)+1);
return(0);
}

守护进程重读配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <pthread.h>
#include <syslog.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

sigset_t mask;

extern int already_runniing(void);

void
reread(void)
{

}

void *
thr_fn(void *arg)
{
int err, signo;
for (;;){
err = sigwait(&mask, &signo); //等到信号
if (err != 0){
syslog(LOG_ERR, "sigwait failed");
exit(1);
}
switch (signo)
{
case SIGHUP:
syslog(LOG_INFO, "Re-reading configuration file");
reread();
/* code */
break;
case SIGTERM:
syslog(LOG_INFO, "got SIGTERM; exiting");
exit(0);
default:
syslog(LOG_INFO, "unexpected signal %d\n", signo);
break;
}
return(0);
}
}

int
main(int argc, char *argv[])
{
int err;
pthread_t tid;
char *cmd;
struct sigaction sa;

if ((cmd = strrchr(argv[0], '/') == NULL))
cmd = argv[0];
else
cmd++;
daemonize(cmd);
if (already_runniing()){
syslog(LOG_ERR, "daemon already running");
exit(1);
}

sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0)
{
fprintf(stderr, "can't restore SIGHUP default");
exit(-1);
}
sigfillset(&mask);
if ((err = pthread_sigmask(SIG_BLOCK, &mask, NULL)) != 0) //阻塞所有信号
{
fprintf(stderr,"SIG_BLOCK error");
exit(-1);
}

err = pthread_create(&tid, NULL, thr_fn, 0);
if (err != 0)
{
fprintf(stderr,"can't create thread");
exit(-1);
}
exit(0);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>


int
set_cloexec(int fd)
{
int val;

if ((val = fcntl(fd, F_GETFD, 0)) < 0)
return(-1);
val |= FD_CLOEXEC; //使用exec执行时,关闭该文件描述符

return(fcntl(fd, F_SETFD,val));
}

高级I/O

非阻塞I/O

  • 调用open函数时使用O_NONBLOCK标志
  • 对于已经打开的文件,可以使用fcntl函数打开O_NONBLOCK标志
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

char buf[500000];

int
main(void)
{
int ntowrite, nwrite;
char *ptr;

ntowrite = read(STDIN_FILENO, buf, sizeof(buf));
fprintf(stderr, "read %d bytes\n", ntowrite);

set_fl(STDOUT_FILENO, O_NONBLOCK);

ptr = buf;
while (ntowrite > 0){
errno = 0;
nwrite = write(STDOUT_FILENO, ptr, ntowrite);
fprintf(stderr, "nwrite = %d, errno = %d\n", nwrite, errno);

if (nwrite > 0){
ptr += nwrite;
ntowrite -= nwrite;
}
}

clr_fl(STDERR_FILENO, O_NONBLOCK);

exit(0);

}

记录锁

fcntl Record Locking

1
2
3
4
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /*struct flock *flockptr*/)
Returns:depends on cmd if OK(see follwing), -1 on error

对于记录锁,cmd的取值为:F_GETLKF_SETLKF_SETLKW

1
2
3
4
5
6
7
struct flock {
short l_type;/*F_RDLCK:分享读锁, F_WRLCK:独占写锁, or F_UNLCK:解锁区域*/
short l_whence;/*SEEK_SET, SEEK_CUR, or SEEK_END*/
off_t l_start;/*offset in bytes, relative to l_whence*/
off_t l_len;/*length, in bytes; 0 means lock to EOF*/
pid_t l_pid;/*returned with F_GETLK*/
};

image-20230131132819912

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <fcntl.h>

/*
#define read_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLK, F_RDLCK, (offset), (whence), (len))
#define readw_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLKW, F_RDLCK, (offset), (whence), (len))
#define write_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLK, F_WRLCK, (offset), (whence), (len))
#define writew_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLKW, F_WRLCK, (offset), (whence), (len))
#define un_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLK, F_UNLCK, (offset), (whence), (len))
*/

int
lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;

lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
lock.l_len = len;

return(fcntl(fd, cmd, &lock));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

/*
#define is_read_lockable(fd, offset, whence, len) \
(lock_test((fd), F_RDLCK, (offset), (whence), (len)) == 0)
#define is_write_lockable(fd, offset, whence, len) \
(lock_test((fd), F_WRLCK, (offset), (whence), (len)) == 0)
*/

pid_t
lock_test(int fd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
lock.l_len = len;

if (fcntl(fd, F_GETLK, &lock) < 0) //申请锁
{
fprintf(stderr, "fcntl error");
exit(-1);
}

if (lock.l_type == F_UNLCK)
return(0);
return(lock.l_pid);
}

死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "apue.h"

static void
lockabyte(const char *name, int fd, off_t offset)
{
if (writew_lock(fd, offset, SEEK_SET, 1) < 0)
{
fprintf(stderr, "%s: writew_lock error", name);
exit(-1);
}
printf("%s: got the lock, byte %lld\n",name, (long long)offset);
}

int
main(void)
{
int fd;
pid_t pid;

if ((fd = creat("templock", FILE_MODE)) < 0) //创建一个名为templock的文件
{
fprintf(stderr, "creat error");
exit(-1);
}

if (write(fd, "ab", 2) != 2) //往文件里写入ab
{
fprintf(stderr, "write error");
exit(-1);
}

TELL_WAIT();

if((pid = fork()) < 0){
fprintf(stderr,"fork error");
exit(-1);
}else if (pid == 0){
lockabyte("child", fd, 0); //加锁
TELL_PARENT(getppid());
WAIT_PARENT();
lockabyte("child", fd, 1);
}else {
lockabyte("parent", fd, 1);
TELL_CHILD(pid);
WAIT_CHILD();
lockabyte("parent", fd, 0);
}
exit(0);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <unistd.h>
#include <fcntl.h>

int
lockfile(int fd)
{
struct flock fl;
fl.l_type = F_WRLCK;
fl.l_start = 0;
fl.l_whence = SEEK_SET;
fl.l_len = 0;
return(fcntl(fd, F_SETLK, &fl));

}

强制锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
#define SIGIOT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGSTKFLT 16
#define SIGCHLD 17
#define SIGCONT 18
#define SIGSTOP 19
#define SIGTSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
#define SIGURG 23
#define SIGXCPU 24
#define SIGXFSZ 25
#define SIGVTALRM 26
#define SIGPROF 27
#define SIGWINCH 28
#define SIGIO 29


#include <fcntl.h>


#define read_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLK, F_RDLCK, (offset), (whence), (len))
#define readw_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLKW, F_RDLCK, (offset), (whence), (len))
#define write_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLK, F_WRLCK, (offset), (whence), (len))
#define writew_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLKW, F_WRLCK, (offset), (whence), (len))
#define un_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLK, F_UNLCK, (offset), (whence), (len))


int
lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;

lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
lock.l_len = len;

return(fcntl(fd, cmd, &lock));
}




static sigset_t newmask, oldmask, zeromask;
static volatile sig_atomic_t sigflag;

static void
sig_usr(int signo) /* one signal handler for SIGUSR1 and SIGUSR2 */
{
sigflag = 1;
}

void
TELL_WAIT(void)
{
if (signal(SIGUSR1, sig_usr) == SIG_ERR)
printf("signal(SIGUSR1) error");
if (signal(SIGUSR2, sig_usr) == SIG_ERR)
printf("signal(SIGUSR2) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGUSR1);
sigaddset(&newmask, SIGUSR2);

/* Block SIGUSR1 and SIGUSR2, and save current signal mask */
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) //阻塞SIGUSR1和SIGUSR2
printf("SIG_BLOCK error");
}


void
TELL_PARENT(pid_t pid)
{
kill(pid, SIGUSR2); /* tell parent we're done */
}

void
WAIT_PARENT(void)
{
while (sigflag == 0)
sigsuspend(&zeromask); /* and wait for parent */ //清空阻塞,接收信号
sigflag = 0;

/* Reset signal mask to original value */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
printf("SIG_SETMASK error");
}

void
TELL_CHILD(pid_t pid)
{
kill(pid, SIGUSR1); /* tell child we're done */
}

void
WAIT_CHILD(void)
{
while (sigflag == 0)
sigsuspend(&zeromask); /* and wait for child */
sigflag = 0;

/* Reset signal mask to original value */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
printf("SIG_SETMASK error");
}


void set_fl(int fd, int flags) /* flags are file status flags to turn on */
{
int val;

if ( (val = fcntl(fd, F_GETFL, 0)) < 0)
{
printf("fcntl F_GETFL error");
exit(1);
}

val |= flags; /* turn on flags */

if (fcntl(fd, F_SETFL, val) < 0)
{
printf("fcntl F_SETFL error");
exit(1);
}
}

int
main(int argc, char *argv[])
{
int fd;
pid_t pid;
char buf[5];
struct stat statbuf;

if (argc != 2){
fprintf(stderr, "usage: %s filename\n", argv[0]);
exit(1);
}

if ((fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < 0)
{
fprintf(stderr, "open error");
exit(-1);
}
if (write(fd, "abcdef", 6) != 6)
{
fprintf(stderr, "write error");
exit(-1);
}

if (fstat(fd, &statbuf) < 0)
{
fprintf(stderr, "fstat error");
exit(-1);
}
//强制锁定
if (fchmod(fd, (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0) //删除组执行,将进程的有效组ID设置为文件的组所有者ID
{
fprintf(stderr, "fchmod error");
exit(-1);
}

TELL_WAIT();

if ((pid = fork()) < 0){
fprintf(stderr, "fork error");
exit(-1);
} else if (pid > 0) { //父进程
if (write_lock(fd, 0, SEEK_SET, 0) < 0) //添加写锁
{
fprintf(stderr, "write_lock error");
exit(-1);
}
TELL_CHILD(pid);//唤醒子进程

if(waitpid(pid, NULL, 0) < 0){ //等待子进程发送信号
fprintf(stderr, "waitpid error");
exit(-1);
}
} else {
WAIT_PARENT();//判断父进程是否有发送下信号
set_fl(fd, O_NONBLOCK); //将文件修改为不阻塞
if (read_lock(fd, 0, SEEK_SET, 0) != -1) //增加读锁
{
fprintf(stderr, "child: read_lock succeeded");
exit(-1);
}
printf("read_lock of already-locked region returns %d\n",errno);

if (lseek(fd, 0, SEEK_SET) == -1)
{
fprintf(stderr, "lseek error");
exit(-1);
}

if (read(fd, buf, 2) < 0)
{
fprintf(stderr, "read failed (mandatory locking works)");
exit(-1);
}
else
printf("read OK (o mandatory locking), buf = %2.2s\n",buf);
}
exit(0);

}

IO多路复用

从一个文件描述符中读取数据,并写入到另一个文件描述符中,通过阻塞I/O实现

1
2
3
while ((n = read(STDIN_FILENO, buf, BUFSIZE)) > 0)
if (write(STDOUT_FILENO, buf, n) != n)
err_sys("write error");

上述实现方法不能应用于从两个文件描述符当中读取数据,因为我们没有办法同时阻塞两个read

image-20230202223826795

使用两个不同进程分别进行读与写能够解决上述情况,但是当父子进程被终止时需要相互使用信号进行通知,会增加程序的复杂性

image-20230202224110468

使用同个进程两个不同线程进行处理,但是需要同步两个线程之间的信息。

  • 轮询:读取文件描述符,若无数据直接返回,等一段时间再进行读取
    • 缺点:浪费CPU时间
    • 应该避免在多任务系统上进行使用
  • 同步I/O:当从文件描述符通过I/O读取时,内核需要通知用户
    • 缺点:兼容性不高、单一的信号无法标记所有的文件描述符
  • I/O复用:通过构建描述符列表,并调用一个函数,直到其中一个描述符准备好I/O时才返回

select 和 pselect函数

select函数实现I/O复用,参数指明

  • 哪个文件描述符我们感兴趣
  • 文件描述符的哪个状态我们感兴趣
  • 我们需要等待多长时间

return

  • 总共有多少个文件描述符已就绪
  • 三个条件(读、写和异常)中的每一个都准备好了哪些描述符

总的来说,select函数用于获取我们对某个文件描述符感兴趣的状态

1
2
3
4
5
6
7
8
#include <sys/select.h>
int select(int maxfdp1, //最大的文件描述符的个数加一,可以设置为FD_SETSIZE,在<sys/select.h>头文件中
fd_set *restrict readfds, //感兴趣的读文件描述符
fd_set *restrict writefds, //感兴趣的写文件描述符
fd_set *restrict exceptfds, //感兴趣的异常的描述符
struct timeval *restricct tvptr);

//Returns:count of ready deescriptors, 0 on timeout, -1 on error
  • tvptr == NULL
    • 无限制的等待。当描述符就绪以及被信号中断则返回。若被信号中断则select返回-1以及errno被设置为EINTR
  • tvptr->tv_sec == 0 && tvptr->tv_usec == 0
    • 不等待。测试所有指定的描述符,并立即返回。
  • tvptr->tv_sec != 0 || tvptr->tv_usec != 0
    • 等待指定秒数或毫秒数。当指定的描述符之一就绪或超时返回。

image-20230202232500872

fd_set数据类型的操作

1
2
3
4
5
6
7
#include <sys/select.h>

int FD_ISSET(int fd, fd_set *fdset);
Returns:nonzero if fd is in set,0 otherwise
void FD_CLR(int fd, fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_ZERO(fd_set *fd_set);

example1

1
2
3
4
5
6
7
8
9
10
fd_set rset;
int fd;

FD_ZERO(&rset);
FD_SET(fd, &rset);
FD_SET(STDIN_FILENO, &rset);

if (FD_ISSET(fd, &rset)){
...
}

example2

1
2
3
4
5
6
7
8
9
fd_set readset, writeset;

FD_ZERO(&readset);
FD_ZERO(&writeset);
FD_SET(0, &readset);
FD_SET(3, &readset);
FD_SET(1, &writeset);
FD_SET(2, &WRITESET);
select(4, &readset, &writeset, NULL, NULL);

返回值可能的情况

  • 返回值为-1意味着错误发生。例如被信号中断
  • 返回值为0,意味着等待超时,此时所有的描述符集会被清空
  • 非负数,意味着对应的描述符已经就绪。该值是三个集合中所有描述符的总和。

pselect

1
2
3
4
5
6
7
8
9
#include <sys/select.h>

int pselect(int maxfdp1,
fd_set *restrict readfds,
fd_set *restrict writefds,
fd_set *restrict exceptfds,
const struct timespec *restrict tsptr,
const sigset_t *restrict sigmask);
Returns:count of ready descriptors, 0 on timeout, -1 on error

与select区别

  • 时间采用timespec结构体,提供了秒与纳米级别的粒度
  • 提供了信号集,可以指定安装对应的信号掩码

poll

1
2
3
4
#include <poll.h>

int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);
Returns:count of ready descriptors, 0 on timeout, -1 on error
1
2
3
4
5
struct pollfd {
int fd;
short events;
short revents;
};

image-20230203200512981

  • timeout == -1
    • 一直等待。如果捕获到信号,轮询将返回-1,errno设置为EINTR
  • timeout == 0
    • 不等待。所有描述符都将被测试并且立即返回。这是一种poll找出多个描述符状态的方法,而不会阻塞轮询调用。
  • timeout > 0
    • 等到指定的毫秒数。当指定描述符就绪或者超时则返回。超时时返回0。

异步I/O

异步操作可能产生的错误

  • 操作提交相关
  • 操作本身
  • 确定异步状态的函数

System V 异步I/O

在流设备与流管道上工作。

image-20230203205833011

BSD异步I/O

BSD驱动系统需要结合两个信号

  • SIGIO:通常为异步信号
  • SIGURG:用于通知进程带外数据已到达网络连接

为了接收SIGIO信号,需要执行三个步骤

  1. 建立基于SIGIO的信号处理函数
  2. 设置进程ID或进程组ID为了接收 信号的描述符通过函数fcntl并设置参数F_SETOWN
  3. 通过fcntl设置O_ASYNC文件状态标志,启用描述符上的异步I/O

POSIX异步I/O

POSIX的异步I/O接口使用AIO块去描述I/O操作

AIO块通过aiocb结构体定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct aiocb{
int aio_fildes; //文件描述符
off_t aio_offset; //基于I/O的文件偏移
volatile void *aio_buf; //I/O buffer
size_t aio_nbytes; //需要转换为字节数
int aio_reqprio //优先级
struct sigevent aio_sigevent; //信号的信息
int aio_lio_opcode; //操作列表I/O
};


struct sigevent{
int sigev_notify; //通知类型
int sigev_signo; //信号数字
union sigval sigev_value; //通知参数
void (*sigev_notify_function)(union sigval); //通知函数
pthread_attr_t *sigev_notify_attributes; //通知属性
};

sigev_notify类型有三个值可选

  • SIGEV_NONE:异步I/O请求完成时不会通知进程
  • SIGEV_SIGNAL:当异步I/O请求完成时,将生成sigev_signo字段指定的信号
  • SIGEV_THREAD:当异步I/O请求完成时,调用sigev_notify_function字段指定的函数。传递sigev_value字段作为其唯一参数。除非sigev_notify_attributes字段设置为pthread属性结构的地址,否则该函数将在分离状态的单独线程中执行

在执行异步I/O前,需要初始化AIO控制块,并调用AIO_read函数进行异步读取,或者调用AIO_write函数进行非同步写入

1
2
3
4
5
#include <aio.h>
int aio_read(struct aiocb *aiocb);
int aio_write(struct aiocb *aiocb);

Both return:0 if OK, -1 on error
1
2
3
4
5
#include <aio.h>

int aio_fsync(int op, struct aiocb *aiocb);

Returns:0 if OK, -1 on error
  • op设置为O_DSYNC类似于fdatasync的调用
  • op设置为O_SYNC类似于fsync的调用,需要你在入参的位置上传递给他一个fd,然后系统调用就会对这个fd指向的文件起作用。fsync会确保一直到写磁盘操作结束才会返回。

为了确定异步读、写或同步操作的完成状态,则通过aio_error函数

1
2
3
4
5
#include <aio.h>

int aio_error(const struct aiocb *aiocb);

Returns:(see following)

返回值

  • 0,异步操作已完成,需要通过aio_return函数从操作中获取返回值
  • -1,异步操作失败,errno中存储了原因
  • EINPROGRESS,异步读取、写入或同步仍处于挂起状态
  • 其他值,任何其他返回值都会为我们提供与失败的异步操作相对应的错误代码。

通过aio_return获取操作的返回值

1
2
3
4
5
#include <atio.h>

ssize_t aio_return(const struct aiocb *aiocb);

Returns:(see following)
  • -1,设置errno则返回错误
  • 返回异步操作的结果

当完成其他处理但仍然有其他异步操作未完成时,调用aio_suspend函数进行阻塞

1
2
3
4
5
#include <aio.h>

int aio_suspend(const struct aiocb *const list[], int nent,
const struct timespec *timeout);
Returns:0 if OK, -1 on error
  • 当被信号中断时,返回-1且将errno设置为EINTR
  • 超时,返回-1且将errno设置为EAGAIN
  • 如果异步操作完成,则返回0
  • 如果在调用aio_suspend时所有异步I/O操作都已完成,那么aio_susend将返回而不会阻塞

取消异步操作

1
2
3
4
5
6
7
#include <aio.h>

/*
fd指定想要取消的未完成异步操作的I/O,若aiocb为NULL则取消所有异步操作
*/
int aio_cancel(int fd, struct aiocb *aiocb);
Returns:(see following)
  • AIO_ALLDONE:所有操作在取消前已经全部完成

  • AIO_CANCELED:所有请求的操作都已经被取消

  • AIO_NOTCANCELED:至少有一个请求未被取消

  • -1:调用aio_cancel函数失败,错误代码被存在errno

1
2
3
4
5
6
7
8
#include <aio.h>

int lio_listio(int mode, //判断I/O是否为真正异步
struct aiocb *restrict const list[restrict],//需要执行的I/O操作的AIO控制块列表
int nent, //元素的个数
struct sigevent *restrict sigev //通知的补充,当所有I/O操作完成时发送
);
Returns:0 if OK, -1 on error

此函数可以控制一个AIO控制块列表来描述IO请求

mode

  • LIO_WAIT:直到list指定的所有操作完成时才会返回,sigev参数将会被忽略
  • LIO_NOWAIT:当I/O请求排队时,lio_listio函数立即返回。根据sigev参数的指定,当所有I/O操作完成时,将异步通知进程。不想通知时,sigev可以被设置为NULL

AIO控制块中的aio_lio_opcoded区域制定了操作是否为读、写或者忽略。读被认为是传入aio_read函数,写被认为是传入aio_write函数

以下是限制允许执行的异步I/O操作的数量

image-20230203221759223

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#include <ctype.h>
#include <fcntl.h>
#include <aio.h>
#include <errno.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>


#define BSZ 4096
#define NBUF 8
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

enum rwop {
UNUSERD = 0,
READ_PENDING = 1,
WRITE_PENDING = 2
};

struct buf{
enum rwop op; //操作码
int last;
struct aiocb aiocb;
unsigned char data[BSZ];
};

struct buf bufs[NBUF];

unsigned char
translate(unsigned char c)
{
if (isalpha(c)){
if (c >= 'n')
c -= 13;
else if (c >= 'a')
c += 13;
else if (c >= 'N')
c -= 13;
else
c += 13;
}
return(c);
}

int
main(int argc, char* argv[])
{
int ifd, ofd, i, j, n, err, numop;
struct stat sbuf;
const struct aiocb *aiolist[NBUF];
off_t off = 0;

if (argc != 3)
{
fprintf(stderr, "usage: rot13 infile outfile");
exit(-1);
}
if ((ifd = open(argv[1], O_RDONLY)) < 0)
{
fprintf(stderr, "can't open %s", argv[1]);
exit(-1);
}
if ((ofd = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < 0)
{
fprintf(stderr, "can't create %s", argv[2]);
exit(-1);
}
if (fstat(ifd, &sbuf) < 0) //获取读入文件的状态
{
fprintf(stderr,"fstat failed");
exit(-1);
}

for (i = 0;i < NBUF; i++){ //初始化
bufs[i].op = UNUSERD;
bufs[i].aiocb.aio_buf = bufs[i].data;
bufs[i].aiocb.aio_sigevent.sigev_notify = SIGEV_NONE; //异步`I/O`请求完成时不会通知进程
aiolist[i] = NULL;
}

numop = 0;
for (;;){
for (i = 0; i < NBUF; i++){
switch (bufs[i].op)
{

case UNUSERD: //初始状态
printf("UNUSERD\n");

if (off < sbuf.st_size) //偏移小于文件大小
{
printf("%d\n",sbuf.st_size);
bufs[i].op = READ_PENDING; //挂起
bufs[i].aiocb.aio_fildes = ifd;
bufs[i].aiocb.aio_offset = off;
off += BSZ; //每次读取BSZ大小
if (off >= sbuf.st_size)
bufs[i].last = 1; //到达文件末尾
bufs[i].aiocb.aio_nbytes = BSZ;
if (aio_read(&bufs[i].aiocb) < 0) //异步读
{
fprintf(stderr, "aio_read failed");
exit(-1);
}
aiolist[i] = &bufs[i].aiocb;
numop++;
}
break;
case READ_PENDING:
printf("READ\n");
if ((err = aio_error(&bufs[i].aiocb)) == EINPROGRESS) //异步读取、写入或同步仍处于挂起状态
continue;
if (err != 0){
if (err == -1)
{
fprintf(stderr, "aio_error failed");
exit(-1);
}
else
{
fprintf(stderr,"read failed");
exit(0);
}
}
if ((n = aio_return(&bufs[i].aiocb)) < 0)
{
fprintf(stderr, "aio_return failed");
exit(-1);
}
if ( n != BSZ && !bufs[i].last) //若读取的长度不是BSZ则没读全
{
fprintf(stderr, "short read (%d/%d)",n, BSZ);
exit(0);
}
for (j = 0; j < n; j++)
bufs[i].data[j] = translate(bufs[i].data[j]);
printf("%s\n",bufs[i].data);
bufs[i].op = WRITE_PENDING;
bufs[i].aiocb.aio_fildes = ofd;
bufs[i].aiocb.aio_nbytes = n;
if (aio_write(&bufs[i].aiocb) < 0) //异步写
{
fprintf(stderr,"aio_write failed");
exit(-1);
}
break;
case WRITE_PENDING:
printf("WRITE\n");
if ((err = aio_error(&bufs[i].aiocb)) == EINPROGRESS)
continue;
if (err != 0){
if (err == -1)
{
fprintf(stderr,"aio_error faiiled");
exit(-1);
}
else
{
fprintf(stderr,"write failed");
exit(-1);
}
}

if ((n = aio_return(&bufs[i].aiocb)) < 0)
{
fprintf(stderr,"aio_return failed");
exit(-1);
}
if (n != bufs[i].aiocb.aio_nbytes)
{
fprintf(stderr,"short write (%d/%d)",n , BSZ);
exit(-1);
}
aiolist[i] = NULL;
bufs[i].op = UNUSERD;
numop--;
break;
}
}
if (numop == 0){
if (off >= sbuf.st_size)
break;
} else {
if (aio_suspend(aiolist, NBUF, NULL) < 0)
{
fprintf(stderr, "aio_suspend failed");
exit(-1);
}
}
}
bufs[0].aiocb.aio_fildes = ofd;
if (aio_fsync(O_SYNC, &bufs[0].aiocb) < 0)
{
fprintf(stderr, "aio_fsync failed");
exit(-1);
}
exit(0);

}

readn和writen

管道、FIFOs和一些设备,特别是终端和网络,会有两个特点

  • 读取的数据会比要求的少,这不是错误,而需要继续读
  • 写的数据比预期的少,造成这样的原因是写缓冲区已经满了,需要继续写剩余的数据。导致这样的原因通常是不阻塞的文件描述符以及捕获了信号。
1
2
3
4
5
6
#include "apue.h"

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

Both returns:number of bytes read or written, -1 on error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

ssize_t
readn(int fd, void *ptr, size_t n)
{
size_t nleft;
ssize_t nread;

nleft = n; //剩余需要读的字节数
while(nleft > 0){
if ((nread = read(fd, ptr, nleft)) < 0){
if (nleft == n)
return(-1);
else
break;
}else if (nread == 0){
break;
}
nleft -= nread;
ptr += nread;
}
return(n - nleft);
}

ssize_t
writen(int fd,const void *ptr, size_t n)
{
size_t nleft;
ssize_t nwritten;

nleft = n;
while (nleft > 0){
if ((nwritten = write(fd, ptr, nleft)) < 0){
if (nleft == n)
return(-1);
else
break;
}else if (nwritten == 0){
break;
}
nleft -= nwritten;
ptr += nwritten;
}
return(n - nleft);
}

Memory-Mapped I/O

mmap

内存映射I/O允许我们将磁盘上的文件映射到内存中的缓冲区,当从缓存区获取字节时,文件的相应字节就会被读取。

1
2
3
4
#include <sys/mmanh>

void *mmap(void *addr, size_t len, int prot, int flag, int fd, off_t off);
Returns:starting address of mapped region if OK,MAP_FAILED on error

image-20230204133015594

image-20230204133236552

flag可选值

  • MAP_FIXED:返回值必须等于addr
  • MAP_SHARED:对映射内存的操作会同步到文件中
  • MAP_PRIVATE:对映射内存的操作会同步到私有副本中

addroff的值需要与页对齐

有两个信号常用于映射区域

  • SIGSEGV:试图访问对我们不可用的内存
  • SIGBUS:访问没有意义的部分内存

内存映射区域可以通过fork继承,但是不会通过exec继承

mprotect

可以通过mprotect函数修改现存的映射区域的权限

1
2
3
4
5
#include <sys/mman.h>

int mprotect(void *addr, size_t len, int prot);

Returns:0 if OK,-1 on error

prot参数与mmap的一样,addr需要与页对齐

msync

用于将映射区域内的数据同步到文件中

1
2
3
4
#include <sys/mman.h>
int msync(void *addr, size_t len, int flags);

Returns:0 if OK, -1 on error

flag参数

  • MS_ASYNC简单地安排要写入的页面
  • MS_SYNC在返回之前等待写入完成
  • 可选标志MS_INVALIDATE允许告诉操作系统丢弃与底层存储不同步的任何页面。

munmap

取消映射

1
2
3
4
#include <sys/mman.h>
int munmap(void *addr, size_t len);

Returns:0 if OK, -1 on error

example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <fcntl.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
#define COPYINCR (1024*1024*1024)

int
main(int argc, char *argv[])
{
int fdin, fdout;
void *src, *dst;
struct stat sbuf;
size_t copysz;
off_t fsz = 0;

if (argc != 3)
{
fprintf(stderr,"usage: %s <froomfiile> <tofiile>",argv[0]);
exit(-1);
}
if ((fdin = open(argv[1], O_RDONLY)) < 0)
{
fprintf(stderr, "can't create %s for writing", argv[2]);
exit(-1);
}
if ((fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE))< 0)
{
fprintf(stderr, "can't create %s for writing",argv[2]);
exit(-1);
}
if (fstat(fdin, &sbuf) < 0)
{
fprintf(stderr, "fstat error");
exit(-1);
}
if (ftruncate(fdout, sbuf.st_size) < 0) //用于截断文件
{
fprintf(stderr, "ftruncate error");
exit(-1);
}

while (fsz < sbuf.st_size){
if ((sbuf.st_size - fsz) > COPYINCR)
copysz = COPYINCR;
else
copysz = sbuf.st_size - fsz;
if ((src = mmap(0, copysz, PROT_READ, MAP_SHARED, fdin, fsz)) == MAP_FAILED)
{
fprintf(stderr, "mmap error for input");
exit(-1);
}
if ((dst = mmap(0, copysz, PROT_READ | PROT_WRITE,MAP_SHARED,fdout,fsz)) == MAP_FAILED)
{
fprintf(stderr, "mmap error for output");
exit(-1);
}

memcpy(dst, src, copysz);
munmap(src, copysz);
munmap(dst, copysz);
fsz += copysz;

}
exit(0);
}

进程间通信(IPC)

pipe

管道有两个限制

  • 半双工通信,数据流只有一个方向
  • 管道只能在具有共同祖先的进程之间使用

FIFOs绕过了第二个限制,而Socket绕过了上诉两个限制

1
2
3
4
5
#include <unistd.h>

int pipe(int fd[2]);

Returns: if OK, -1 on error

fstat函数针对管道文件返回FIFO类型,可以通过S_ISFIFP宏获取。

image-20230207234736511

父进程关闭fd[0]读管道,子进程关闭fd[1]写管道,则父进程只往管道中写,而子进程只从管道中读数据。

image-20230207234928683

两个应用于管道的规则

  1. 当写管道关闭时,读管道会读到文件末尾,并返回已经读了的数据。
  2. 当读管道关闭时,写管道往管道中写会发出SIGPIPE信号。默认的信号处理为返回-1并设置errnoEPIPE

PIPE_BUF为管道缓冲区的常量值,写入少于该值时不会覆盖其他管道的缓冲区,但是超过该值则会覆盖其余管道的缓冲区。

父进程传字符串到子进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define MAXLINE 1024

int
main(void)
{
int n;
int fd[2];
pid_t pid;
char line[MAXLINE];

if (pipe(fd) < 0)
{
fprintf(stderr, "pipe error!\n");
exit(-1);
}
if ((pid = fork()) < 0){
fprintf(stderr,"fork error!\n");
exit(-1);
}else if (pid > 0){
close(fd[0]); //父进程写
write(fd[1], "hello world\n",12);
}else
{
/* code */
close(fd[1]);//子进程读
n = read(fd[0], line, MAXLINE);
write(STDOUT_FILENO, line, n);
}
exit(0);

}

实现管道功能,读指定文件并输入到more程序中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

#define DEF_PAGE "/bin/more"
#define MAXLINE 1024

int
main(int argc, char *argv[])
{
int n;
int fd[2];
pid_t pid;
char *pager, *argv0;
char line[MAXLINE];
FILE *fp;

if (argc != 2)
{
fprintf(stderr, "usgae: a.out <pathname>");
exit(-1);
}

if ((fp = fopen(argv[1], "r")) == NULL)
{
fprintf(stderr, "can't open %s", argv[1]);
exit(-1);
}

if (pipe(fd) < 0)
{
fprintf(stderr, "pipe error\n");
exit(-1);
}

if ((pid = fork()) < 0){
fprintf(stderr, "fork error!\n");
exit(-1);
}else if (pid > 0){
close(fd[0]);//父进程写
while (fgets(line, MAXLINE, fp) != NULL){ //从文件中逐行读取内容
n = strlen(line); // 获取字符长度
if (write(fd[1], line, n) != n) //写进管道内
{
fprintf(stderr, "write error to pipe");
exit(-1);
}
}
if (ferror(fp))
{
fprintf(stderr, "fgets error");
exit(-1);
}
close(fd[1]); //写完关闭写管道
if (waitpid(pid, NULL, 0) < 0) //等待子进程
{
fprintf(stderr, "waitpid error");
exit(-1);
}
exit(0);
}
else {
close(fd[1]); //子进程读管道
if (fd[0] != STDIN_FILENO)
{
if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO)//将管道描述符复制给STDIN_FILENO,然后close(fd[0])
{
fprintf(stderr, "dup2 error!\n");
exit(-1);
}
close(fd[0]);
}

if ((pager = getenv("PAGER") == NULL))
pager = DEF_PAGE;
if ((argv0 = strrchr(pager, '/')) != NULL) //从后往前搜索'/'
argv0++;
else
argv0 = pager;
if (execl(pager, argv0, (char *)0) < 0)
{
fprintf(stderr, "execl error for %s", pager);
exit(-1);
}

}
exit(0);
}

利用管道实现父子进程信号同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <stdio.h>
#include <stdlib.h>

static int pfd1[2], pfd2[2];

void
TELL_WAIT(void)
{
if(pipe(pfd1) < 0 || pipe(pfd2) < 0) //初始化两个管道
{
fprintf(stderr, "pipe error!\n");
exit(-1);
}
}

//pfd2父进程写,子进程读
void
TELL_PARENT(pid_t pid)
{
if (write(pfd2[1], "c", 1) != 1)
{
fprintf(stderr, "write error");
exit(-1);
}
}

void
WAIT_PARENT(void)
{
char c;
if (read(pfd1[0], &c, 1) != 1)
{
fprintf(stderr, "read error");
exit(-1);
}

if (c != 'p')
{
fprintf(stderr, "WAIT_PARENT: incorrect data");
exit(-1);
}
}

//pfd1为子进程写,父进程读
void
TELL_CHILD(pid_t pid)
{
if(write(pfd1[1], "p", 1) != 1)
{
fprintf(stderr, "write error");
exit(-1);
}
}

void
WAIT_CHILD(void)
{
char c;
if (read(pfd2[0], &c, 1) != 1)
{
fprintf(stderr, "read error");
exit(-1);
}

if (c != 'c')
{
fprintf("WAIT_CHILD: incorrect data");
exit(-1);
}
}

popen 和 pclose

popen用于创建管道、fork子管道、关闭管道未使用的端、执行shell命令以及等待命令终止。

1
2
3
4
5
6
7
#include <stdio.h>

FILE *popen(const char *cmdstring, const char *type);
Returns:file pointer if OK, NULL on error

int pclose(FILE *fp);
Returns:termination status of cmdstring, or -1 on error
  • type = r则父进程连接子进程的标准输出流

image-20230208184650475

  • type = w则父进程连接子进程的标准输入流

image-20230208184714204

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>

#define PAGER "${PAGER:-more}"
#define MAXLINE 1024


int
main(int argc, char *argv[])
{
char line[MAXLINE];
FILE *fpin, *fpout;

if (argc != 2)
{
fprintf(stderr, "usage: a.out <pathname>");
exit(-1);
}

if ((fpin = fopen(argv[1], "r")) == NULL)
{
fprintf(stderr, "can't open %s", argv[1]);
exit(-1);
}

if ((fpout = popen(PAGER, "w")) == NULL)
{
fprintf(stderr, "popen error");
exit(-1);
}

while (fgets(line, MAXLINE, fpin) != NULL) {
if (fputs(line, fpout) == EOF)
{
fprintf(stderr, "fputs error to pipe");
exit(-1);
}
}

if (ferror(fpin))
{
fprintf(stderr, "fgets error");
exit(-1);
}

if (pclose(fpout) == -1)
{
fprintf(stderr, "pclose error");
exit(-1);
}
exit(0);

}

使用管道实现字符大小写转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//gcc -o myuclc 15-14.c
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>

int
main(void)
{
int c;

while ((c = getchar()) != EOF){
if (isupper(c))
c = tolower(c);
if (putchar(c) == EOF)
{
fprintf(stderr, "output error\n");
exit(-1);
}
if (c == '\n')
fflush(stdout);
}
exit(0);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//gcc -o 15-15 15-15.c
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>

#define MAXLINE 1024

int
main(void)
{
char line[MAXLINE];
FILE *fpin;
if ((fpin = popen("./myuclc", "r")) == NULL)
{
fprintf(stderr, "popen error\n");
exit(-1);
}
for(;;){
fputs("prompt> ",stdout);
fflush(stdout);
if (fgets(line, MAXLINE, fpin) == NULL)
break;
if (fputs(line, stdout) == EOF)
{
fprintf(stderr, "fputs error to pipe");
exit(-1);
}
}
if (pclose(fpin) == -1)
{
fprintf(stderr, "pclose error");
exit(-1);
}
putchar('\n');
exit(0);
}

协作进程

image-20230209010328935

使用协作进程实现加法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define MAXLINE 1024

int main(void)
{
int n, int1, int2;
char line[MAXLINE];

while ((n = read(STDIN_FILENO, line, MAXLINE)) > 0)
{
line[n] = 0;
if (sscanf(line, "%d%d", &int1, &int2) == 2){
sprintf(line, "%d\n", int1 + int2);
n = strlen(line);
if (write(STDOUT_FILENO, line, n) != n)
{
fprintf(stderr, "write error");
exit(-1);
}
}
else {
if (write(STDOUT_FILENO, "invalid args\n",13) != 13)
{
fprintf(stderr, "write error");
exit(-1);
}
}

}
exit(0);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

#define MAXLINE 1024

static void sig_pipe(int);

int
main(void)
{
int n, fd1[2], fd2[2];
pid_t pid;
char line[MAXLINE];

if (signal(SIGPIPE, sig_pipe) == SIG_ERR) //注册信号处理
{
fprintf(stderr, "signal error!\n");
exit(-1);
}

if (pipe(fd1) < 0 || pipe(fd2) < 0) //建立两个管道
{
fprintf(stderr, "fork error!\n");
}
if ((pid = fork()) < 0)
{
fprintf(stderr, "fork error");
exit(-1);
}
else if (pid > 0){ //父进程
close(fd1[0]); //fd1写
close(fd2[1]); //fd2读

while (fgets(line, MAXLINE, stdin) != NULL){
n = strlen(line);
if (write(fd1[1], line, n) != n) //向管道中写数据
{
fprintf(stderr, "write error to pipe\n");
exit(-1);
}
if ((n = read(fd2[0], line, MAXLINE)) < 0) //从管道中读数据
{
fprintf(stderr, "read error from pipe");
exit(-1);
}

if (n == 0) //管道被关闭
{
fprintf(stderr, "child closed pipe");
break;
}
line[n] = 0;
if (fputs(line, stdout) == EOF)
{
fprintf(stderr, "fputs error");
exit(-1);
}
}

if (ferror(stdin))
{
fprintf(stderr, "fgets error on stdin");
exit(-1);
}
exit(0);
} else { //子进程
close(fd1[1]); //fd1为读
close(fd2[0]); //fd2为写
if (fd1[0] != STDIN_FILENO) {
if (dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO)
{
fprintf(stderr, "dup2 error to stdin");
exit(-1);
}
close(fd1[0]);
}

if (fd2[1] != STDOUT_FILENO){
if (dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO)
{
fprintf(stderr, "dup2 error to stdout");
exit(-1);
}
close(fd2[1]);
}
if (execl("./add2", "add2", (char *)0) < 0)
{
fprintf(stderr, "execl error");
exit(-1);
}
}
exit(0);
}

static void
sig_pipe(int signo)
{
fprintf(stderr, "SIGPIPE caught\n");
exit(1);
}

改写add2文件,使用标准I/O库函数实现对管道的读写,会造成死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <stdlib.h>

#define MAXLINE 1024


int
main(void)
{
int int1, int2;
char line[MAXLINE];

while (fgets(line, MAXLINE, stdin) != NULL){
if (sscanf(line, "%d%d", &int1, &int2) == 2){
if (printf("%d\n", int1 + int2) == EOF)
{
fprintf(stderr, "printf error!\n");
exit(-1);
}
}
else {
if (printf("invalid args\n") == EOF)
{
fprintf(stderr, "printf error");
exit(-1);
}
}
}
exit(0);
}

采用fgets函数从标准输入流中读取数据,由于标准输入流指向管道,因此在Linux下会默认为全缓冲,那么调用fgets函数时会进行阻塞,等到管道中的数据被填充满时才进行数据的读取,在父进程中同样通过fgets函数进行数据读取,同样为全缓冲,那么造成父进程也在等到管道中的数据被填满后才读取数据,导致了父子进程互相等待的局面,从而导致了死锁

改进方法,将标准输入输出流修改为行缓冲或无缓冲模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <stdio.h>
#include <stdlib.h>

#define MAXLINE 1024


int
main(void)
{
int int1, int2;
char line[MAXLINE];
/*无缓冲
if (setvbuf(stdin, NULL, _IONBF, 0) != 0)
{
fprintf(stderr, "setvbuf error\n");
exit(-1);
}
if (setvbuf(stdout, NULL, _IONBF, 0) != 0)
{
fprintf(stderr, "setvbuf error\n");
ex
*/
//行缓冲
if (setvbuf(stdin, NULL, _IOLBF, 0) != 0)
{
fprintf(stderr, "setvbuf error\n");
exit(-1);
}
if (setvbuf(stdout, NULL, _IOLBF, 0) != 0)
{
fprintf(stderr, "setvbuf error\n");
exit(-1);
}
while (fgets(line, MAXLINE, stdin) != NULL){
if (sscanf(line, "%d%d", &int1, &int2) == 2){
if (printf("%d\n", int1 + int2) == EOF)
{
fprintf(stderr, "printf error!\n");
exit(-1);
}
}
else {
if (printf("invalid args\n") == EOF)
{
fprintf(stderr, "printf error");
exit(-1);
}
}
}
exit(0);
}

FIFOs

FIFOs有时也被称之为命名管道。未命名管道只能是相关的进程间进行数据交互,即由共同祖先创造的子进程之间。但是命名管道可以使得不相关的进程之间也交换数据。

创建FIFO文件

1
2
3
4
#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);
Both return: 0 if OK, -1 on error
  • 如果path为绝对路径,则忽略fd参数,此时mkfifoatmkfifo行为类似
  • path如果为相对路径,并且fd指向合法的被打开的目录文件描述符,那么path则相对于fd指向目录进行寻址
  • 如果fd的值为AT_FDCWD,那么path则相对于当前目录进行寻址

O_NONBLOCK标志会影响FIFO文件的行为

  • 如果为设置该标志,那么以只读方式打开FIFO文件会进行阻塞直到有以只写方式打开FIFO文件的进程出现,相反以只写方式打开FIFO文件的进程也会等到只读形式打开FIFO文件的进程出现。

FIFOs可以用于以下两个场景

  • FIFOs被用于shell从一个管道传递数据给另一个,并且不需要创建中间的临时文件
1
2
3
mkfifo fifo1
prog3 < fifo1 &
prog1 < infile | tee fifo1 | prog2

image-20230211000205461

  • FIFOs被用于客户-服务应用程序中传递数据的中间集合点

image-20230211003608390

客户经过FIFO发送请求给服务,并且FIFO的路径是所有客户都知道的。

但是为了使得服务能够给每个客户发送回应包,客户在给服务发送请求包时需要携带自己的进程号,便于让服务识别。当客户关闭FIFO时会给服务发送SIGPIPE的信号,因此服务必须处理该信号。

XSI IPC

有三种类型的进程间通信被称之为XSI IPC

  • 信息队列
  • 信号量
  • 共享队列

Identifiers与Keys

Identifiers用于内核空间使用,用于标记IPC对象

Keys用于用户空间使用

有许多方法可以使得服务与客户使用相同的IPC结构

  • 使用IPC_PRIVATE的键值新建IPC的结构,并且会返回identifiers给客户,但是该方法的缺陷为客户需要利用文件操作去读取identifiers
  • 服务与客户使用相同的键值,但是该方法的问题是键值可能已经被使用
  • 服务与客户通过文件名与对象ID生成键值,然后使用上述第二个方法。

通过路径与id值生成键

1
2
3
4
5
#include <sys/ipc.h>

key_t ftok(const char *path, int id);

Returns:key if OK, (key_t)-1 on error

Permission Structure

ipc_perm结构与每个IPC相关联的。这个结构定义了权限和拥有者。

1
2
3
4
5
6
7
8
struct ipc_perm {
uid_t uid; /* owner’s effective user ID */
gid_t gid; /* owner’s effective group ID */
uid_t cuid; /* creator’s effective user ID */
gid_t cgid; /* creator’s effective group ID */
mode_t mode; /* access modes */
...
};

消息队列

消息队列是存储在内核中并被链表链接的信息,通过identified识别队列,也称之为queue ID

消息队列相关的结构体msqid_ds

1
2
3
4
5
6
7
8
9
10
11
struct msqid_ds {
struct ipc_perm msg_perm; /* see Section 15.6.2 */
msgqnum_t msg_qnum; /* # of messages on queue */
msglen_t msg_qbytes; /* max # of bytes on queue */
pid_t msg_lspid; /* pid of last msgsnd() */
pid_t msg_lrpid; /* pid of last msgrcv() */
time_t msg_stime; /* last-msgsnd() time */
time_t msg_rtime; /* last-msgrcv() time */
time_t msg_ctime; /* last-change time */
...
};

msgget

1
2
3
4
#include <sys/msg.h>

int msgget(key_t key, int flag);
Returns:meessage queue ID if OK, -1 on error

当队列被创建完毕后,msqid_ds的成员会进行初始化

  • ipc_perm结构体将被初始化,并且权限相关的标志位将会被设置。
  • msg_qnummsg_lspidmsg_stimemsg_rtime被设置为0
  • msg_ctime被设置为当前时间
  • msg_qbytes被设置为系统的限制

msgctl

针对消息队列执行操作

1
2
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • IPC_STAT:获取msqid_ds结构体信息
  • IPC_SET:将buf指向的msg_perm.uidmsg_perm.gidmsg_prerm.modemsg_gbytes拷贝到与消息队列相关的msqid_ds结构
  • IPC_RMID:从系统中删除消息队列以及队列中所有数据

msgsnd

将数据放入消息队列中

1
2
#include <sys/msg.h>
int msgnd(int msqid, const void *ptr, size_t nbytes, int flag);

如果发送的最大数据为512字节,则可以使用下面的结构体

1
2
3
4
struct mymesg{
long mtype; //消息类型
char mtext[512]; //消息数据,长度为n字节
};

msgrcv

检索消息队列中的消息

1
2
3
4
5
#include <sys/msg.h>

ssize_t msgrcv(int msgid, void *ptr, size_t nbytes, long type, int flag);

Returns:size of data portion of message if OK, -1 on error

type参数指定需要返回哪个参数

  • type == 0:队列中的第一个消息返回
  • type > 0:返回与type一致的消息
  • type < 0:第一个小于或等于type绝对值的消息

信号量

为了获得共享资源,进程需要做以下操作

  • 测试控制资源的信号量
  • 如果信号量的值是正数,则进程获得该资源,并且把信号量的值减去一
  • 如果信号量的值为0,则进程需要休眠等待直到信号量的的值大于一,当进程被唤醒,重复操作一

信号量结构体semid_ds

1
2
3
4
5
6
7
struct semid_ds {
struct ipc_perm sem_perm; /* see Section 15.6.2 */
unsigned short sem_nsems; /* # of semaphores in set */
time_t sem_otime; /* last-semop() time */
time_t sem_ctime; /* last-change time */
...
};

每个信号量由至少包含以下成员的匿名结构表示

1
2
3
4
5
6
7
struct {
unsigned short semval; /* semaphore value, always >= 0 */
pid_t sempid; /* pid for last operation */
unsigned short semncnt; /* # processes awaiting semval>curval */
unsigned short semzcnt; /* # processes awaiting semval==0 */
...
};

semget

获取信号量的id值

1
2
3
#include <sys/sem.h>
int semget(key_t key, int nsems, int flag);
Returns:semaphore ID if OK, -1 on error

semctl

操作信号量

1
2
3
4
5
6
7
8
9
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, ...)
/*
Returns:(see following)
For all the GET commands other than GETALL, the function returns the corresponding
value. For the remaining commands, the return value is 0 if the call succeeds. On error,
the semctl function sets errno and returns −1
*/

第四个为可选参数,若存在则为senum类型

1
2
3
4
5
union semun {
int val; /* for SETVAL */
struct semid_ds *buf; /* for IPC_STAT and IPC_SET */
unsigned short *array; /* for GETALL and SETALL */
};

cmd为执行的命令,命令选择如下

  • IPC_STAT:获取此集合的semid_ds结构,将其存储在arg.buf指向的结构中。

  • IPC_SET:从与此集关联的semid_ds结构中arg.buf指向的结构中设置sem_perm.uidsem_perm.gidsem_perm.mode字段。此命令只能由有效用户ID等于sem_perm.cuidsem_perm.uid的进程或具有超级用户权限的进程执行

  • IPC_RMID:从系统中删除信号量集。此删除是立即的。任何其他仍在使用该信号量的进程在其下一次尝试对该信号量进行操作时都会收到EIDRM错误。

    此命令只能由有效用户ID等于sem_perm.cuidsem_perm.uid的进程或具有超级用户权限的进程执行。

  • GETVAL:返回成员semnumsemval

  • SETVAL:设置成员semnumsemval值。该值由arg.val指定

  • GETPID:返回成员semnumsempid

  • GETNCNT:返回成员semnumsemncnt

  • GETZCNT:返回成员semnumsemzcnt

  • GETALL:获取集合中的所有信号量值。这些值存储在arg.array指向的数组中

  • SETALL:将集合中的所有信号量值设置为arg.array指向的值

semop

原子地对信号集执行一系列操作

1
2
3
#include <sys/sem.h>
int semop(int semid, struct sembuf semoparray[], size_t nops);
Returns: 0 if OK, −1 on error

semoparray参数是指向信号量操作数组的指针,由sembuf结构表示

1
2
3
4
5
struct sembuf {
unsigned short sem_num; /* member # in set (0, 1, ..., nsems-1) */
short sem_op; /* operation (negative, 0, or positive) */
short sem_flg; /* IPC_NOWAIT, SEM_UNDO */
};

结构体sembuf中地sem_op变量定义了对信号集的操作

  • sem_op为正值,则对应的进程归还资源。sem_op的值被加到信号量的值上。如果指定undo标志,则还将该进程的信号量调整值减去sem_op
  • sem_op为负值,希望获得信号量控制的资源。则从信号量值中减去sem_op绝对值。这保证了生成的信号量值大于或等于0。如果指定了undo标志,则sem_op的绝对值也会添加到此进程的信号量调整值中。
    • 如果信号量的值小于sem_op的绝对值,则以下条件使用
      • 指定了IPC_NOWAIT,则semop返回错误EAGAIN
      • 未指定IPC_NOWAIT,则信号量的semncnt值递增,并且调用过程暂停,直到出现以下情况
        • 信号量的值大于或等于sem_op的绝对值,此时信号量的semncnt值减少,并且从信号量的值中减去sem_op的绝对值。如果指定了undo标志,则sem_op的绝对值也会添加到此进程的信号量调整值中。
        • 信号量从系统中被移除。函数返回EIDRM
        • 信号被捕捉,信号量中的semncnt值递减,并且函数返回EINTR
  • sem_op值为0,代表进程需要等待直到信号量的值为0
    • 如果信号量的值当前为0,则函数立即返回
    • 如果信号量的值是非零,则发生以下的情况
      • 如果IPC_NOWAIT被指定,则error被标记为EAGIN
      • 如果IPC_NOWAIT被指定,则semzcnt的值递增,并且调用进程悬挂直到以下的情况发生
        • 信号量的值变为0。信号量的semzcnt递减
        • 信号量从系统中被移除。函数返回并将error设置为EIDRM
        • 信号被捕捉。信号量的semzcnt递减,并且函数返回并将error设置为EINTR

共享内存

共享内存允许两个或多个进程共享给定的内存区域。

共享内存的结构体

1
2
3
4
5
6
7
8
9
10
11
struct shmid_ds {
struct ipc_perm shm_perm; /* see Section 15.6.2 */
size_t shm_segsz; /* size of segment in bytes */
pid_t shm_lpid; /* pid of last shmop() */
pid_t shm_cpid; /* pid of creator */
shmatt_t shm_nattch; /* number of current attaches */
time_t shm_atime; /* last-attach time */
time_t shm_dtime; /* last-detach time */
time_t shm_ctime; /* last-change time */
...
};

shmget

获取共享内存的identifier

1
2
3
4
#include <sys/shm.h>

int shmget(key_t key, size_t size, int flag);
Returns:shared memory ID if OK, -1 on error

shmctl

操作共享内存

1
2
3
4
5
#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

Returns:0 if OK, -1 on error

cmd指示了操作的行为

  • IPC_STAT:从段中获取shmid_ds结构体,并且将这个结构存入buf
  • IPC_SET:从与此共享内存段关联的shmid_ds结构中buf指向的结构中设置以下三个字段:shm_perm.uidshm_perm_gidshm_perm.mode。此命令只能由有效用户ID等于shm_permcuidshm_perk.uid的进程或具有超级用户权限的进程执行。
  • IPC_RMID:从系统中删除共享内存段集。由于为共享内存段(shmid_ds结构中的shm_attch字段)保留了一个附加计数,因此在使用该段的最后一个进程终止或分离该段之前,不会删除该段。无论该段是否仍在使用,都会立即删除该段的标识符,以便shmat不再附加该段。此命令只能由有效用户ID等于shm_perm.uidshm_perm.uid的进程或具有超级用户权限的进程执行。

LINUX额外增加的命令

  • SHM_LOCK:将共享内存段锁定在内存中。此命令只能由超级用户执行。
  • SHM_UNLOCK:解锁共享内存段。此命令只能由超级用户执行。

shmat

共享存储段连接到调用进程的哪个地址上与addr参数以及flag中是否制定SHM_RND位有关

1
2
3
4
#include <sys/shm.h>

void *shmat(int shmid, const void *addr, int flag);
//Returns: pointer to shared memory segment if OK, -1 on error
  • addr为0:则此段链接到由内核选择的第一个可用地址上。

  • addr非0并且SHM_RND没被指定,该段被附着的地址为addr

  • addr非0并且指定SHM_RND,该段被附着的地址为addr-(addr % SHMLBA).SHM_RND为取整。

  • flag的指定了SHM_RDONLY位,则以只读形式连接此段,否则以读写形式连接此段。

shmdt

使用shmdt函数可以将地址从共享存储段中分离。并不是删除,直到带IPC_RMID命令的调用shmctl特地删除。

1
2
3
4
#include <sys/shm.h>

int shmdt(const vidd *addr);
//返回值:成功返回0,出错,返回-1

example1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>

#define ARRAY_SIZE 40000
#define MALLOC_SIZE 100000
#define SHM_SIZE 1000000
#define SHM_MODE 0600

char array[ARRAY_SIZE];

int
main(void)
{
int shmid;
char *ptr, *shmptr;

printf("array[] from %p to %p\n",(void *)&array[0], (void *)&array[ARRAY_SIZE]);
printf("stack around %p\n",(void *)&shmid);

if ((ptr = malloc(MALLOC_SIZE)) == NULL)
{
fprintf(stderr, "malloc error");
exit(-1);
}

printf("malloced from %p to %p\n", (void *)ptr, (void *)ptr+MALLOC_SIZE);

if ((shmid = shmget(IPC_PRIVATE, SHM_SIZE, SHM_MODE)) < 0)
{
fprintf(stderr, "shmget error");
exit(-1);
}

if ((shmptr = shmat(shmid, 0, 0)) == (void *)-1)
{
fprintf(stderr, "shmat error");
exit(-1);
}
printf("shared memory attached from %p to %p\n", (void *)shmptr, (void *)shmptr+SHM_SIZE);

if (shmctl(shmid, IPC_RMID, 0) < 0)
{
fprintf(stderr, "shmctl error");
exit(-1);
}
exit(0);
}

example2

使用/dev/zero设备使得两个相关进程共享一段匿名内存

使用/dev/zero设备的好处

  • 创建一个未命名的存储区,长度为mmap的第二个参数,页对齐
  • 存储区都初始化为0
  • 若指定了MAP_SHARED则进程共享此区域
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

#define NLOOPS 1000
#define SIZE sizeof(long)

static int pfd1[2], pfd2[2];

void
TELL_WAIT(void)
{
if(pipe(pfd1) < 0 || pipe(pfd2) < 0) //初始化两个管道
{
fprintf(stderr, "pipe error!\n");
exit(-1);
}
}

//pfd2父进程写,子进程读
void
TELL_PARENT(pid_t pid)
{
if (write(pfd2[1], "c", 1) != 1)
{
fprintf(stderr, "write error");
exit(-1);
}
}

void
WAIT_PARENT(void)
{
char c;
if (read(pfd1[0], &c, 1) != 1)
{
fprintf(stderr, "read error");
exit(-1);
}

if (c != 'p')
{
fprintf(stderr, "WAIT_PARENT: incorrect data");
exit(-1);
}
}

//pfd1为子进程写,父进程读
void
TELL_CHILD(pid_t pid)
{
if(write(pfd1[1], "p", 1) != 1)
{
fprintf(stderr, "write error");
exit(-1);
}
}

void
WAIT_CHILD(void)
{
char c;
if (read(pfd2[0], &c, 1) != 1)
{
fprintf(stderr, "read error");
exit(-1);
}

if (c != 'c')
{
fprintf("WAIT_CHILD: incorrect data");
exit(-1);
}
}

static int
update(long *ptr)
{
return ((*ptr)++);
}

int
main(void)
{
int fd, i, counter;
pid_t pid;
void *area;

if ((fd = open("/dev/zero", O_RDWR)) < 0)
{
fprintf(stderr, "open error");
exit(-1);
}
if ((area = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) //将/dev/zero文件映射到内存中
{
fprintf(stderr, "mmap error");
exit(-1);
}
close(fd);

TELL_WAIT(); //注册管道

if ((pid = fork()) < 0){
fprintf(stderr, "fork error");
exit(-1);
} else if (pid > 0){ //父进程
for (i = 0; i < NLOOPS; i += 2){
if ((counter = update((long *)area)) != i)
{
fprintf(stderr, "parent: expected %d, got %d",i, counter);
exit(-1);
}

TELL_CHILD(pid);
WAIT_CHILD();
}
} else {
for (i = 1; i < NLOOPS + 1; i += 2){
WAIT_PARENT();

if ((counter = update((long *)area)) != i)
{
fprintf(stderr, "child: expected %d, got %d", i, counter);
exit(-1);
}

TELL_PARENT(getppid());
}
}
exit(0);
}

匿名映射

MAP_ANON为建立匿名映射区域

1
mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0)

POSIX信号量

POSX信号量分为命名信号量与匿名信号量

sem_open

创建和使用命名信号量

1
2
3
4
#include <semaphore.h>

sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
//返回值:成功,返回指向信号量的指针;若出错,返回SEM_FAILED
  • 当使用现有的信号量时,只需要指定nameoflag参数为0即可。
  • 当使用oflagO_CREAT标志时,若信号量不存在则会创建,若存在则直接使用
    • 需要额外指定两个参数
      • mode:谁可以访问该信号量
      • value:指定信号量的初始化值,范围为0-SEM_VALUE_MAX
  • 若想确保信号量为新建,则可以指定oflagO_CREAT|O_EXCL,若信号量已存在则会导致sem_open失败

sem_close

关闭信号量

1
2
3
#include <semaphore.h>
int sem_close(sem_t *sem);
//返回值:若成功,返回0;若出错,返回-1

销毁命名信号量,若没有对信号量的引用则直接销毁,若存在则等待到没引用时进行销毁。

1
2
3
4
5
#include <semaphore.h>

int sem_unlink(const char *name);

//返回值:若成功,返回0;若出错,返回-1

sem_wait和sem_trywait

对信号量进行减一操作

1
2
3
4
5
6
#include <semaphore.h>

int sem_trywait(sem_t *sem);
int sem_wait(sem_t *sem);

//两个函数的返回值:若成功,返回0;若出错则返回-1
  • sem_wait,若信号量为0会阻塞
  • sem_trywait,若信号量为0不会阻塞,而是直接返回-1并且将errno设置为EAGAIN

sem_timedwait

阻塞一段时间

1
2
3
4
#include <semaphore.h>
#include <time.h>
int sem_timedwait(sem_t *restrict sem,const struct timespec *restrict tsptr);
//返回值:若成功,返回0;若出错,返回-1
  • 若超时则直接返回-1,将errno设置为ETIMEDOUT

sem_post

将信号量值增加1

1
2
3
4
#include <semaphore.h>
int sem_post(sem_t *sem);

//返回值:若成功,返回0;若出错,返回-1

sem_init

创建未命名的信号量

1
2
3
4
#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);
//返回值:若成功,返回0;若出错,返回-1
  • pshared:表明是否再多个进程中使用变量
    • 是,非零值
  • value:指定了信号量的初始值

sem_destory

用于丢弃信号量

1
2
3
4
#include <semaphore.h>
int sem_destroy(sem_t *sem);

//返回值:若成功,返回0;若出错,返回-1

sem_getvalue

检索信号量值

1
2
3
4
#include <semaphore.h>

int sem_getvalue(sem_t *restrict sem, int *restrict valp);
//返回值:若成功,返回0;若出错,返回-1.
  • 函数执行成功后,值会被填充到valp

15-35.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <semaphore.h>
#include <fcntl.h>

struct slock{
sem__t *semp;
char name[_POSIX_NAME_MAX];
};

struct slock *
s_alloc()
{
struct slock *sp;
static int cnt;

if ((sp = malloc(sizeof(struct slock))) == NULL)
return(NULL);
do{
snprintf(sp->name, sizeof(sp->name), "/%ld.%d", (long)getpid(), cnt++); //记录信号量的名字
sp->semp = sem_open(sp->name, O_CREAT|O_EXCL, S_IRWXU, 1); //打开了信号量
} while (((sp->semp == SEM_FAILED) && (errno == EEXIST));
if (sp->semp == SEM_FAILED){
free(sp);
return(NULL);
}
sem_unlink(sp->name);
return(sp);
}

void
s_free(struct slock *sp)
{
sem_close(sp->semp);
free(sp);
}

int
s_lock(struct slock *sp)
{
return(sem_wait(sp->semp));
}

int
s_trylock(struct slock *sp)
{
return(sem_trywait(sp->semp));
}

int
s_unlock(struct slock *sp)
{
return(sem_post(sp->semp));
}

网络IPC:套接字

socket

套接字是一个端点的抽象

1
2
3
4
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

//返回值:若成功,返回文件(套接字)描述符;若出错,返回-1
  • domain:通信的特性,包括地址格式,各个域的常数以AF_开头,表示地址族
    • AF_INETIPv4因特尔域
    • AF_INET6IPv6因特尔域
    • AF_UNIXUNIX
    • AF_UNSPEC:未指定
  • type:套接字类型
    • SOCK_DGRAM:固定长度、无连接的、不可靠的报文传递
    • SOCK_RAWIP协议的数据报接口
    • SOCK_SEQPACKET:固定长度的、有序的、可靠的、面向连接的报文传递
    • SOCK_STREAM:有序的、可靠的、双向的、面向连接的字节流
  • protocol:协议类型,若为0则使用默认协议
    • IPPROTO_IPIPv4网际协议
    • IPPROTO_IPV6IPv6网际协议
    • IPPROTO_ICMP:控制报文协议
    • IPPROTO_TCP:传输控制协议
    • IPPROTO_UDP:用户数据包协议

Linux内部,套接字被视为文件描述符,但是并不是所有对文件的操作都可以应用在套接字上

函数 使用套接字时的行为
close 释放套接字
Dup和dup2 和一般文件描述符一样复制
fchdir 失败,并且将errno设置为ENOTDIR
fchomod 未指定
fchown 由实现定义
fcntl 支持一些命令,包括F_DUPFD、F_DUPFD_CLOEXEC、F_GETFD、F_GETEFL、F_GETOWN、F_SETFD、F_SETFL和F_SETOWN
Fdatasync和fsync 由实现定义
fstat 支持部分stat结构体成员,具体由实现定义
ftruncate 未指定
ioctl 支持部分命令,依赖于底层设备驱动
lseek 由实现的定义(通常失败时会将errno设为ESPIPE)
mmap 未指定
poll 正常工作
Pread和Pwrite 失败是会将errno设为ESPIPE
read和readv 与没有任何标志位的recv等价
select 正常工作
write和writev 与没有任何标志位的send等价

shutdown

用来禁止一个套接字的I/O

1
2
3
4
#include <sys/socket.h>

int shutdown(int sockfd, int how);
//返回值:若成功,返回0;若出错,返回-1
  • how:用于指定禁止的行为
    • SHUT_RD:关闭读端,无法从套接字读取数据
    • SHUT_WR:关闭写端,无法从套接字发送数据
    • SHUT_RDWR:关闭读写端,即无法从套接字发送数据也无法读取数据

字节序转换

1
2
3
4
5
6
7
8
9
10
#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位整数

地址格式

为了使不同的地址都能够传入socket中,Linux使用相同的结构体管理地址

1
2
3
4
struct sockaddr {
sa_family_t sa_family; //地址族
char sa_data[14]; //地址长度
}

IPv4因特网域,套接字使用sockaddr_in表示

1
2
3
4
5
6
7
8
9
struct in_addr{
in_addr_t s_addr; //IPv4地址
};

struct sockaddr_in {
sa_family_t sin_family; //地址族
in_port_t sin_port; //端口号
struct in_addr sin_addr; //IPv4地址
};

IPv6因特网域,套接字使用sockaddr_in6表示

1
2
3
4
5
6
7
8
9
10
struct_in6_addr{
uint8_t sa_addr[16];
};
struct sockaddr_in6{
sa_faimly_t sin6_family; //地址族
in_port_t sin6_port; //端口号
uint32_t sin6_flowinfo; //流信息相关,暂时未实现
struct in6_addr sin6_addr; //IPv6的地址
uint32_t sin6_scope_id; //范围接口集
};

Linux中,sockaddr_in的定义如下

1
2
3
4
5
6
struct sockaddr_in {
sa_family_t sin_family; //地址族
in_port_t sin_port; //端口号
struct in_addr sin_addr; //IPv4地址
unsigned char sin_zero[8]; //填充字段
};

inet_ntop和inet_pton

用于二进制地址格式与点分十进制字符表示之间的相互转换

1
2
3
4
5
6
7
#include <arpa/inet.h>

const char *inet_ntop(int domain, const void *restrict addr,
char *restrict str, socklen_t size);
//返回值:若成功,返回地址字符串指针;若出错,返回NULL
int inet_pton(int domain, const char * restrict str, void *restrict addr);
//返回值:若成功,返回1;若格式无效,返回0;若出错,返回-1

gethostent

网络配置信息被存放在许多地方,例如在静态文件中/etc/hosts/etc/services,名字服务管理,如域名系统或者网络信息服务。通过gethostent,都可以找到给定计算机系统的主机信息。

1
2
3
4
5
6
7
#include <netdb.h>

struct hostent *gethostent(void);
//返回值:若成功,返回指针;若出错,返回NULL

void sethostent(int stayopen);
void endhostent(void);

hostent结构体

1
2
3
4
5
6
7
8
struct hostent{
char *h_name, //主机名
char **h_aliases; //别名
int h_addrtype; //地址类型
int h_length; //地址长度
char **h_addr_list; //指向网络地址数组
...
};

getnetbyaddr和getnetbyname

1
2
3
4
5
6
7
8
9
#include <netdb.h>

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);
//若成功返回指针, 若出错返回NULL

netent结构包含以下字段

1
2
3
4
5
6
7
struct netent {
char *n_name;
char **n_aliases;
int n_addrtype;
uint32_t n_net;
...
};

协议名字和协议编号之间映射

1
2
3
4
5
6
7
8
9
#include <netdb.h>

struct protoent *getprotobyname(const char *name);
struct protoent *getprotobynumber(int proto);
struct protoent *getprotoent(void);
//若成功,返回指针;若出错,返回NULL

void setprotoent(int stayopen);
void endprotoent(void);

protoent结构

1
2
3
4
5
6
struct protoent{
char *p_name; //协议名
char **p_aliases; //别名
int p_proto; //协议号
...
}

端口与服务映射

1
2
3
4
5
6
7
8
9
#include <netdb.h>

struct servent *getservbyname(const char *name, const char *proto);
struct servent *getserbyport(int port, const char *proto);
struct servent *getsetvent(void);
//若成功,返回指针,若出错,返回NULL

void setservent(int stayopen);
void endservent(void);

servent结构

1
2
3
4
5
6
7
struct servent{
char *s_name; //服务名
char **s_aliases; //别名
int s_port; //端口号
char *s_proto; //协议名
...
};

getaddrinfo

getaddrinfo函数允许将一个主机名和一个服务名映射到一个地址

1
2
3
4
5
6
7
8
9
10
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *restrict host,
const char *restrict service,
const struct addrinfo *restrict hint,
struct addrinfo **restrict res);
//返回值:若成功,返回0;若出错,返回非0错误码

void freeaddrinfo(struct addrinfo *ai);

getaddrinfo函数返回一个链表结构addrinfo。可以用freeaddrinfo来释放一个或多个这种结构,取决于用ai_next字段链接起来的结构有多少。

addrinfo结构

1
2
3
4
5
6
7
8
9
10
11
struct addrinfo{
int ai_flags; //自定义行为
int ai_family; //地址族
int ai_socktype; //socket的类型
int ai_protocol; //协议
socklen_t ai_addrlen; //地址长度
struct sockaddr *ai_addr; //地址
char *ai_canonname; //主机的规范名称
struct addrinfo *ai_next; //链表的下一个
...
}
  • hint:用于过滤地址的模板
标志 描述
AI_ADDRCONFIG 查询配置的地址类型(IPv4或IPv6)
AI_ALL 查询IPv4和IPv6地址
AI_CANONNAME 需要一个规范的名字
AI_NUMERICHOST 以数字格式指定主机地址,不翻译
AI_NUMERICSERV 将服务指定为数字端口号,不翻译
AI_PASSIVE 套接字地址用于监听绑定
AI_V4MAPPED 如没有找到IPv6地址,返回映射到IPv6格式的IPv4地址

gai_strerror

用于打印getaddrinfo的失败消息

1
2
3
#include <netdb.h>
const char *gai_strerror(int error);
//返回值:指向描述错误的字符串的指针

getnameinfo

getnameinfo函数将一个地址转换成一个主机名和一个服务名

1
2
3
4
5
6
7
#include <sys/socket.h>
#include <netdb.h>

int getnameinfo(const struct sockaddr *restrict addr, socklen_t alen,
char *restrict host, socklen_t hostlen,
char *restrict service, socklen_t servlen, int flags);
//返回值:若成功,返回0;若出错,返回非0值

flag参数提供了控制翻译的方式

标志 描述
NI_DGRAM 服务基于数据包而非基于流
NI_NAMEREQD 如果找不到主机名,将其作为一个错误对待
NI_NOFQDN 对于本地主机,仅返回全限定域名的节点名部分
NI_NUMERICHOST 返回主机地址的数字形式
NI_NUMERICSCOPE 对于IPv6,返回范围ID的数字形式
NI_NUMERICSERV 返回服务地址的数字形式,即端口号

16-9.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#include <stdio.h>
#include <stdlib.h>

#if defined(SOLARIS)
#include <netinet/in.h>
#endif

#include <netdb.h>
#include <arpa/inet.h>

#if defined(BSD)
#include <sys/socket.h>
#include <netinet/in.h>
#endif

void
print_family(struct addrinfo *aip)
{
printf(" family \n");
switch(aip->ai_family){
case AF_INET:
//IPv4
printf("inet\n");
break;
case AF_INET6:
//IPv6
printf("inet6\n");
break;
case AF_UNIX:
printf("unix\n");
break;
case AF_UNSPEC:
printf("unspecified\n");
break;
default:
printf("unknown\n");
break;
}
}

void
print_type(struct addrinfo *aip)
{
printf(" type \n");
switch (aip->ai_socktype){
case SOCK_STREAM:
printf("stream\n");
break;
case SOCK_DGRAM:
printf("datagram\n");
break;
case SOCK_SEQPACKET:
printf("seqpacket\n");
break;
case SOCK_RAW:
printf("raw\n");
break;
default:
printf("unknow (%d)\n", aip->ai_socktype);
}
}

void
print_protocol(struct addrinfo *aip)
{
printf(" protocol \n");
switch (aip->ai_protocol)
{
case 0:
printf("default\n");
break;
case IPPROTO_TCP:
printf("TCP\n");
break;
case IPPROTO_UDP:
printf("UDP\n");
case IPPROTO_RAW:
printf("raw\n");
break;
default:
printf("unknow (%d)\n", aip->ai_protocol);
break;
}
}

void
print_flags(struct addrinfo *aip)
{
printf("flags\n");
if (aip->ai_flags == 0){
printf(" 0");
} else {
if (aip->ai_flags & AI_PASSIVE)
printf(" passive\n");
if (aip->ai_flags & AI_CANONNAME)
printf(" cannon\n");
if (aip->ai_flags & AI_NUMERICHOST)
printf(" numhost\n");
if (aip->ai_flags & AI_NUMERICSERV)
printf(" numserv\n");
if (aip->ai_flags & AI_V4MAPPED)
printf(" v4mapped\n");
if (aip->ai_flags & AI_ALL)
printf(" all\n");
}
}

int
main(int argc, char *argv[])
{
struct addrinfo *ailist, *aip;
struct addrinfo hint;
struct sockaddr_in *sinp;
const char *addr;
int err;
char abuf[INET_ADDRSTRLEN];

if (argc != 3)
{
fprintf(stderr, "usage: %s nodename service", argv[0]);
exit(-1);
}

hint.ai_flags = AI_CANONNAME;
hint.ai_family = 0;
hint.ai_socktype = 0;
hint.ai_protocol = 0;
hint.ai_addrlen = 0;
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;
if ((err = getaddrinfo(argv[1], argv[2], &hint, &ailist)) != 0)
{
fprintf(stderr, "getaddrinfo erro: %s", gai_strerror(err));
exit(-1);
}
for (aip = ailist; aip != NULL; aip = aip->ai_next){
print_flags(aip);
print_family(aip);
print_type(aip);
print_protocol(aip);
printf("\n\thost %s", aip->ai_canonname ? aip->ai_canonname:"-");
if (aip->ai_family == AF_INET) {
sinp = (struct sockaddr_in *)aip->ai_addr;
addr = inet_ntop(AF_INET, &sinp->sin_addr, abuf, INET_ADDRSTRLEN);
printf(" address %s", addr?addr:"unknown");
printf(" port %d", ntohs(sinp->sin_port));
}
printf("\n");
}
exit(0);
}

bind

服务器使用bind函数关联地址与套接字,服务器通常选择保留一个地址并且注册在/etc/setvices或者某个名字服务中。

1
2
3
4
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
//返回值:若成功,返回0;若出错,返回-1

getsockname

调用getsockname函数来发现绑定到套接字上的地址

1
2
3
4
5
#include <sys/socket.h>

int getsockname(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp);

//返回值:若成功,返回0;若出错,返回-1
  • alenp:指向整数指针,里面存放着缓冲区sockaddr的长度,若成功执行则将返回长度存放到该整数中

getpeername

若与对等方建立了连接,那么通过getpeername函数可以获得对方绑定的地址

1
2
3
#include <sys/socket.h>

int getpeername(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp);

connect

connect函数用于需要建立客户端与服务器之间的连接,例如SOCK_STREAMSOCK_SEQPACKET

1
2
3
4
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
//返回值:若成功,返回0;若出错,返回-1
  • sockfd:本地的套接字
  • addr:需要建立连接的远程地址

16-10.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>

#define MAXSLEEP 128

int
connect_retry(int sockfd, const struct sockaddr *addr, socklen_t alen) //重连
{
int numsec;

for (numsec = 1; numsec <= MAXSLEEP; numsec <<= 1){
if (connect(sockfd, addr, alen) == 0){
return 0; //连接出错
}

if (numsec <= MAXSLEEP/2)
sleep(numsec); //休眠时间 1 2 4 8.....64 65 66 ....128
}
return(-1);
}

16-11.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>


#define MAXSLEEP 128

int
connect_retry(int domain, int type, int protocol, const struct sockaddr *addr, socklen_t alen)
{
int numsec, fd;
for (numsec = 1; numsec <= MAXSLEEP; numsec <<= 1)
{
if ((fd = socket(domain, type, protocol)) < 0)
return(-1);
if (connect(fd, addr, alen) == 0){
return(fd);
}
close(fd);

if (numsec <= MAXSLEEP/2)
sleep(numsec);
}
return(-1);
}

listen

服务器调用listen函数表示愿意接受请求

1
2
3
4
#include <sys/socket.h>

int listen(int sockfd, int backlog);
//返回值:若成功,返回0;若出错,返回-1
  • backlog:提供提示,用于指示系统该进程所要入队的未完成连接请求数量

accept

服务器用于接收连接

1
2
3
#include <sys/socket.h>
int accpet(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);
//返回值:若成功,返回文件描述符;若出错返回-1
  • addr:用于存储客户端的地址,不需要时可以设置为NULL
  • len:缓冲区的长度

16-12.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <errno.h>
#include <sys/socket.h>

int
initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen)
{
int fd;
int err = 0;
if ((fd = socket(addr->sa_family, type, 0)) < 0)
{
return(-1);
}
if (bind(fd, addr, alen) < 0)
goto errout;
if (type == SOCK_STREAM || type == SOCK_SEQPACKET)
{
if (listen(fd, qlen) < 0)
goto errout;
}
return(fd);
errout:
err = errno;
close(fd);
errno = err;
return(-1);
}

send

用于网络套接字发送数据,才使用这个函数之前,连接已经建立完毕

1
2
3
4
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
//返回值:若成功,返回发送的字节数,若出错,返回-1

image-20230225192531323

sendto

send函数的唯一区别是该函数可以携带地址

1
2
ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags, const struct sockaddr *destaddr, socklen_t destlen);
//返回值:若成功,返回发送的字节数;若出错,返回-1

sendmsg

与上两个发送函数的区别,信息以msghdr结构体携带

1
2
3
4
#include <sys/socket.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

//返回值:若成功,返回发送的字节数;若出错,返回-1

msghdr结构体

1
2
3
4
5
6
7
8
9
10
struct msghdr {
void *msg_name; /* optional address */
socklen_t msg_namelen; /* address size in bytes */
struct iovec *msg_iov; /* array of I/O buffers */
int msg_iovlen; /* number of elements in array */
void *msg_control; /* ancillary data */
socklen_t msg_controllen; /* number of ancillary bytes */
int msg_flags; /* flags for received message */
...
}

recv

1
2
3
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
//返回值:返回数据的字节长度;若无可用数据或对等方已经按序结束,返回0;若出错,返回-1

image-20230225194325201

recvfrom

recvfrom函数与recv函数的区别在于可以接收发送信息方的地址

1
2
3
4
#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *restrict buf,size_t len, int flags, struct sockaddr *restrict addr, socklen_t *restrict addrlen);
//返回值:返回数据的字节长度;若无可用数据或对等方已经按序结束,返回0;若出错,返回-1

recvmsg

使用msghdr结构体接收信息

1
2
3
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
//返回值:返回数据的字节长度;若无可用数据或对等方已经按序结束,返回0:若出错,返回-1

image-20230225200104067

16-16.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <netdb.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>

#define BUFLEN 128

extern int connect_retry(int, int, int, const struct sockaddr *, socklen_t);

void
print_uptime(int sockfd)
{
int n;
char buf[BUFLEN];

while ((n = recv(sockfd, buf, BUFLEN, 0)) > 0)
{
printf("writing\n");
write(STDOUT_FILENO, buf, n);
}
if (n < 0)
{
fprintf(stderr, "recv error\n");
exit(-1);
}
}

int main(int argc, char *argv[])
{
struct addrinfo *ailist, *aip;
struct addrinfo hint;
int sockfd, err;

if (argc != 2)OP[E]
{
fprintf(stderr, "usage: ruptime hostname");
exit(-1);
}

memset(&hint, 0, sizeof(hint));
hint.ai_socktype = SOCK_STREAM; //TCP连接
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;
if ((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0) //获取服务器名与服务对应的地址和端口
{
fprintf(stderr, "getaddrinfo error: %s", gai_strerror(err));
exit(-1);
}
for (aip = ailist; aip != NULL; aip = aip->ai_next){
if ((sockfd = connect_retry(aip->ai_family, SOCK_STREAM, 0, aip->ai_addr, aip->ai_addrlen)) < 0){
err = errno;
} else {
print_uptime(sockfd);
exit(0);
}
}
fprintf(stderr, "can't connect to %s",argv[1]);
exit(-1);

}

16-17.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>

#define BUFLEN 1024

extern int connect_retry(int, int, int, const struct sockaddr *, socklen_t);


void
print_uptime(int sockfd)
{
int n;
char buf[BUFLEN];

while ((n = recv(sockfd, buf, BUFLEN, 0)) > 0)
{
printf("writing\n");
write(STDOUT_FILENO, buf, n);
}
if (n < 0)
{
fprintf(stderr, "recv error\n");
exit(-1);
}
}

int main(int argc, char *argv[])
{
struct addrinfo *ailist, *aip;
struct addrinfo hint;
int sockfd, err;

memset(&hint, 0, sizeof(hint));
hint.ai_socktype = SOCK_STREAM;
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;

if ((err = getaddrinfo(argv[1], "ruptimed", &hint, &ailist)) != 0)
{
fprintf(stderr, "getaddrinfo error: %s", gai_strerror(err));
exit(-1);
}
for (aip = ailist; aip != NULL; aip = aip->ai_next){
fprintf(stderr, "ai_canonname:%s",aip->ai_canonname);
if((sockfd = connect_retry(aip->ai_family, SOCK_STREAM, 0, aip->ai_addr, aip->ai_addrlen)) < 0){
err = errno;
}else
{
print_uptime(sockfd);
exit(0);
}
}
fprintf(stderr,"can't connect to %s",argv[1]);
exit(-1);
}

16-18.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#include <netdb.h>
#include <errno.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/resource.h>
#include <string.h>


#define QLEN 10

#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#endif

extern int initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen);

int
set_cloexec(int fd)
{
int val;

if ((val = fcntl(fd, F_GETFD, 0)) < 0)
return(-1);
val |= FD_CLOEXEC; //使用exec执行时,关闭该文件描述符

return(fcntl(fd, F_SETFD,val));
}

void
daemonize(const char *cmd)
{
int i, fd0, fd1, fd2;
pid_t pid;
struct rlimit rl;
struct sigaction sa;

/*
* Clear file creation mask.
*/
umask(0);

/*
* Get maximum number of file descriptors.
*/
if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
{
fprintf(stderr,"%s: can't get file limit", cmd);
exit(-1);
}
/*
* Become a session leader to lose controlling TTY.
*/
if ((pid = fork()) < 0)
{
fprintf(stderr,"%s: can't fork", cmd);
exit(-1);
}
else if (pid != 0) /* parent */
exit(0);
setsid();

/*
* Ensure future opens won't allocate controlling TTYs.
*/
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0)
{
fprintf(stderr,"%s: can't ignore SIGHUP", cmd);
exit(-1);
}
if ((pid = fork()) < 0)
{
fprintf(stderr,"%s: can't fork", cmd);
exit(-1);
}
else if (pid != 0) /* parent */
exit(0);

/*
* Change the current working directory to the root so
* we won't prevent file systems from being unmounted.
*/
if (chdir("/") < 0)
{
fprintf(stderr,"%s: can't change directory to /", cmd);
exit(-1);
}

/*
* Close all open file descriptors.
*/
if (rl.rlim_max == RLIM_INFINITY)
rl.rlim_max = 1024;
for (i = 0; i < rl.rlim_max; i++)
close(i);

/*
* Attach file descriptors 0, 1, and 2 to /dev/null.
*/
fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0);

/*
* Initialize the log file.
*/
openlog(cmd, LOG_CONS, LOG_DAEMON);
if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
syslog(LOG_ERR, "unexpected file descriptors %d %d %d",
fd0, fd1, fd2);
exit(1);
}
}

void
serve(int sockfd)
{
int clfd, status;
pid_t pid;

set_cloexec(sockfd);
for (;;) {
if ((clfd = accept(sockfd, NULL, NULL)) < 0){
syslog(LOG_ERR, "ruptimed: accept error: %s", strerror(errno));
exit(-1);
}

if((pid = fork()) < 0){
syslog(LOG_ERR, "ruptimed: fork error: %s",strerror(errno));
exit(-1);
} else if (pid == 0) {
if(dup2(clfd, STDOUT_FILENO) != STDOUT_FILENO ||
dup2(clfd, STDERR_FILENO) != STDERR_FILENO) {
syslog(LOG_ERR, "ruptimed: unexpected error");
exit(1);
}
close(clfd);
execl("/usr/bin/uptime", "uptime", (char *)0);
syslog(LOG_ERR, "ruptimed: uunexpecred return from exec: %s", strerror(errno));
} else {
close(clfd);
waitpid(pid, &status, 0);
}
}
}

int
main(int argc, char *argv[])
{
struct addrinfo *ailist, *aip;
struct addrinfo hint;
int sockfd, err, n;
char *host;

if (argc != 1)
{
fprintf(stderr, "usgae: ruptimed");
exit(-1);
}
if ((n = sysconf(_SC_HOST_NAME_MAX)) < 0)
n = HOST_NAME_MAX;
if ((host = malloc(n)) == NULL)
{
fprintf(stderr, "malloc error");
exit(-1);
}
if (gethostname(host, n) < 0){
fprintf(stderr, "gethostname error");
exit(-1);
}
daemonize("ruptimed");
memset(&hint, 0, sizeof(hint));
hint.ai_flags = AI_CANONNAME;
hint.ai_socktype = SOCK_STREAM;
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;

if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) {
syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s",gai_strerror(err));
exit(1);
}
for (aip = ailist; aip != NULL; aip = aip->ai_next) {
if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr, aip->ai_addrlen, QLEN)) >= 0) {
serve(sockfd);
exit(0);
}
}

}

16-19.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <netdb.h>
#include <errno.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define BUFLEN 128
#define TIMEOUT 20

void
sigalrm(int signo)
{

}

void print_uptime(int sockfd, struct addrinfo *aip)
{
int n;;
char buf[BUFLEN];

buf[0] = 0;
if (sendto(sockfd, buf, 1, 0, aip->ai_addr, aip->ai_addrlen) < 0)
{
fprintf(stderr, "sento error");
exit(-1);
}
alarm(TIMEOUT);
if ((n = recvfrom(sockfd, buf, BUFLEN, 0, NULL, NULL)) <0){
if (errno != EINTR)
alarm(0);
fprintf(stderr, "recv error");
exit(-1);
}
alarm(0);
write(STDOUT_FILENO, buf, n);
}

int
main(int argc, char *argv[])
{
struct addrinfo *ailist, *aip;
struct addrinfo hint;
int sockfd, err;
struct sigaction sa;

if (argc != 2)
{
fprintf(stderr, "usage: ruptime hostname");
exit(-1);
}
sa.sa_handler = sigalrm;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGALRM, &sa, NULL) < 0)
{
fprintf(stderr, "sigaction error");
exit(-1);
}
memset(&hint, 0, sizeof(hint));
hint.ai_socktype = SOCK_DGRAM; //过滤模板
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;
if ((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0)
{
fprintf(stderr, "getaddrinfo error :%s",gai_strerror(err));
exit(-1);
}
for (aip = ailist; aip != NULL; aip = aip->ai_next) {
if ((sockfd = socket(aip->ai_family, SOCK_DGRAM, 0)) < 0) {
err = errno;
} else {
print_uptime(sockfd, aip);
exit(0);
}
}

fprintf(stderr, "can't contact %s: %s\n", argv[1], strerror(err));
exit(1);
}

16-20.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#include <netdb.h>
#include <errno.h>
#include <syslog.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/resource.h>


#define BUFLEN 128
#define MAXADDRLEN 256

#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#endif

extern int initserver(int , const struct sockaddr *, socklen_t, int);

void
daemonize(const char *cmd)
{
int i, fd0, fd1, fd2;
pid_t pid;
struct rlimit rl;
struct sigaction sa;

/*
* Clear file creation mask.
*/
umask(0);

/*
* Get maximum number of file descriptors.
*/
if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
{
fprintf(stderr,"%s: can't get file limit", cmd);
exit(-1);
}
/*
* Become a session leader to lose controlling TTY.
*/
if ((pid = fork()) < 0)
{
fprintf(stderr,"%s: can't fork", cmd);
exit(-1);
}
else if (pid != 0) /* parent */
exit(0);
setsid();

/*
* Ensure future opens won't allocate controlling TTYs.
*/
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0)
{
fprintf(stderr,"%s: can't ignore SIGHUP", cmd);
exit(-1);
}
if ((pid = fork()) < 0)
{
fprintf(stderr,"%s: can't fork", cmd);
exit(-1);
}
else if (pid != 0) /* parent */
exit(0);

/*
* Change the current working directory to the root so
* we won't prevent file systems from being unmounted.
*/
if (chdir("/") < 0)
{
fprintf(stderr,"%s: can't change directory to /", cmd);
exit(-1);
}

/*
* Close all open file descriptors.
*/
if (rl.rlim_max == RLIM_INFINITY)
rl.rlim_max = 1024;
for (i = 0; i < rl.rlim_max; i++)
close(i);

/*
* Attach file descriptors 0, 1, and 2 to /dev/null.
*/
fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0);

/*
* Initialize the log file.
*/
openlog(cmd, LOG_CONS, LOG_DAEMON);
if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
syslog(LOG_ERR, "unexpected file descriptors %d %d %d",
fd0, fd1, fd2);
exit(1);
}
}

void
serve(int sockfd)
{
int n;
socklen_t alen;
FILE *fp;
char buf[BUFLEN];
char abuf[MAXADDRLEN];
struct sockaddr *addr = (struct sockaddr *)abuf;

set_cloexec(sockfd);
for(;;) {
alen = MAXADDRLEN;
if ((n = recvfrom(sockfd, buf, BUFLEN, 0, addr, &alen)) < 0) {
syslog(LOG_ERR, "ruptimed: recvfrom error :%s", strerror(errno));
exit(1);
}
if ((fp = popen("/usr/bin/uptime", "r")) == NULL) {
sprintf(buf, "error: %s\n", strerror(errno));
sendto(sockfd, buf, strlen(buf), 0, addr, alen);
} else {
if (fgets(buf, BUFLEN, fp) != NULL)
sendto(sockfd, buf, strlen(buf), 0, addr, alen);
pclose(fp);
}
}
}

int
main(int argc, char *argv[])
{
struct addrinfo *ailist, *aip;
struct addrinfo hint;
int sockfd, err, n;
char *host;
if (argc != 1)
{
fprintf(stderr, "usage: ruptimed");
exit(-1);
}
if ((n = sysconf(_SC_HOST_NAME_MAX)) < 0)
n = HOST_NAME_MAX;
if ((host = malloc(n)) == NULL)
{
fprintf(stderr, "malloc error");
exit(-1);
}
if (gethostname(host, n) < 0)
{
fprintf(stderr, "gethostname error");
exit(-1);
}
daemonize("ruptimed");
memset(&hint, 0, sizeof(hint));
hint.ai_flags = AI_CANONNAME;
hint.ai_socktype = SOCK_DGRAM;
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;
if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0){
syslog(LOG_ERR, "ruptimed: getaddrinfo error:%s", gai_strerror(err));
exit(1);
}
for (aip = ailist; aip != NULL; aip = aip->ai_next) {
if ((sockfd = initserver(SOCK_DGRAM, aip->ai_addr, aip->ai_addrlen, 0)) >= 0){
serve(sockfd);
exit(0);
}
}
exit(1);
}

套接字选项

  • 通用选项
  • 在套接字层次管理的选项,但是依赖于下层协议的支持
  • 特定于某协议的选项,每个协议独有的

setsockopt

1
2
3
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int option, const void *val, socklen_t len);
//返回值:若成功,返回0;若出错,返回-1
  • level:标识了选项应用的协议
    • ​ 通用套接字层则为SOL_SOCKET
    • TCPIPPRTO_TCP
    • IPIPPROTO_IP

image-20230226202107691

  • val:根据选项的不同指向一个数据结构或者一个整数。
    • 选项是on/off开关。若非零则启用,零则关闭
  • len:指定val指向的对象的大小

getsockopt

1
2
3
4
#include <sys/socket.h>

int getsockopt(int sockfd, int level, int option, void *restrict val, socklen_t *restrict lenp);
//返回值:若成功,返回0;若出错,返回-1

sockatmark

判断是否到达紧急标志

1
2
3
#include <sys/socket.h>
int sockatmark(int sockfd);
//返回值:若在标记处,返回-1;若没在标记处,返回0;若出错,返回-1

高级进程间通信

UNIX域套接字

1
2
3
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sockfd[2]);
//返回值:若成功,返回0:若出错,返回-1

17-2.c

1
2
3
4
5
6
#include <sys/socket.h>
int
fd_pipe(int fd[2])
{
return(socketpair(AF_UNIX, SOCK_STREAM, 0, fd));
}

借助UNIX域套接字轮询XSI消息队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <poll.h>
#include <pthread.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <string.h>
#include <stdio.h>

#define NQ 3
#define MAXMSZ 512
#define KEY 0x123

struct threadinfo {
int qid; //线程id
int fd; //文件描述符
};

struct mymesg {
long mtype; //类型
char mtext[MAXMSZ]; //字符串
};

void *
helper(void *arg)
{
int n;
struct mymesg m;
struct threadinfo *tip = arg;

for (;;){
memset(&m, 0, sizeof(m));
if ((n = msgrcv(tip->qid, &m, MAXMSZ, 0, MSG_NOERROR)) < 0) //检索消息队列中的第一条消息,标志位为MSG_NOERROR时,当消息大小超过msgze时被截断。
{
fprintf(stderr, "msgrcv error!\n");
exit(-1);
}
if (write(tip->fd, m.mtext, n) < 0) //不断地从队列中取出数据,并写入线程对应的文件描述符中
{
fprintf(stderr, "write error!\n");
exit(-1);
}
}
}

int
main()
{
int i, n, err;
int fd[2];
int qid[NQ];
struct pollfd pfd[NQ];
struct threadinfo ti[NQ];
pthread_t tid[NQ];
char buf[MAXMSZ];

for (i = 0; i < NQ; i++){
if ((qid[i] = msgget((KEY+i), IPC_CREAT | 0666)) < 0) //创建队列
{
fprintf(stderr, "msgget error\n");
exit(-1);
}

printf("queue ID %d is %d\n", i, qid[i]);

if (socketpair(AF_UNIX, SOCK_DGRAM, 0, fd) < 0) //全双工的UINX域套接字
{
fprintf(stderr, "pthread_create error");
exit(-1);
}
pfd[i].fd = fd[0];
pfd[i].events = POLLIN; //感兴趣的事件
ti[i].qid = qid[i];
ti[i].fd = fd[1];
if ((err = pthread_create(&tid[i], NULL, helper, &ti[i])) != 0)
{
fprintf(stderr, "pthread create error!");
exit(-1);
}

}
for (;;) {
if (poll(pfd, NQ, -1) < 0)//监听指定的事件
{
fprintf(stderr, "poll error");
exit(-1);
}
for (i = 0; i < NQ; i++) {
if (pfd[i].revents & POLLIN) { //判断到指定的描述符中有数据
if ((n = read(pfd[i].fd, buf, sizeof(buf))) < 0) //则从描述符中读取数据到缓存区中
{
fprintf(stderr, "read error");
exit(-1);
}
buf[n] = 0;
printf("queue id %d, message %s\n", qid[i], buf);
}
}
}
exit(0);
}

17-4.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#define MAXMSZ 512

struct mymesg {
long mtype; //消息类型
char mtext[MAXMSZ]; //消息
};

int
main(int argc, char *argv[])
{
key_t key;//关键值
long qid;
size_t nbytes;
struct mymesg m;

if (argc != 3) {
fprintf(stderr ,"usage: sendmsg KEY message\n");
exit(-1);
}

key = strtol(argv[1], NULL, 0);
if ((qid = msgget(key, 0)) < 0)
{
fprintf(stderr, "can't open queue key %s\n", argv[1]);
exit(-1);
}
memset(&m, 0, sizeof(m));
strncpy(m.mtext, argv[2], MAXMSZ-1);
nbytes = strlen(m.mtext);
m.mtype = 1;
if (msgsnd(qid, &m, nbytes, 0) < 0)
{
fprintf(stderr, "can't send message\n");
exit(-1);
}
exit(0);

}

17-5.c 命名UNIX域套接字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <stddef.h>

int
main(int argc, char *argv[])
{
int fd, size;
struct sockaddr_un un;

un.sun_family = AF_UNIX;
strcpy(un.sun_path, "foo.socket"); //设置绑定路径
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) //创建套接字
{
fprintf(stderr, "socket failed");
exit(-1);
}
size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path); //计算路径长度
if (bind(fd, (struct sockaddr *)&un, size) < 0) //绑定本地路径
{
fprintf(stderr, "bind failed");
exit(-1);
}
printf("UNIX domain socket bound\n");
exit(0);
}

唯一连接

1
2
3
4
5
6
7
8
9
10
#include "apue.h"

int serv_listen(const char *name);
//返回值:若成功,返回要监听的文件描述符; 若出差,返回负值

int serv_acceptt(int listenfd, uid_t *uidptr);
//返回值:若成功,返回新文件描述符;若出差,返回负值

int cli_conn(const char *name);
//返回值:若成功,返回文件描述符;若出错,返回负值

serv_listen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>

#define QLEN 10

int
serv_listern(const char *name)
{
int fd, len, err, rval;
struct sockaddr_un un;

if (strlen(name) >= sizeof(un.sun_path))
{
errno = ENAMETOOLONG;
return(-1);
}

if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return(-2);

unlink(name); //删除文件名为name的文件

memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, name);
len = offsetof(struct sockaddr_un, sun_path) + strlen(name);

if (bind(fd, (struct sockaddr *)&un, len) < 0) { //绑定sun_path路径
rval = -3;
goto errout;
}

if (listen(fd, QLEN) < 0) { //开启监听
rval = -4;
goto errout;
}
return(fd);

errout:
err = errno;
close(fd);
errno = err;
return(rval);

}

serv_accept

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <sys/socket.h>
#include <sys/un.h>
#include <time.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <stddef.h>

#define STALE 30

int
serve_accept(int listenfd, uid_t *uidptr)
{
int clifd, err, rval;
socklen_t len;
time_t staletime;
struct stat statbuf;
struct sockaddr_un un;
char *name;

if ((name = malloc(sizeof(un.sun_path) + 1)) == NULL)
return(-1);
len = sizeof(un);
if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0){ //等到客户端的请求
free(name);
return(-2);
}

len -= offsetof(struct sockaddr_un, sun_path);
memcpy(name, un.sun_path, len); //客户端的地址
name[len] = 0;
if (stat(name, &statbuf) < 0) {
rval = -3;
goto errout;
}

#ifdef S_ISSOCK
if (S_ISSOCK(statbuf.stmode) == 0) {
rval = -4;
goto errout;
}
#endif
/*
S_IRWXG:(S_IRGRP | S_IWGRP | S_IXGRP)
S_IRGRP:文件的组所有者的读取权限位。通常是040
S_IWGRP:文件的组所有者的写许可权位。通常为020
S_IXGRP:执行或搜索文件组所有者的权限位。通常是010

S_IRWXO:(S_IROTH | S_IWOTH | S_IXOTH)
S_IROTH:其他用户的读取权限位。通常是04
S_IWOTH:其他用户的写许可权位。通常是02
S_IXOTH:对其他用户执行或搜索权限位。通常是01

S_IRWXU:(S_IRUSR | S_IWUSR | S_IXUSR)
S_IRUSR:文件所有者的读取权限位。在许多系统上,该位为0400
S_IWUSR:文件所有者的写许可权位。通常为0200
S_IXUSR:对文件所有者执行(对于普通文件)或搜索(对于目录)权限位。通常为0100。
*/
if ((statbuf.st_mode & (S_IRWXG | S_IRWXO)) ||
(statbuf.st_mode & S_IRWXU) != S_IRWXU) {
rval = -5;
goto errout;
}

staletime = time(NULL) - STALE;
/*
atime:最后访问时间
ctime:最后修改时间
mtime:文件状态修改时间
*/
if (statbuf.st_atime < staletime ||
statbuf.st_ctime < staletime ||
statbuf.st_mtime < staletime) {
rval = -6;
goto errout;
}

if (uidptr != NULL)
*uidptr = statbuf.st_uid;
unlink(name); //删除客户端的地址
free(name);
return(clifd);

errout:
err = errno;
close(clifd);
free(name);
errno = err;
return(rval);

}

cli_conn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/stat.h>

#define CLI_PATH "/var/tmp"
#define CLI_PERM S_IRWXU

int
cli_conn(const char *name)
{
int fd, len, err, rval;
struct sockaddr_un un, sun;
int do_unlink = 0;

if (strlen(name) >= sizeof(un.sun_path)) {
errno = ENAMETOOLONG;
return(-1);
}

if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) //创建套接字
return (-1);

memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
sprintf(un.sun_path, "%s%05ld", CLI_PATH, (long)getpid()); //创建客户端的地址
len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);

unlink(un.sun_path); //防止文件已经存在
if (bind(fd, (struct sockaddr *)&un, len) < 0) {
rval = -2;
goto errout;
}

if (chmod(un.sun_path, CLI_PERM) < 0) {
rval = -3;
do_unlink = 1;
goto errout;
}

memset(&sun, 0, sizeof(sun));
sun.sun_family = AF_UNIX;
strcpy(sun.sun_path, name);
len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
if (connect(fd, (struct sockaddr *)&sun, len) < 0) { //连接服务器的地址
rval = -4;
do_unlink = 1;
goto errout;
}
return(fd);

errout:
err = errno;
close(fd);
if (do_unlink)
unlink(un.sun_path);
errno = err;
return(rval);
}

传送文件描述符

1
2
3
4
5
6
7
#include "apue.h"

int send_fd(int fd, int fd_to_send);
int send_err(int fd, int status, char *errmsg);
//若成功,返回0;若出错,返回-1
int recv_fd(int fd, ssize_t (*userfunc)(int, const void *, size_t));
//返回值:若成功,返回文件描述符;若出错,返回负值

msghdr结构体

1
2
3
4
5
6
7
8
9
struct msghdr {
void *msg_name; /* 可选地址 */
socklen_t msg_namelen; /* 地址长度 */
struct iovec *msg_iov; /* I/O缓冲区列表 */
int msg_iovlen; /* 列表的个数 */
void *msg_control; /* 辅助数据 */
socklen_t msg_controllen; /* 辅助字节数 */
int msg_flags; /* 收到消息的标志 */
};

msg_control字段指向cmsghdr

1
2
3
4
5
6
struct cmsghdr {
socklen_t cmsg_len; /* 数据字节数, 包括头部 */
int cmsg_level; /* 协议 */
int cmsg_type; /* 协议指定类型 */
/* followed by the actual control message data */
}

访问控制数据

1
2
3
4
5
6
7
8
9
10
#include <sys/socket.h>

unsigned char *CMSG_DATA(struct cmsghdr *cp);
//返回值:返回一个指针,指向与cmsghdr结构相关联的数据
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *mp);
//返回值:返回一个指针,指向与msghdr结构相关联的第一个cmsghdr结构;若无这样的结构,返回NULL
struct cmsghdr *CMSG_NXTHDR(struct msghdr *mp, struct cmsghdr *cp);
//返回值:返回一个指针,指向与msghdr结构相关联的下一个cmsghdr结构,该msghdr结构给出了当前的cmsghdr结构;若当前cmsghdr结构已是最后一个,返回NULL
unsigned int CMSG_LEN(unsigned int nbytes);
//返回值:返回为nbytes长的数据对象分配的长度

17-13.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>

#if defined(SCM_CREDS) /* BSD interface */
#define CREDSTRUCT cmsgcred
#define SCM_CREDTYPE SCM_CREDS
#elif defined(SCM_CREDENTIALS) /* Linux interface */
#define CREDSTRUCT ucred
#define SCM_CREDTYPE SCM_CREDENTIALS
#else
#endif

#define RIGHTSLEN CMSG_LEN(sizeof(int))
#define CREDSLEN CMSG_LEN(sizeof(struct CREDSTRUCT))
#define CONTROLLEN (RIGHTSLEN + CREDSLEN)

static struct cmsghdr *cmptr = NULL;

int
send_fd(int fd, int fd_to_send) //用于发送文件描述符
{
struct iovec iov[1];
struct msghdr msg;
char buf[2];

iov[0].iov_base = buf;
iov[0].iov_len = 2;
msg.msg_iov = iov;
msg.msg_name = NULL;
msg.msg_namelen = 0;

if (fd_to_send < 0) {
msg.msg_control = NULL;
msg.msg_controllen = 0;
buf[1] = -fd_to_send;
if (buf[1] == 0)
buf[1] = 1;
} else {
if (cmptr = NULL && (cmptr = malloc(CONTROLLEN)) == NULL)
return(-1);
cmptr->cmsg_level = SOL_SOCKET;
cmptr->cmsg_type = SCM_RIGHTS;
cmptr->cmsg_len = CONTROLLEN;
msg.msg_control = cmptr;
msg.msg_controllen = CONTROLLEN;
*(int *)CMSG_DATA(comptr) = fd_to_send;
buf[1] = 0;
}

buf[0] = 0;
if (sendmsg(fd, &msg, 0) != 2)
reurn(-1);
return(0);
}

17-14.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define CONTROLLEN CMSCG_LEN(sizeof(int))
#define MAXLINE 256

static struct cmsghdr *cmptr = NULL;

int
recv_fd(int fd, ssize_t (*userfunc)(int, const void *, size_t)) //fd是域套接字
{
int newfd, nr, status;
char *ptr;
char buf[MAXLINE];
struct iovec iov[1];
struct msghdr msg;

status = -1;
for (;;) {
iov[0].iov_base = buf;
iov[0].iov_len = sizeof(buf);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
if (cmptr == NULL && (cmptr = malloc(CONTROLLEN)) == NULL)
return(-1);
msg.msg_control = cmptr;
msg.msg_controllen = CONTROLLEN;
if ((nr = recvmsg(fd, &msg, 0)) < 0) {
fprintf(stderr, "recvmsg error\n");
return(-1);
} else if (nr == 0) {
fprintf(stderr, "connection closed by servver");
exit(-1);
}
for (ptr = buf; ptr < &buf[nr];) {
if (*ptr++ == 0) { //读取状态
if (ptr != &buf[nr-1])
{
fprintf(stderr, "message format error");
exit(-1);
}
status = *ptr & 0xFF;
if (status == 0){
if (msg.msg_controllen < CONTROLLEN)
{
fprintf(stderr, "status = 0 but no fd");
exit(-1);
}
newfd = *(int *)CMSG_DATA(cmptr);
} else {
newfd = -status;
}
nr -= 2;
}
}
}
if (nr > 0 && (*userfunc)(STDERR_FILENO, buf, nr) != nr)
return(-1);
if (status >= 0)
return(newfd);
}

FreeBSD中,将证书作为cmsgcred结构传送

1
2
3
4
5
6
7
8
9
10
11
#define CMGROUP_MAX 16
struct cmsgcred {
pid_t cmcred_pid; /* sender’s process ID */
uid_t cmcred_uid; /* sender’s real UID */
uid_t cmcred_euid; /* sender’s effective UID */
gid_t cmcred_gid; /* sender’s real GID */
short cmcred_ngroups; /* number of groups */
gid_t cmcred_groups[CMGROUP_MAX]; /* groups */
short cmcred_ngroups; /*组数*/
gid_t cmcred_groups[CMGROUP_MAX]; /*组*/
};

Linux中,将证书作为ucred结构传送

1
2
3
4
5
struct ucred{
pid_t pid;
uid_t uid;
gid_t gid;
};

17-15.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>

#if defined(SCM_CREDS)
#define CREDSTRUCT cmsgcred
#define SCM_CREDTYPE SCM_CREDS
#elif defined(SCM_CREDENTIALS)
#define CREDSTRUCT ucred
#define SCM_CREDTYPE SCM_CREDENTIALS
#endif

#define RIGHTSLEN CMSG_LEN(sizeof(int))
#define CREDSLEN CMSG_LEN(sizeof(struct CREDSTRUCT))
#define CONTROLLEN (RIGHTSLEN + CREDSLEN)

static struct cmsghdr *cmptr = NULL;

int
send_fd(int fd, int fd_to_send)
{
struct CREDSTRUCT *credp;
struct cmsghdr *cmp;
struct iovec iov[1];
struct msghdr msg;
char buf[2];

iov[0].iov_base = buf;
iov[0].iov_len = 2;
msg.msg_iov = iov;
msg.msg_controllen = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_flags = 0;
if (fd_to_send < 0) {
msg.msg_control = NULL;
msg.msg_controllen = 0;
buf[1] = -fd_to_send;
if(buf[1] == 0)
buf[1] = 1;
} else {
if (cmptr == NULL && (cmptr = malloc(CONTROLLEN)) == NULL)
return(-1);
msg.msg_control = cmptr;
msg.msg_controllen = CONTROLLEN;
cmp = cmptr;
cmp->cmsg_level = SOL_SOCKET; //用于设置获取socket属性
cmp->cmsg_type = SCM_RIGHTS; //用于传送访问权
cmp->cmsg_len = RIGHTSLEN;
*(int *)CMSG_DATA(cmp) = fd_to_send;
cmp = CMSG_NXTHDR(&msg, cmp); //获取下一个控制消息
cmp->cmsg_level = SOL_SOCKET;
cmp->cmsg_type = SCM_CREDTYPE; //用于在UNIX系统中传递进程的用户和组的凭据信息
cmp->cmsg_len = CREDSLEN;
credp = (struct CREDSTRUCT *)CMSG_DATA(cmp); //增加证书信息
#if defined(SCM_CREDENTIALS)
credp->uid = geteuid();
credp->gid = getegid();
credp->pid = getpid();
#endif
buf[1] = 0;
}

buf[0] = 0;
if (sendmsg(fd, &msg, 0) != 2)
return(-1);
return(0);
}

17-16.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#if defined(SCM_CREDS)
#define CREDSTRUCT cmsgcred
#define CR_UID
#define SCM_CREDTYPE SCM_CREDS
#elif defined(SCM_CREDENTIALS)
#define CREDSTRUCT ucred
#define CR_UID uid
#define CREDOPT SO_PASSCRED
#define SCM_CREDTYPE SCM_CREDENTIALS
#endif

#define RIGHTSLEN CMSG_LEN(sizeof(int))
#define CREDSLEN CMSG_LEN(sizoef(struct CREDSTRUCT))
#define CONTROLLEN (RIGHTSLEN + CREDSLEN)

#define MAXLINE 256

static struct cmsghdr *cmptr = NULL;

int recv_ufd(int fd, uid_t *uidptr, ssize_t (*userfunc)(int, const void *, size_t))
{
struct cmsghdr *cmp;
struct CREDSTRUCT *credp;
char *ptr;
char buf[MAXLINE];
struct iovec iov[1];
struct msghdr msg;
int nr;
int newfd = -1;
int status = -1;
#if defined(CREDOPT)
const int on = 1;
/*
setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
表示套接字可以在进程间传递用户和组标识
sockfd:标识一个打开的套接字。
level:选项定义的层次;SOL_SOCKET 表示基础套接字选项,IPPROTO_TCP 表示 TCP 协议选项等等。
optname:需要访问的选项名。
optval:指向存放选项值的缓冲区。
optlen:缓冲区长度
*/
if (setsockopt(fd, SOL_SOCKET, CREDOPT, &on, sizeof(int)) < 0) {
fprintf(stderr, "setsockopt error");
exit(-1);
}
#endif
for (;;) {
iov[0].iov_base = buf;
iov[0].iov_len = sizeof(buf);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
if (cmptr == NULL && (cmptr = malloc(CONTROLLEN)) == NULL)
return(-1)
msg.msg_control = cmptr;
msg.msg_controllen = CONTROLLEN;
if ((nr = recvmsg(fd, &msg, 0)) < 0) {
fprintf(stderr, "recvmsg error");
return(-1);
} else if (nr == 0) {
fprintf(stderr, "connection closed by server");
return(-1);
}

for (ptr = buf; ptr < &buf[nr]; ) {
if (*ptr++ == 0) {
if (ptr != &buf[nr-1])
{
fprintf(stderr, "message format error");
exit(-1);
}
}
status = *ptr & 0xFF;
if (status == 0) {
if (msg.msg_controllen != CONTROLLEN)
{
fprintf(stderr, "status = 0 but no fd");
exit(-1);
}

for (cmp = CMSG_FIRSTHDR(&msg);
cmp != NULL; cmp = CMSG_NXTHDR(&msg, cmp)) {
if (cmp->cmsg_level != SOL_SOCKET)
continue;
switch (cmp->cmsg_type)
{
case SCM_RIGHTS:
newfd = *(int *)CMSG_DATA(cmp);
break;
case SCM_CREDTYPE:
credp = (struct CREDSTRUCT *)CMSG_DATA(cmp);
*uidptr = credp->CR_UID;
break;
}
}
} else {
newfd = -status;
}
nr -= 2;
}
}
if (nr > 0 && (*userfunc)(STDERR_FILENO, buf, nr) != nr)
return(-1);
if (status >= 0)
return(newfd);
}

17-21.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/uio.h>
#include "include/apue.h"

#define CL_OPEN "open"
#define MAXLINE 256
#define MAXARG 50
#define WHITE "\t\n"

char errmsg[MAXLINE];
int oflag;
char *pathname;

int cli_args(int, char **);
void handle_request(char *, int, int);

int
buf_args(char *buf, int (*optfunc)(int, char **))
{
char *ptr, *argv[MAXARG];
int argc;

if (strtok(buf, WHITE) == NULL) //定位\t\n
return(-1);
argv[argc = 0] = buf;//重新调整参数
while ((ptr = strtok(NULL, WHITE)) != NULL) {
if (++argc >= MAXARG - 1);
return(-1);
argv[argc] = ptr;
}
argv[++argc] = NULL;

return((*optfunc)(argc, argv));
}

int
cli_args(int argc, char **argv)
{
if (argc != 3 || strcmp(argv[0], CL_OPEN) !=0) {
strcpy(errmsg, "usage: <pathname> <oflag> \n");
return(-1);
}
pathname = argv[1];
oflag = atoi(argv[2]);
return(0);
}

void
handle_request(char *buf, int nread, int fd)
{
int newfd;

if (buf[nread-1] != 0) { //结尾不是截断符
snprintf(errmsg, MAXLINE-1,
"request not null terminated: %*.*s\n", nread, nread, buf);
send_err(fd, -1, errmsg);
return;
}
if (buf_args(buf, cli_args) < 0) {
send_err(fd, -1, errmsg);
return;
}
if ((newfd = open(pathname, oflag)) < 0) {
snprintf(errmsg, MAXLINE - 1, "can't open %s: %s\n", pathname, strerror(errno));
send_err(fd, -1, errmsg);
return;
}
if (send_fd(fd, newfd) < 0)
err_sys("send_fd error");
close(newfd);
}

int
main(void)
{
int nread;
char buf[MAXLINE];

for(;;) {
if ((nread = read(STDIN_FILENO, buf, MAXLINE)) < 0)
{
fprintf(stderr, "read error on stream pipe");
exit(-1);
}
else if (nread == 0)
break;
handle_request(buf, nread, STDOUT_FILENO);
}
exit(0);
}

opend.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "include/apue.h"
#include <errno.h>

#define CS_OPEN "/tmp/opend.socket"
#define CL_OPEN "open"

extern int debug;
extern char errmsg[];
extern int oflag;
extern char *pathname;

typedef struct
{
/* data */
int fd;
uid_t uid;
} Client;

extern Client *client;
extern int client_size;

int cli_args(int, char **);
int client_add(int, uid_t);
void client_del(int);
void loop(void);
void handle_request(char *, int, int, uid_t);

17-27.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include "opend.h"
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

#define NALLOC 10

static void
client_alloc(void)
{
int i;

if (client == NULL)
client = malloc(NALLOC * sizeof(Client));
else
client = realloc(client, (client_size+NALLOC)*sizeof(Client));
if (client == NULL)
{
fprintf(stderr, "can't alloc for client array");
exit(-1);
}

for (i = client_size; i < client_size + NALLOC; i++)
client[i].fd = -1;
client_size += NALLOC;
}

int
client_add(int fd, uid_t uid)
{
int i;
if (client == NULL)
client_alloc();
again:
for (i = 0; i < client_size; i++) {
if (client[i].fd == -1) {
client[i].fd = fd;
client[i].uid = uid;
return(i);
}
}

client_alloc();
goto again;
}

void
client_del(int fd)
{
int i;
for (i = 0; i < client_size; i++) {
if (client[i].fd == fd) {
client[i].fd = -1;
return;
}
}
log_quit("can't find client entry for fd %d", fd);
}

命令处理

1
2
3
4
5
6
7
#include <unistd.h>

int getopt(int argc, char *const argv[], const char *options);
extern int optind, opterr, optopt;
extern char *optarg;

//返回值:若所有选项被处理完,返回-1;否则,返回下一个选项字符

17-28.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include "opend.h"
#include <syslog.h>
#include <sys/select.h>

int debug, oflag, client_size, log_to_stderr;
char errmsg[MAXLINE];
char *pathname;
Client *clinet = NULL;

void
loop(void)
{
int i, n, maxfd, maxi, listenfd, clifd, nread;
char buf[MAXLINE];
uid_t uid;
fd_set rset, allset;
FD_ZERO(&allset);

if ((listenfd = serv_listen(CS_OPEN)) < 0) //服务器监听本地文件
log_sys("serv_listen error");
FD_SET(listenfd, &allset); //设置文件描述符的集合
maxfd = listenfd;
maxi = -1;

for ( ; ; ){
rset = allset;
if ((n = select(maxfd + 1, &rset, NULL, NULL, NULL)) < 0) //监听读取套接字的集合
log_sys("select error");
if (FD_ISSET(listenfd, &rset)) { //判断listenfd是否在rset集合中
if ((clifd = serv_accept(listenfd, &uid)) < 0) //获取与客户端通信的套接字,以及uid值
log_sys("serv_accept error: %d", clifd);
i = client_add(clifd, uid);
FD_SET(clifd, &allset);
if (clifd < maxfd)
maxfd = clifd;
if (i > maxi)
maxi = i;
log_msg("new connection: uid %d, fd %d", uid, clifd);
continue;
}

for (i = 0; i <=maxi; i++) {
if ((clifd = client[i].fd) < 0)
continue;
if (FD_ISSET(clifd, &rset)) {
if ((nread = read(clifd, buf, MAXLINE)) < 0){
log_sys("read error on fd %d", clifd);
}else if (nread == 0){
log_msg("closed: uid %d, fd %d", client[i].uid, clifd);
client_del(clifd);
FD_CLR(clifd, &allset);
close(clifd);
}else {
handle_request(buf, nread, clifd, client[i].uid);
}
}
}
}
}

int
main(int argc, char *argv[])
{
int c;
log_open("open.serv", LOG_PID, LOG_USER);

opterr = 0;
while ((c = getopt(argc, argv, "d")) != EOF) {
switch(c) {
case 'd':
debug = log_to_stderr = 1;
break;
case '?':
err_quit("unrecognized option: -%c", optopt);
}
}

if (debug == 0)
daemonize("opend");
loop();
}

17-30.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include "opend.h"
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#define NALLOC 10

static struct pollfd *
grow_pollfd(struct pollfd *pfd, int *maxfd)
{
int i;
int oldmax = *maxfd;
int newmax = oldmax + NALLOC;

if ((pfd = realloc(pfd, newmax * sizeof(struct pollfd))) == NULL)
{
fprintf(stderr, "realloc error");
exit(-1);
}

for (i = oldmax; i < newmax; i++) {
pfd[i].fd = -1;
pfd[i].events = POLLIN;
pfd[i].revents = 0;
}
*maxfd = newmax;
return(pfd);

}

void
loop(void)
{
int i, listenfd, clifd, nread;
char buf[MAXLINE];
uid_t uid;
struct pollfd *pollfd;
int numfd = 1;
int maxfd = NALLOC;

if ((pollfd = malloc(NALLOC * sizeof(struct pollfd))) == NULL)
err_sys("malloc error");
for (i = 0; i < NALLOC; i++) {
pollfd[i].fd = -1;
pollfd[i].events = POLLIN; //关注读事件
pollfd[i].revents = 0;
}

if ((listenfd = serv_listen(CS_OPEN)) < 0)
log_sys("serv_listen error");
client_add(listenfd, 0);
pollfd[0].fd = listenfd;

for (;;) {
if (poll(pollfd, numfd, -1) < 0) //监听服务端的文件
log_sys("poll error");

if (pollfd[0].revents & POLLIN) {
if ((clifd = serv_accept(listenfd, &uid)) < 0) //得到客户端的套接字
log_sys("serv_accept");
client_add(clifd, uid); //将客户端的套接字以及uid添加进去

if (numfd == maxfd)
pollfd = grow_pollfd(pollfd, &maxfd);
pollfd[numfd].fd = clifd;
pollfd[numfd].events = POLLIN;
pollfd[numfd].revents = 0;
numfd++;
log_msg("new connection: uid %d, fd %d", uid, clifd);
}

for (i = 1; i < numfd; i++) {
if (pollfd[i].revents & POLLHUP) { //客户端连接断开
goto hungup;
} else if (pollfd[i].revents & POLLIN) { // 从客户端读取数据
if ((nread = read(pollfd[i].fd, buf, MAXLINE)) < 0) {
log_sys("read error on fd %d", pollfd[i].fd);
} else if (nread == 0) {
hungup:
log_msg("closed: uid %d, fd %d", client[i].uid, pollfd[i].fd);
client_del(pollfd[i].fd);
close(pollfd[i].fd);
if (i < (numfd - 1)) {
pollfd[i].fd = pollfd[numfd-1].fd;
pollfd[i].events = pollfd[numfd-1].events;
pollfd[i].revents = pollfd[numfd-1].revents;
i--;
}
numfd--;
} else {
handle_request(buf, nread, pollfd[i].fd, client[i].uid);
}
}
}
}
}

终端I/O

程序禁用中断字符,并将文件结束符设置为Ctrl+B

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <termios.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

isatty(int fd)
{
struct termios ts;

return(tcgetattr(fd, &ts) != -1); //获取属性
}

int
main(void)
{
struct termios term;
long vdisable;

if (isatty(STDIN_FILENO) == 0)
{
fprintf(stderr, "standard input is not a terminal device");
exit(-1);
}

if ((vdisable = fpathconf(STDIN_FILENO, _PC_VDISABLE)) < 0) //表示获取标准输入文件描述符的禁用字符值
{
fprintf(stderr, "fpathconf error or _POSIX_VDISABLE not in effect");
exit(-1);
}

if (tcgetattr(STDIN_FILENO, &term) < 0)
{
fprintf(stderr, "tcgetattr error");
exit(-1);
}

term.c_cc[VINTR] = vdisable; //设置中断信号
term.c_cc[VEOF] = 2; //设置文件结束符
//TCSAFLUSH 表示在设置终端属性后,刷新终端输入输出队列中尚未处理的数据
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &term) < 0) //设置属性
{
fprintf(stderr, "tcsetattr error");
exit(-1);
}

exit(0);
}

获得和设置终端属性

获得和设置termios结构,调用tcgetattrtcsetattr函数

1
2
3
4
5
6
#include <termios.h>

int tcgetattr(int fd, struct termios *termptr);
int tcsetattr(int fd, int opt, const struct termios *termptr);

//两个函数的返回值:若成功,返回0;若出错,返回-1
  • fd:引用终端设备
  • opt参数
    • TCSANOW:更改立即发生
    • TCSADRAIN:发送了所有输出后更改才发生。若更改输出参数则应使用此选项。
    • TCSAFLUSH:发送了所有输出后更改才发生。更进一步,在更改发生时未读的所有输入数据都被丢弃(冲洗)。

终端选项标志

18-11.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <termio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
struct termios term;

if (tcgetattr(STDIN_FILENO, &term) < 0)
{
fprintf(stderr, "tcgetattr error");
exit(-1);
}

switch (term.c_cflag & CSIZE) {
case CS5:
printf("5 bits/byte\n");
break;
case CS6:
printf("6 bits/byte\n");
break;
case CS7:
printf("7 bits/byte\n");
break;
case CS8:
printf("8 bits/byte\n");
break;
default:
printf("unknown bits/byte\n");
}

term.c_cflag &= ~CSIZE; //将CSIZE标志位取消
term.c_cflag |= CS8; //标记上CS8
if (tcsetattr(STDIN_FILENO, TCSANOW, &term) < 0) //设置属性,TCSANOW表示修改立即生效
{
fprintf(stderr, "tcsetattr error");
exit(-1);
}

exit(0);
}

stty命令

在程序中使用tcgetattrtcsetattr函数进行检查和更改。在命令行中用stty命令进行检查和更改。

波特率函数

波特率表示为位/秒

1
2
3
4
5
6
7
8
9
#include <termios.h>

speed_t cfgetispeed(const struct termios *temptr);
speed_t cfgetospeed(const struct termios *termptr);
//两个函数的返回值:波特率值

int cfsetispeed(struct termios *termptr, speed_t speed);
int cfsetospeed(struct termios *termptr speed_t speed);
//两个函数的返回值:若成功,返回0;出错,返回-1

行控制函数

1
2
3
4
5
6
7
8
#include <termios.h>

int tcdrain(int fd);
int tcflow(int fd, int action);
int tcflush(int fd, int queue);
int tcsendbreak(int fd, int duration);

//4个函数的返回值:若成功,返回0;若出错,返回-1
  • tcdrain函数等待所有输出都被传递
  • tcflow函数用于对输入和输出流控制进行控制,action参数
    • TCOOFF:输出被挂起
    • TCOON:重新启动以前被挂起的输出。
    • TCIOFF:系统发送一个STOP字符,这将使终端设备停止发送数据。
    • TCION:系统发送一个START字符,这将使终端设备恢复发送数据
  • tcflush函数冲洗输入缓冲区或输出缓冲区。queue参数必定是下列3个常量之一
    • TCIFLUSH冲洗输入队列
    • TCOFLUSH冲洗输出队列
    • TCIOFLUSH冲洗输入队列和输出队列
  • tcsendbreak函数在一个指定的时间区间内发送连续的0值位流。若duration参数为0,则此种传递延续0.25~0.5。POSIX.1说明若duration非0,则传递时间依赖于实现。

终端标识

1
2
3
4
#include <stdio.h>
char *ctermid(char *ptr);

//返回值:若成功,返回指向控制终端名的指针;若出错,返回指向空字符串的指针

ctermid函数的实现

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <string.h>

static char ctermid_name[L_ctermid];

char *
ctermid(char *str)
{
if (str == NULL)
str = ctermid_name;
return(strcpy(str, "/dev/tty"));
}

若文件描述符引用一个终端设备则isatty返回真。ttyname返回的是在该文件描述符上打开的终端设备的路径名。

1
2
3
4
5
6
#include <unistd.h>
int isatty(int fd);
//返回值:若为终端设备,返回1(真);否则,返回0(假)

char *ttyname(int fd);
//返回值:指向终端路径名的指针;若出错,返回NULL

isatty函数的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <termios.h>
#include <stdio.h>

int
isatty(int fd)
{
struct termios ts;

return(tcgetattr(fd, &ts) != -1);
}

int
main(void)
{
printf("fd 0: %s\n", isatty(0) ? "tty" : "not a tty");
printf("fd 1: %s\n", isatty(1) ? "tty" : "not a tty");
printf("fd 2: %s\n", isatty(2) ? "tty" : "not a tty");
}

ttyname函数的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
#include <sys/stat.h>
#include <dirent.h>
#include <limits.h>
#include <string.h>
#include <termio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

struct devdir {
struct devdir *d_next;
char *d_name;
};

static struct devdir *head;
static struct devdir *tail;
static char pathname[_PC_PATH_MAX + 1];

static void
add(char *dirname)
{
struct devdir *ddp;
int len;

len = strlen(dirname); //判断目录名长度

if ((dirname[len - 1] == '.') && (dirname[len - 2] == '/' ||
(dirname[len - 2] == '.' && dirname[len-3] == '/')))
return;
if (strcmp(dirname, "/dev/fd") == 0)
return;
if ((ddp = malloc(sizeof(struct devdir))) == NULL)
return;
if ((ddp->d_name = strdup(dirname)) == NULL) {
free(ddp);
return;
}

ddp->d_next = NULL;
if (tail == NULL) {
head = ddp;
tail = ddp;
} else {
tail->d_next = ddp;
tail = ddp;
}
}

static void
cleanup(void)
{
struct devdir *ddp, *nddp;
ddp = head;
while (ddp != NULL) {
nddp = ddp->d_next;
free(ddp->d_name);
free(ddp);
ddp = nddp;
}
head = NULL;
tail = NULL;
}

static char *
searchidr(char *dirname, struct stat *fdstatp)
{
struct stat devstat;
DIR *dp;
int devlen;
struct dirent *dirp;

strcpy(pathname, dirname);
if ((dp = opendir(dirname)) == NULL) //打开指定的目录
return(NULL);
strcat(pathname, "/");
devlen = strlen(pathname);
while ((dirp = readdir(dp)) != NULL) {
strcpy(pathname + devlen, dirp->d_name);
if (strcmp(pathname, "/dev/stdin") == 0 ||
strcmp(pathname, "/dev/stdout") == 0 ||
strcmp(pathname, "/dev/stderr") == 0) //跳过终端
continue;

if (stat(pathname, &devstat) < 0)
continue;
if (S_ISDIR(devstat.st_mode)) { //判断是否为目录
add(pathname); //增加目录项
continue;
}
if (devstat.st_ino == fdstatp->st_ino &&
devstat.st_dev == fdstatp->st_dev) { //找到对应目录项
closedir(dp);
return(pathname);
}
}

closedir(dp);
return(NULL);
}


char *
ttyname(int fd)
{
struct stat fdstat;
struct devdir *ddp;
char *rval;

if (isatty(fd) == 0)
return(NULL);
if (fstat(fd, &fdstat) < 0)
return(NULL);
if (S_ISCHR(fdstat.st_mode) == 0)
return(NULL);

rval = searchidr("/dev", &fdstat); //搜索/dev下的目录判断是否符合
if (rval == NULL) {
for (ddp = head; ddp != NULL; ddp = ddp->d_next)
if ((rval = searchidr(ddp->d_name, &fdstat)) != NULL)
break;
}
cleanup();
return(rval);
}

int
main(void)
{
char *name;
if (isatty(0)) {
name = ttyname(0);
if (name == NULL)
name = "undefined";
} else {
name = "not a tty";
}
printf("fd 0: %s\n", name);

if (isatty(1)) {
name = ttyname(1);
if (name == NULL)
name = "undefined";
} else {
name = "not a tty";
}
printf("fd 1: %s\n", name);

if (isatty(2)) {
name = ttyname(2);
if (name == NULL)
name = "undefined";
} else {
name = "not a tty";
}

printf("fd 2: %s\n", name);

exit(0);
}#include <sys/stat.h>
#include <dirent.h>
#include <limits.h>
#include <string.h>
#include <termio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

struct devdir {
struct devdir *d_next;
char *d_name;
};

static struct devdir *head;
static struct devdir *tail;
static char pathname[_PC_PATH_MAX + 1];

static void
add(char *dirname)
{
struct devdir *ddp;
int len;

len = strlen(dirname); //判断目录名长度

if ((dirname[len - 1] == '.') && (dirname[len - 2] == '/' ||
(dirname[len - 2] == '.' && dirname[len-3] == '/')))
return;
if (strcmp(dirname, "/dev/fd") == 0)
return;
if ((ddp = malloc(sizeof(struct devdir))) == NULL)
return;
if ((ddp->d_name = strdup(dirname)) == NULL) {
free(ddp);
return;
}

ddp->d_next = NULL;
if (tail == NULL) {
head = ddp;
tail = ddp;
} else {
tail->d_next = ddp;
tail = ddp;
}
}

static void
cleanup(void)
{
struct devdir *ddp, *nddp;
ddp = head;
while (ddp != NULL) {
nddp = ddp->d_next;
free(ddp->d_name);
free(ddp);
ddp = nddp;
}
head = NULL;
tail = NULL;
}

static char *
searchidr(char *dirname, struct stat *fdstatp)
{
struct stat devstat;
DIR *dp;
int devlen;
struct dirent *dirp;

strcpy(pathname, dirname);
if ((dp = opendir(dirname)) == NULL) //打开指定的目录
return(NULL);
strcat(pathname, "/");
devlen = strlen(pathname);
while ((dirp = readdir(dp)) != NULL) {
strcpy(pathname + devlen, dirp->d_name);
if (strcmp(pathname, "/dev/stdin") == 0 ||
strcmp(pathname, "/dev/stdout") == 0 ||
strcmp(pathname, "/dev/stderr") == 0) //跳过终端
continue;

if (stat(pathname, &devstat) < 0)
continue;
if (S_ISDIR(devstat.st_mode)) { //判断是否为目录
add(pathname); //增加目录项
continue;
}
if (devstat.st_ino == fdstatp->st_ino &&
devstat.st_dev == fdstatp->st_dev) { //找到对应目录项
closedir(dp);
return(pathname);
}
}

closedir(dp);
return(NULL);
}


char *
ttyname(int fd)
{
struct stat fdstat;
struct devdir *ddp;
char *rval;

if (isatty(fd) == 0)
return(NULL);
if (fstat(fd, &fdstat) < 0)
return(NULL);
if (S_ISCHR(fdstat.st_mode) == 0)
return(NULL);

rval = searchidr("/dev", &fdstat); //搜索/dev下的目录判断是否符合
if (rval == NULL) {
for (ddp = head; ddp != NULL; ddp = ddp->d_next)
if ((rval = searchidr(ddp->d_name, &fdstat)) != NULL)
break;
}
cleanup();
return(rval);
}

int
main(void)
{
char *name;
if (isatty(0)) {
name = ttyname(0);
if (name == NULL)
name = "undefined";
} else {
name = "not a tty";
}
printf("fd 0: %s\n", name);

if (isatty(1)) {
name = ttyname(1);
if (name == NULL)
name = "undefined";
} else {
name = "not a tty";
}
printf("fd 1: %s\n", name);

if (isatty(2)) {
name = ttyname(2);
if (name == NULL)
name = "undefined";
} else {
name = "not a tty";
}

printf("fd 2: %s\n", name);

exit(0);
}

规范模式

getpass函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <signal.h>
#include <stdio.h>
#include <termio.h>
#include <unistd.h>
#include <stdlib.h>


#define MAX_PASS_LEN 8

char *
getpass(const char *prompt)
{
static char buf[MAX_PASS_LEN + 1];
char *ptr;
sigset_t sig, osig;
struct termios ts, ots;
FILE *fp;
int c;
//ctermid函数会返回当前的终端的字符串的指针
if ((fp = fopen(ctermid(NULL), "r+")) == NULL) //打开当前终端
return(NULL);
setbuf(fp, NULL); //禁用缓冲

sigemptyset(&sig);
sigaddset(&sig, SIGINT); //代表中断信号
sigaddset(&sig, SIGTSTP); // 代表终端停止信号
sigprocmask(SIG_BLOCK, &sig, &osig); //阻塞两个信号

tcgetattr(fileno(fp), &ts); //获取当前终端的属性
ots = ts;
/*
ECHO:如果设置了该标志位,则终端将回显所有输入字符。默认情况下,该标志位是开启的。
ECHOE:如果设置了该标志位,则终端将在接收到退格字符(^H)时,将光标移回前一个字符,并用空格符将该字符覆盖掉。默认情况下,该标志位是开启的。
ECHOK:如果设置了该标志位,则终端将在接收到换行符(\n)时,将行清空。默认情况下,该标志位是关闭的。
ECHONL:如果设置了该标志位,则终端将在接收到换行符(\n)时,回显该字符。默认情况下,该标志位是关闭的。
*/
ts.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); //关闭回显
tcsetattr(fileno(fp), TCSAFLUSH, &ts); //TCSAFLUSH 是一个参数常量,用于 tcsetattr() 函数中。它表示在设置终端属性之前,将已经存在的输入数据丢弃,并等待所有输出数据传输完成后再进行设置。具体来说,TCSAFLUSH 参数的含义如下:
fputs(prompt, fp);

ptr = buf;
while ((c = getc(fp)) != EOF && c != '\n')
if (ptr < &buf[MAX_PASS_LEN])
*ptr++ = c;
*ptr = 0;
putc('\n', fp);
tcsetattr(fileno(fp), TCSAFLUSH, &ots);
sigprocmask(SIG_SETMASK, &osig, NULL); //恢复原来的信号
fclose(fp);
return(buf);
}

int
main(void)
{
char *ptr;

if ((ptr = getpass("Enter password:")) == NULL)
{
fprintf(stderr, "getpass error\n");
exit(-1);
}
printf("password: %s\n", ptr);

while (*ptr != 0)
*ptr++ = 0;
exit(0);
}

非规范模式

18-20.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <signal.h>
#include <stdio.h>
#include <termio.h>
#include <unistd.h>
#include <stdlib.h>


#define MAX_PASS_LEN 8

char *
getpass(const char *prompt)
{
static char buf[MAX_PASS_LEN + 1];
char *ptr;
sigset_t sig, osig;
struct termios ts, ots;
FILE *fp;
int c;
//ctermid函数会返回当前的终端的字符串的指针
if ((fp = fopen(ctermid(NULL), "r+")) == NULL) //打开当前终端
return(NULL);
setbuf(fp, NULL); //禁用缓冲

sigemptyset(&sig);
sigaddset(&sig, SIGINT); //代表中断信号
sigaddset(&sig, SIGTSTP); // 代表终端停止信号
sigprocmask(SIG_BLOCK, &sig, &osig); //阻塞两个信号

tcgetattr(fileno(fp), &ts); //获取当前终端的属性
ots = ts;
/*
ECHO:如果设置了该标志位,则终端将回显所有输入字符。默认情况下,该标志位是开启的。
ECHOE:如果设置了该标志位,则终端将在接收到退格字符(^H)时,将光标移回前一个字符,并用空格符将该字符覆盖掉。默认情况下,该标志位是开启的。
ECHOK:如果设置了该标志位,则终端将在接收到换行符(\n)时,将行清空。默认情况下,该标志位是关闭的。
ECHONL:如果设置了该标志位,则终端将在接收到换行符(\n)时,回显该字符。默认情况下,该标志位是关闭的。
*/
ts.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); //关闭回显
tcsetattr(fileno(fp), TCSAFLUSH, &ts); //TCSAFLUSH 是一个参数常量,用于 tcsetattr() 函数中。它表示在设置终端属性之前,将已经存在的输入数据丢弃,并等待所有输出数据传输完成后再进行设置。具体来说,TCSAFLUSH 参数的含义如下:
fputs(prompt, fp);

ptr = buf;
while ((c = getc(fp)) != EOF && c != '\n')
if (ptr < &buf[MAX_PASS_LEN])
*ptr++ = c;
*ptr = 0;
putc('\n', fp);
tcsetattr(fileno(fp), TCSAFLUSH, &ots);
sigprocmask(SIG_SETMASK, &osig, NULL); //恢复原来的信号
fclose(fp);
return(buf);
}

int
main(void)
{
char *ptr;

if ((ptr = getpass("Enter password:")) == NULL)
{
fprintf(stderr, "getpass error\n");
exit(-1);
}
printf("password: %s\n", ptr);

while (*ptr != 0)
*ptr++ = 0;
exit(0);
}

终端窗口大小

1
2
3
4
5
6
struct winsize {
unsigned short ws_row; //终端窗口的行数,可显示的文本行数
unsigned short ws_col; //终端窗口的列数,可显示的文本列数
unsigned short ws_xpixel; //终端窗口的宽度
unsigned short ws_ypixel; //终端窗口的高度
};

18-22.c获取窗口大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <termio.h>
#ifndef TIOCGWINSZ
#include <sys/ioctl.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

static void
pr_winsize(int fd)
{
struct winsize size;

if (ioctl(fd, TIOCGWINSZ, (char *)&size));
{
fprintf(stderr, "TIOCGWINSZ error\n");
exit(-1);
}
printf("%d rows, %d columns\n", size.ws_row, size.ws_col);
}

static void
sig_winch(int signo)
{
printf("SIGWINCH received\n");
pr_winsize(STDIN_FILENO);
}

int
main(void)
{
if (isatty(STDIN_FILENO) == 0)
exit(1);
if (signal(SIGWINCH, sig_winch) == SIG_ERR)
{
fprintf(stderr, "signal error");
exit(-1);
}
pr_winsize(STDIN_FILENO);

for ( ; ; )
pause();
}

打开伪终端设备

打开主设备

1
2
3
4
5
#include <stdlib.h>
#include <fcntl.h>

int posix_openpt(int oflag);
//返回值:若成功,返回下一个可用的PTY主设备文件描述符;若出错,返回-1
  • oflag:打开标志

grantpt获取访问从设备的权限,unlockpt解除限制

1
2
3
4
5
#include <stdlib.h>
int grantpt(int fd);
int unlockpt(int fd);

//两个函数的返回值:若成功,返回0;若出错,返回-1

获取从设备的路径

1
2
3
4
#include <stdlib.h>
char *ptsname(int fd);

//返回值:若成功,返回指向PTY从设备名的指针;若出错,返回NULL

ptym_open打开PTY主设备文件描述符,ptys_open打开PTY从设备文件描述符

1
2
3
4
5
6
#include "apue.h"

int ptym_open(char *pts_name, int pts_namesz);
//返回值:若成功,返回PTY主设备文件描述符;若出错,返回-1
int ptys_open(char *pts_name);
//返回值:若成功,返回PTY从设备问而建描述符:若出错,返回-1

19-9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <errno.h>
#include <fcntl.h>
#if defined(SOLARIS)
#include <stropt.h>
#endif

int
ptym_open(char *pts_name, int pts_namesz)
{
char *ptr;
int fdm, err;

if ((fdm = posix_openpt(O_RDWR)) < 0) //打开pty设备
return(-1);
if (grantpt(fdm) < 0) //授予访问从设备的权限
goto errout;
if (unlockpt(fdm) < 0) //解锁从设备
goto errout;
if ((ptr = ptsname(fdm)) == NULL) //获取从设备路径
goto errout;

strncpy(pts_name, ptr, pts_namesz); //存放从设备的路径
pts_name[pts_namesz - 1] = '\0';
return(fdm);

errout:
err = errno;
close(fdm);
errno = err;
return(-1);
}

int
ptys_open(char *pts_name)
{
int fds;
#if defined(SOLARIS)
int err, setup;
#endif
if ((fds = open(pts_name, O_RDWR)) < 0)
return(-1);
#if defined(SOLARIS)
if ((setup = ioctl(fds, I_FIND, "ldterm")) < 0)
goto errout;

if (setup == 0) {
if (ioctl(fds, I_PUSH, "pterm") < 0)
goto errout;
if (ioctl(fds, I_PUSH, "ldterm") < 0)
goto errout;
if (ioctl(fds, I_PUSH, "ttcompat") < 0) {
errout:
err = errno;
close(fds);
errno = err;
return(-1);
}
}
#endif
return(fds);
}

pty_fork

1
2
3
4
5
6
7
8
#include "apue.h"
#include <termios.h>

pid_t pty_fork(int *ptrfdm, char *slave_name, int slave_namesz,
const struct termios *slave_termios,
const struct winsize *slave_winsize);

//返回值:子进程中返回0;父进程中返回子进程的进程ID;若出错,返回-1
  • slave_name: 存放从设备名
  • slave_termios:若不为空,则初始化为设备的终端行规程,若为空,则把从设备termios结构设置成实现定义的初始状态
  • slave_winsize:若不为空,则根据指针指向的结构体初始化设备的窗口大小。若为空,winsize结构体被初始化为0

19-10.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#include <termio.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#if defined(SOLARIS)
#include <stropt.h>
#endif

int
ptym_open(char *pts_name, int pts_namesz)
{
char *ptr;
int fdm, err;

if ((fdm = posix_openpt(O_RDWR)) < 0) //打开pty设备
return(-1);
if (grantpt(fdm) < 0) //授予访问从设备的权限
goto errout;
if (unlockpt(fdm) < 0) //解锁从设备
goto errout;
if ((ptr = ptsname(fdm)) == NULL) //获取从设备路径
goto errout;

strncpy(pts_name, ptr, pts_namesz); //存放从设备的路径
pts_name[pts_namesz - 1] = '\0';
return(fdm);

errout:
err = errno;
close(fdm);
errno = err;
return(-1);
}

int
ptys_open(char *pts_name)
{
int fds;
#if defined(SOLARIS)
int err, setup;
#endif
if ((fds = open(pts_name, O_RDWR)) < 0)
return(-1);
#if defined(SOLARIS)
if ((setup = ioctl(fds, I_FIND, "ldterm")) < 0)
goto errout;

if (setup == 0) {
if (ioctl(fds, I_PUSH, "pterm") < 0)
goto errout;
if (ioctl(fds, I_PUSH, "ldterm") < 0)
goto errout;
if (ioctl(fds, I_PUSH, "ttcompat") < 0) {
errout:
err = errno;
close(fds);
errno = err;
return(-1);
}
}
#endif
return(fds);
}

pid_t
pty_fork(int *ptrfdm, char *slave_name, int slave_namesz,
const struct termios *slave_termios,
const struct winsize *slave_winsize)
{
int fdm, fds;
pid_t pid;
char pts_name[20];

if ((fdm = ptym_open(pts_name, sizeof(pts_name))) < 0) //打开主设备,会将从设备路径存放在pts_name
{
fprintf(stderr, "can't open master pty: %s, error %d", pts_name, fdm);
exit(-1);
}

if (slave_name != NULL) {
strncpy(slave_name, pts_name, slave_namesz);
slave_name[slave_namesz - 1] = '\0';
}

if ((pid = fork()) < 0) {
return(-1);
} else if (pid == 0) { //子进程
if (setsid() < 0) { //设置进程的用户ID
fprintf(stderr, "setsid error");
exit(-1);
}
if ((fds = ptys_open(pts_name)) < 0) //打开从设备
{
fprintf(stderr, "can't open slave pty");
exit(-1);
}
close(fdm);
#if defined(BSD)
if (ioctl(fds, TIOCSCTTY, (char *)0) < 0)
{
fprintf(stderr, "TIOCSCTTY error");
exit(-1);
}
#endif
if (slave_termios != NULL) {
if (tcsetattr(fds, TCSANOW, slave_termios) < 0) //终端的行为
{
fprintf(stderr, "tcsetattr error on slave pty");
exit(-1);
}
if (slave_winsize != NULL) {
if (ioctl(fds, TIOCGWINSZ, slave_winsize) < 0)
{
fprintf(stderr, "TIOCSWINSZ error on slave pty");
exit(-1);
}
}

if (dup2(fds, STDIN_FILENO) != STDIN_FILENO)
{
fprintf(stderr, "dup2 error to stdin");
exit(-1);
}

if (dup2(fds, STDOUT_FILENO) != STDOUT_FILENO)
{
fprintf(stderr, "dup2 error to stdout");
exit(-1);
}

if (dup2(fds, STDERR_FILENO) != STDERR_FILENO)
{
fprintf(stderr, "error to stderr");
exit(-1);
}
if (fds != STDIN_FILENO && fds != STDOUT_FILENO && fds != STDERR_FILENO)
close(fds);
returnr(0);
} else {
*ptrfdm = fdm;
return(pid);
}
}
}

19-11.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
#include <termio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>

#ifdef LINUX
#define OPTSTR "+d:eiv"
#else
#define OPTSTR "d:einv"
#endif

static void set_noecho(int);
void do_driver(char *);
void loop(int, int);

extern int optind, opterr, optopt;
extern char *optarg;

int
ptym_open(char *pts_name, int pts_namesz)
{
char *ptr;
int fdm, err;

if ((fdm = posix_openpt(O_RDWR)) < 0) //打开pty设备
return(-1);
if (grantpt(fdm) < 0) //授予访问从设备的权限
goto errout;
if (unlockpt(fdm) < 0) //解锁从设备
goto errout;
if ((ptr = ptsname(fdm)) == NULL) //获取从设备路径
goto errout;

strncpy(pts_name, ptr, pts_namesz); //存放从设备的路径
pts_name[pts_namesz - 1] = '\0';
return(fdm);

errout:
err = errno;
close(fdm);
errno = err;
return(-1);
}

int
ptys_open(char *pts_name)
{
int fds;
#if defined(SOLARIS)
int err, setup;
#endif
if ((fds = open(pts_name, O_RDWR)) < 0)
return(-1);
#if defined(SOLARIS)
if ((setup = ioctl(fds, I_FIND, "ldterm")) < 0)
goto errout;

if (setup == 0) {
if (ioctl(fds, I_PUSH, "pterm") < 0)
goto errout;
if (ioctl(fds, I_PUSH, "ldterm") < 0)
goto errout;
if (ioctl(fds, I_PUSH, "ttcompat") < 0) {
errout:
err = errno;
close(fds);
errno = err;
return(-1);
}
}
#endif
return(fds);
}

static struct termios save_termios;
static int ttysavefd = -1;
static enum{ RESET, RAW, CBREAK} ttystate = RESET;

int tty_cbreak(int fd) //设置非规范模式
{
int err;
struct termios buf;

if (ttystate != RESET)
{
errno = EINVAL;
return(-1);
}

if (tcgetattr(fd, &buf) < 0)
return(-1);
save_termios = buf;
buf.c_lflag &= ~(ECHO | ICANON); //ICANON是一个标志,用于控制是否以规范模式处理输入

buf.c_cc[VMIN] = 1; //要求读取的最小字符数
buf.c_cc[VTIME] = 0;

if (tcsetattr(fd, TCSAFLUSH, &buf) < 0)
return(-1);

if (tcgetattr(fd, &buf) < 0) {
err = errno;
tcsetattr(fd, TCSAFLUSH, &save_termios);
errno = err;
return(-1);
}

if ((buf.c_cflag & (ECHO | ICANON)) || buf.c_cc[VMIN] != 1 ||
buf.c_cc[VTIME] != 0) {
tcsetattr(fd, TCSAFLUSH, &save_termios);
errno = EINVAL;
return(-1);
}

ttystate = CBREAK;
ttysavefd = fd;
return(0);
}

int
tty_raw(int fd)
{
int err;
struct termios buf;

if (ttystate != RESET) {
errno = EINVAL;
return(-1);
}
if (tcgetattr(fd, &buf) < 0)
return(-1);
save_termios = buf;
/*
启用IEXTEN后,可以实现特定的输入处理扩展功能
启用ISIG后,终端驱动程序将对一些特殊字符做出响应,例如Ctrl-C和Ctrl-Z等信号字符。这些信号字符可以用于通知正在运行的程序或shell执行特定的操作或响应事件。
*/
buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);

/*
启用BRKINT后,当终端接收到中断信号(通常是Ctrl-C)或结束信号(通常是Ctrl-D)时,终端驱动程序将发送一个中断信号给正在运行的进程
启用ICRNL后,输入回车字符(\r)将被翻译为新行字符(\n)
启用INPCK后,输入数据将进行奇偶校验
启用ISTRIP后,输入数据的高位(最高位)将被丢弃,只保留低7位。这通常被称为"字符剥离"(character stripping),可用于去除输入数据中的奇怪字符或无用信息
启用IXON后,终端驱动程序将为输出数据中的暂停字符(通常是Ctrl-S)和重新开始字符(通常是Ctrl-Q)启用软件流控制。
*/
buf.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);

/*
它用于设置终端数据位的大小。CSIZE通常与另外两个标志CSTOPB和PARODD一起使用,以确定终端设备的通信参数。

CSIZE标志有三个可能的值:CS5、CS6、CS7和CS8。分别代表使用5、6、7或8位作为数据位。选择使用哪个值通常取决于具体的使用情况和需求。

需要注意的是,CSIZE标志只是终端设置中的一部分。要正确设置终端设备的通信参数,需要考虑其他设置标志,如波特率、停止位和奇偶校验等。

启用PARENB后,终端驱动程序将启用奇偶校验功能,并在输出数据和输入数据中添加奇偶校验位
*/
buf.c_cflag &= ~(CSIZE | PARENB);

buf.c_cflag |= CS8;

/*
启用OPOST后,终端驱动程序将对输出数据进行后处理,以便在发送给终端设备之前进行转换和修改。
*/
buf.c_oflag &= ~(OPOST);

buf.c_cc[VMIN] = 1;
buf.c_cc[VTIME] = 0;
if (tcsetattr(fd, TCSAFLUSH, &buf) < 0)
return(-1);
if (tcgetattr(fd, &buf) < 0) {
err = errno;
tcsetattr(fd, TCSAFLUSH, &save_termios);
errno = err;
return(-1);
}

if ((buf.c_lflag & (ECHO | ICANON | IEXTEN | ISIG)) ||
(buf.c_iflag & (BRKINT | ICRNL | INPCK | ISTRIP | IXON)) ||
(buf.c_cflag & (CSIZE | PARENB | CS8)) != CS8 ||
(buf.c_oflag & OPOST) || buf.c_cc[VMIN] != 1 ||
buf.c_cc[VTIME] != 0) { //检查是否将标志位删除
tcsetattr(fd, TCSAFLUSH, &save_termios);
errno = EINVAL;
return(-1);
}

ttystate = RAW;
ttysavefd = fd;
return(0);
}

int
tty_reset(int fd)
{
if (ttystate == RESET)
return(0);
if (tcsetattr(fd, TCSAFLUSH, &save_termios) < 0)
return(-1);
ttystate = RESET;
return(0);
}

void tty_atexit(void)
{
if (ttysavefd >= 0)
tty_reset(ttysavefd);
}

struct termios *
tty_termios(void)
{
return(&save_termios);
}


pid_t
pty_fork(int *ptrfdm, char *slave_name, int slave_namesz,
const struct termios *slave_termios,
const struct winsize *slave_winsize)
{
int fdm, fds;
pid_t pid;
char pts_name[20];

if ((fdm = ptym_open(pts_name, sizeof(pts_name))) < 0) //打开主设备,会将从设备路径存放在pts_name
{
fprintf(stderr, "can't open master pty: %s, error %d", pts_name, fdm);
exit(-1);
}

if (slave_name != NULL) {
strncpy(slave_name, pts_name, slave_namesz);
slave_name[slave_namesz - 1] = '\0';
}

if ((pid = fork()) < 0) {
return(-1);
} else if (pid == 0) { //子进程
if (setsid() < 0) { //设置进程的用户ID
fprintf(stderr, "setsid error");
exit(-1);
}
if ((fds = ptys_open(pts_name)) < 0) //打开从设备
{
fprintf(stderr, "can't open slave pty");
exit(-1);
}
close(fdm);
#if defined(BSD)
if (ioctl(fds, TIOCSCTTY, (char *)0) < 0)
{
fprintf(stderr, "TIOCSCTTY error");
exit(-1);
}
#endif
if (slave_termios != NULL) {
if (tcsetattr(fds, TCSANOW, slave_termios) < 0) //终端的行为
{
fprintf(stderr, "tcsetattr error on slave pty");
exit(-1);
}
if (slave_winsize != NULL) {
if (ioctl(fds, TIOCGWINSZ, slave_winsize) < 0)
{
fprintf(stderr, "TIOCSWINSZ error on slave pty");
exit(-1);
}
}

if (dup2(fds, STDIN_FILENO) != STDIN_FILENO)
{
fprintf(stderr, "dup2 error to stdin");
exit(-1);
}

if (dup2(fds, STDOUT_FILENO) != STDOUT_FILENO)
{
fprintf(stderr, "dup2 error to stdout");
exit(-1);
}

if (dup2(fds, STDERR_FILENO) != STDERR_FILENO)
{
fprintf(stderr, "error to stderr");
exit(-1);
}
if (fds != STDIN_FILENO && fds != STDOUT_FILENO && fds != STDERR_FILENO)
close(fds);
returnr(0);
} else {
*ptrfdm = fdm;
return(pid);
}
}
}

#define BUFFSIZE 512
static void sig_term(int);
static volatile sig_atomic_t sigcaught;

void
loop(int ptym, int ignoreeof)
{
pid_t child;
int nread;
char buf[BUFFSIZE];

if ((child = fork()) < 0) {
fprintf(stderr, "fork error");
exit(-1);
} else if (child == 0) {
for ( ; ; ) {
if ((nread = read(STDIN_FILENO, buf, BUFFSIZE)) < 0)
{
fprintf(stderr, "read error from stdin");
exit(-1);
}
else if (nread == 0)
break;
if (writen(ptym, buf, nread) != nread) //写入pty设备
{
fprintf(stderr, "write error to master pty");
exit(-1);
}

if (ignoreeof == 0)
kill(getppid(), SIGTERM);
exit(0);
}
if (signal_intr(SIGTERM, sig_term) == SIG_ERR)
{
fprintf(stderr, "signal_intr error for SIGTERM");
exit(-1);
}

for ( ; ;) {
if ((nread = read(ptym, buf, BUFFSIZE)) <= 0)
break;
if (writen(STDOUT_FILENO, buf, nread) != nread)
{
fprintf(stderr, "writen error to stdout");
exit(-1);
}
}

if (sigcaught == 0)
kill(child, SIGTERM);
}
}

static void
sig_term(int signo)
{
sigcaught = 1;
}

int
main(int argc, char *argv[])
{
int fdm, c, ignoreeof, interactive, noecho, verbose;
pid_t pid;
char *driver;
char slave_name[20];
struct termios orig_termios;
struct winsize size;

interactive = isatty(STDIN_FILENO);
ignoreeof = 0;
noecho = 0;
verbose = 0;
driver = NULL;

opterr = 0;
while ((c = getopt(argc, argv, OPTSTR)) != EOF) { //+号的意思是将参数存储在optarg中
switch (c)
{
case /* constant-expression */
'd':
driver = optarg;
break;
/* code */
case 'e':
noecho = 1;
break;
case 'i':
ignoreeof = 1;
break;
case 'n':
interactive = 0;
break;
case 'v':
verbose = 1;
break;
//若getopt的参数不能识别或缺少参数则会返回?
case '?':
fprintf(stderr, "unrecognized option: -%c", optopt);
exit(-1);
}
}
if (optind >= argc)
{
fprintf(stderr, "usage: pty [ -d driver -einv ] program [ arg ... ]");
exit(-1);
}
if (interactive) {
if (tcgetattr(STDIN_FILENO, &orig_termios) < 0) //获取与终端相关的的参数
{
fprintf(stderr, "tcgetattr error on stdin");
exit(-1);
}
if (ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&size) < 0)
{
fprintf(stderr, "TIOCGWINSZ error");
exit(-1);
}
pid = pty_fork(&fdm, slave_name, sizeof(slave_name),
&orig_termios, &size);
} else {
pid = pty_fork(&fdm, slave_name, sizeof(slave_name), NULL, NULL);
}

if (pid < 0)
{
fprintf(stderr, "fork error");
exit(-1);
} else if (pid == 0) {
if (noecho)
set_noecho(STDIN_FILENO);

if (execvp(argv[optind], &argv[optind]) < 0)
{
fprintf(stderr, "can't execute: %s", argv[optind]);
exit(-1);
}
}

if (verbose) {
fprintf(stderr, "slave name = %s\n", slave_name);
if (driver != NULL)
fprintf(stderr, "driver = %s\n", driver);
}

if (interactive && driver == NULL) {
if (tty_raw(STDIN_FILENO) < 0)
{
fprintf(stderr, "tty_raw error");
exit(-1);
}
if (atexit(tty_atexit) < 0)
{
fprintf(stderr, "atexit error");
exit(-1);
}
}

if (driver)
do_driver(driver); //执行驱动程序
loop(fdm, ignoreeof); //fdm是主设备号,对pty设备进行读写
exit(0);
}

static void
set_noecho(int fd)
{
struct termios stermios;

if (tcgetattr(fd, &stermios) < 0)
{
fprintf(stderr, "tcgetaattr error");
exit(-1);
}

stermios.c_cflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
stermios.c_oflag &= ~(ONLCR);

if (tcsetattr(fd, TCSANOW, &save_termios) < 0)
{
fprintf(stderr, "tcsetattr error");
exix(-1);
}

}

数据库函数库

打开数据库

1
2
3
4
5
6
#include "apue_db.h"

DBHANDLE db_open(const char *pathname, int oflag, .../*int mode */);
//返回值:若成功,返回数据库句柄;若失败,返回NULL

void db_close(DBHANDLE db);

数据插入

1
2
3
4
#include "apue_db.h"

int db_store(DBHANDLE db, const char *key, const char *data, int flag);
//返回值:若成功,返回0;若出错,返回非0值
  • flag为插入选项
    • DB_INSERT:插入一条新纪录
    • DB_REPLACE:替换一条已有的记录
    • DB_STORE:插入一条新记录或替换一条已有的记录

指定键从数据库中获取一条记录

1
2
3
4
5
#include "apue_db.h"

char *db_fetch(DBHANDLE db, const char *key);

//返回值:若成功,返回指向数据的指针;若没有找到记录,则返回NULL

删除一条记录

1
2
3
4
#include "apue_db.h"

int db_delete(DBHANDLE db, const char *key);
//返回值:若成功,返回0;若没有找到记录,返回-1

回滚数据库以及逐条读每条记录

1
2
3
4
5
6
#include "apue_db.h"

void db_rewind(DBHANDLE db);
char *db_nextrec(DBHANDLE db, char *key);

//返回值:若成功,返回指向数据的指针;若到达数据库文件的尾端,返回NULL

UNIX环境高级编程笔记
https://h0pe-ay.github.io/UNIX环境高级编程/
作者
hope
发布于
2023年6月28日
许可协议