第五章《Terminals》和第六章《Managing Text-Based Screens with curses》意义不大,所以只挑拣了一个典型用例,和同样内容不太多的第七章《数据管理》(略去不常用的 dbm 数据库)放在一起。顺便把标题也改了,毕竟这本书只算入门级的嘛,看完后要学的东西还很多的。
termios 结构体
通过与该结构体相关的函数,例如 int tcgetattr(int fd, struct termios* termios_p)
和 int tcsetattr(int fd, int actions, const struct termios* termios_p)
,可以改变终端的输入和输出行为。该结构体里面有不少五花八门的咚咚,头都给我看花了,而且很多玩意儿感觉也没什么用——都 21 世纪了,还在纯字符界面里玩花哨,谁鸟你?去其糟粕之后,也许下面这段控制是否回显输入的代码算是真正有用的功能之一,可用于提示用户输入密码:
struct termios oldTermios;
struct termios newTermios;
int infd = fileno(stdin);
tcgetattr(infd, &oldTermios);
newTermios = oldTermios; // 创建副本,便于用完后恢复初始设置。
printf("Input password: ");
newTermios.c_lflag &= ~ECHO;
tcsetattr(infd, TCSAFLUSH, &newTermios); // 禁用输入回显。
char* password = NULL;
size_t n = 0;
getline(&password, &n, stdin); // 屏幕上应当看不到输入的字符。
tcsetattr(infd, TCSANOW, &oldTermios); // 恢复初始设置。
printf("\nYour password is \"%s\".\n", password); // 输出刚才“盲打”的内容。
free(password);
管理内存
void* malloc(size_t size)
、void* calloc(size_t number_of_elements, size_t element_size)
、void* realloc(void* existing_memory, size_t new_size)
和 void free(void* ptr_to_memory)
已经风骚二十多年了,没什么特别的,除了下面这种初学者易犯的错误:
p = realloc(p, size * 2); // 假设前面没有创建 p 的副本。
realloc
在成功时返回新内存的地址,老内存被自动释放;失败返回 NULL
,老内存不变!也就是说,如果这句代码失败,p
就成了一个空指针,而原先的内存已经无法追踪,从而导致内存泄露!
文件锁
《BLP》讲了三种方式,用起来都非常简单:
- 使用函数
int open(const char* path, int oflags, mode_t mode)
,并带上 O_CREAT
和 O_EXCL
标识,来创建锁文件。
- 使用函数
int fcntl(int fildes, int command, struct flock* flock_structure)
对文件进行局部锁定。
- 使用函数
int lockf(int fildes, int function, off_t size_to_lock)
对文件进行局部锁定。
这三种方式创建的锁都属于“劝告锁”(Advisory Lock)。劝告锁仅仅是进程间的一种“游戏规则”,所以只对按规则出牌的进程起作用。如果有进程不按规矩办事,通过其他方式直接操作被“锁住”的文件,内核并不会阻止。劝告锁需要多个进程的协作才能生效;在同一个进程中,即使在前面加了锁,后面的代码也无法检测出来,加锁操作总是会成功。劝告锁很有画地为牢的感觉……
要想彻底锁住文件,必须使用“强制锁”(Mandatory Lock),《BLP》没有讲。上网搜了一下,步骤是:
- 使用
-o mand
选项挂载文件系统。强制锁必须要得到文件系统支持才能生效。
- 修改要加锁的文件的权限:设置 SGID 位,并清除组可执行位。
- 使用
fcntl
对文件进行加锁或解锁,步骤和劝告锁相同。
强制锁不是 POSIX 兼容的,而且由于 Linux 无法解决某些竞争条件,使得它不可靠。详见 Linux 内核文档“mandatory-locking.txt”,注意其中的第 0 个问答就是“Why you should avoid mandatory locking”。