《UNIX环境高级编程》学习笔记(更新中)

1 篇文章 0 订阅
1 篇文章 0 订阅

为接触UNIX、LINUX、DEEPIN等系统,熟悉开发编译环境的学习

第一章:UNIX基础知识

  • UNIX介绍

    • 操作系统为程序提供服务:执行新程序、打开文件、读文件、分配存储区以及获取当前时间等。
  • NIX体系结构

    • 内核的接口被称为系统调用,公用函数库构建在系统调用接口之上,应用程序即可使用系统调用,也可以使用公用函数库。shell是一个特殊的应用程序,为运行其他程序提供了一个接口
  • 登录

    • 登录名:可在系统口令文件 /etc/passwd 文件中查看,其中每一行用6个冒号隔开,他们分别表示: 登录名:加密口令:数字用户ID:数字组ID:注释字段:起始目录:shell程序。目前所有的系统已将加密口令移动到另一个文件中
    • shell
      • shell是命令行解释器,他读取用户输入,然后执行命令。shell的用户输入通常来自于终端,有时则来自于文件(shell脚本)
    • 文件和目录
      • 文件系统:UNIX文件系统是目录和文件的一种层次结构,所有东西的起点被称为根(root)的目录,名称是一个字符 ’/ ’表示。目录是一个包含目录项的文件。
      • 文件名:目录中的名字称为文件名,只有斜线(/)和空字符不能出现在文件名中,因为斜线用来分割构成路径名的各文件名,空字符用来终止一个路径名。不过建议命名文件时还是使用常用的字符集来命名因为可能会使用到shell的特殊符号,则必须使用shell的引号机制来引用,相对来说会很麻烦。推荐的文件名限制在以下字符集之内:字母(a~z、A~Z)、数字(0~9)、句点(.)、短横线(-)和下划线(_)。
      • 路径名:以斜杠开头的路径称为绝对路径名,否则称为相对路径名。文件系统的根的名字是一个特殊的绝对路径名,他不包含文件名。

#include <stdio.h>

#include <stdlib.h>

#include <dirent.h>




#define err(msg) {printf("%s\n", msg);}

#define err_exit(msg) {printf("%s\n", msg);exit(-1);}




int main(int argc, char *argv[])

{

DIR *dp;

struct dirent *dirp;

if(argc != 2)

{

err_exit("usage: mls directory_name");

}

if((dp = opendir(argv[1])) == NULL)

{

err_exit("can't open this directory");

}

while((dirp = readdir(dp)) != NULL)

{

printf("%s\n", dirp->d_name);

}

closedir(dp);

exit(0);

}

此示例代码模拟ls指令,列出目录中的所有文件

  • 工作目录

    • 每个进程都有一个工作目录,所有相对路径名都从工作目录开始解释。进程可以使用chdir函数更改其工作目录。
  • 起始目录:登录时,工作目录设置为起始目录,该起始目录可以从口令文件中相应用户登录项中取得。
  • 输入和输出

    • 文件描述符

      • 文件描述符通常是一个小的非负整数,内核用以标识一个特定进程正在访问的文件。当内核打开一个现有文件或者创建一个新文件时,都会返回一个文件描述符,在对文件进行读、写时可以使用文件描述符。
    • 表准输入、标准输出和标准出错

      • 每次运行一个新程序时,所有的shell都为其打开3个文件描述符,即标准输入、标准输出以及标准出错。这个3个描述符都默认链接向终端,大多数shell都提供一种方法,可以将其重定向到别的文件中:ls > file.txt,这条指令就是将ls的标准输出重新定向到了txt文件中
    • 不带缓冲的I/O

      • 函数open、read、write、lseek以及close提供了不带缓冲的I/O。这些函数都使用文件描述符。
#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) != 0)

{

err_exit("write error");

}

}

if(n <0)

{

err_exit("read error");

}

exit(0);

}

头文件apue.h在后面附录给出,这是一个从终端标准输入读,往标准输出写的程序。在控制台用cc test.c生成可执行程序a.out

如果在命令行执行指令:

./a.out > data

程序将会把标准输入的内容通过标准输出,重定向到data文件中

如果在命令行执行指令:

./a.out < infile > outfile

程序将会把infile中的内容复制到outfile中

  • 标准I/O
    • 标准I/O为那些不带缓冲区的I/O函数提供了一个带缓冲的接口。例如:fgets函数读取一个完整的行,而read函数读取指定字节数。
#include "apue.h"




int main(void)

{

int c;

while((c = getc(stdin)) != EOF)

{

if(putc(c, stdout) == EOF)

{

err_exit("output error");

}

}

if(ferror(stdin))

{

err_exit("input error");

}

exit(0);

}

该程序将标准输入复制到标准输出

  • 程序和进程
    • 程序:程序是一个存储在磁盘上某个目录中的可执行文件。内核使用exec函数(exec族之一,共有7个),将程序读入内存。
    • 进程和进程ID:程序的执行实例被称为进程。UNIX系统确保每个进程都有一个唯一的数字标识符,称为进程ID,是一个非负整数。

#include "apue.h"

int main(void)

{

printf("process ID %ld\n", (long)getpid());

exit(0);

}

该程序会打印自己的进程ID

  • 进程控制
    • 有3个用于进程控制的主要函数:fork、exec和waitpid。

#include "apue.h"

#include <sys/wait.h>

#define MAXLINE 4096

int main(void)

{

char buf[MAXLINE];

pid_t pid;

int status;

printf("%% ");

while(fgets(buf, MAXLINE, stdin) != NULL)

{

if(buf[strlen(buf) -1] == '\n')

{

buf[strlen(buf) -1] = 0;

}

if((pid = fork()) < 0)

{

err_exit("fork error");

}

else if(pid == 0) /* child */

{

execlp(buf, buf, (char *)0);

err_ret("couldn't execute: %s", buf);

exit(127);

}

/* parent */

if((pid = waitpid(pid, &status, 0)) < 0)

{

err_exit("waitpid error");

}

printf("%% ");

}

exit(0);

}

模拟终端调用其他可执行程序

  • 线程和线程ID:线程ID只在所属进程中有效
  • 出错处理:可用errnum(int errnum)和函数perror(const char *msg)打印
  • 用户标识
    • 用户ID:每个用户有一个唯一的用户ID
    • 组ID:允许组中各个成员之间共享资源,在/etc/group中
    • 附属组:每个用户可以属于不同组
  • 信号
    • 忽略信号:一般不建议这样做
    • 按系统默认处理
    • 捕获信号
  • 时间值
    • 日历时间:这个值是从1970年1月1日0时0分0秒以来所经过的秒数的累计值
    • 进程时间:也称为CPU时间,用以度量进程使用的中央处理器资源。
    • 时钟时间
      • 用户CPU时间
      • 系统CPU时间
    • 系统调用和库函数
      • 系统调用:不能被修改
      • 库函数:用户可自定义修改

第二章:UNIX标准及实现

  • 限制:编译时限制(头文件)、与文件和目录无关的运行时限制(sysconf函数)、与文件和目录有关的运行时限制(pathconf和fpathconf函数)
    1. ISO C限制:ISO C定义的所有编译时限制都列在头文件<limits.h>中,列出了32整型的linux系统的值
    2. POSIX限制:某些定义在<limits.h>中
    3. XSL限制
    4. 一些常用的基本系统数据类型

第三章:文件I/O

  • 引言:UNIX系统中的大多数文件I/O只需用到5个函数:open、read、write、lseek以及close,本章描述的函数也被称为不带缓冲的I/O,不带缓冲的意思是read和write都调用内核中的一个系统调用。
  • 文件描述符
  • 函数open和openat(头文件<fcntl.h>)
    1. int open(const char *path, int oflag, .../* mode_t mode */);
      1. path:是要打开或创建的文件名字
      2. oflag:可用来说明此函数的多个选项
        1. O_RDONLY:只读打开
        2. O_WRONLY:只写打开
        3. O_RDWR:可读可写打开
        4. O_EXEC:可执行打开
        5. O_SEARCH:可搜索打开(应用于目录)
        6. ...自行查阅
      3. int openat(int fd, const char *path, int oflag, ... /* mode_t mode */);
        1. fd参数有3种可能
          1. path参数指定的是绝对路径名,在这种情况下,fd参数被忽略,openat函数相当于open函数
          2. path参数指定的是相对路径名,fd参数指出了相对路径名在文件系统中的开始地址
          3. path参数指定了相对路径名,fd参数具有特殊值AT_FDCWD。在这种情况下,路径在当前工作目录中获取,openat函数在操作上与open函数类似。
  • 函数creat(头文件<fcntl.h>)
    1. int create(const char *path, mode_t mode);,此函数等效于open(path, O_WRONLY | O_CREADT | O_TRUNC, mode);
  • 函数close(头文件<unistd.h>)
    1. int close(int fd):当关闭一个文件时还会释放该进程加在该文件上的记录锁
      1. fd:文件描述符
  • 函数lseek(头文件<unistd.h>)
    1. off_t lseek(int fd, off_t offset, int whence):成功返回新文件偏移量,出错返回-1;
      1. 对参数offset的解释与参数whence的值有关
        1. 若whence是SEEK_SET,则将该文件的偏移量设置为距文件开始处offset个字节
        2. 若whence是SEEK_CUR则将该文件的偏移量设置为当前值加offset个字节,offset可为正或负。
        3. 若whence是SEEK_END,则将该文件的偏移量设置为文件长度加offset,offset可为正或负。
      2. 使用lseek时不要判断返回值是否小于0,而是要判断返回值是否等于-1,因为偏移量可能为负值。
      3. 超出文件原本大小的偏移量,会出现空洞文件

#include "apue.h"

int main(void)

{

if(lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)

{

printf("cannot seek\n");

}

else

{

printf("seek ok\n");

}

exit(0);

}

判断文件是否可以seek

#include "apue.h"

#include <fcntl.h>

char buf1[] = "abcdefghij";

char buf2[] = "ABCDEFGHIJ";

int main(void)

{

int fd;

if((fd = creat("file.hole",O_WRONLY | O_TRUNC)) < 0)

{

err_exit("create error");

}

if(write(fd, buf1, 10) != 10)

err_exit("buf1 write error");

if(lseek(fd, 16384, SEEK_SET) == -1)

err_exit("lseek error");

if(write(fd, buf2, 10) != 10)

err_exit("buf2 write error");

exit(0);

}

制造空洞文件

  • read函数(头文件<unistd.h>)
    1. ssize_t read(int fd, void *buf, size_t nbytes):返回读取到的字节数,若已将到文件尾返回0,失败返回-1。
      1. 当从终端设备读时,通常一次最多读一行
      2. 当从网络读时,网络中的缓冲机制可能造成返回值小于所要求读的字节数
      3. 当从管道或FIFO读时,如若管道包含的字节数少于所需的数量,那么read将只返回实际可用的字节数
      4. 当从某些面向记录的设备(如磁带)读时,一次最多返回一个记录
      5. ssize_t带符号的返回值,不带符号的size_t。
  • 函数write(头文件<unistd.h>)
    1. ssize_t write(int fd, const void *buf, size_t nbytes):成功返回写入的字节数,失败返回-1。
    2. 当写入成功后,文件偏移量设置在文件当前结尾处
  • I/O的效率
    1. 只用read和write函数复制文件一个文件时,不同的BUFFSIZE效果是不一样的

#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_exit("write error");

if(n < 0)

err_exit("read error");

exit(0);

}

用read和write函数复制文件

  • 文件共享
  • 原子操作
    1. 函数pread和pwrite(头文件<unistd.h>)
      1. ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset):返回读到的字节数,若已到文件尾,返回0;若出错返回-1。
        1. 调用pread时,无法中断其定位和读操作
        2. 不更新当前文件偏移量
      2. ssize_t pwrite(int fd, const void *buf, size_t n nbytes, off_t offset):返回值:若成功返回写入的字节数;失败返回-1。
    2. 另一个原子操作是在open函数的O_CREAT和O_EXCL选项进行说明时,如果没有O_EXCL这个选项,则多个进程创建同一个文件时,内容可能会被其他程序创建的文件覆盖。
  • 函数dup和dup2(头文件<unistd.h>)
    1. int dup(int fd):返回的新文件描述符一定是当前文件描述符中的最小值,
    2. int dup2(int fd, int fd2):可以用fd2参数指定新描述符的值。
      1. 如果fd2已经打开,则先将其关闭。
      2. 如若fd等于fd2,则dup2返回fd2,而不关闭它
      3. 否则fd的FD_CLOEXEC文件描述符就被清除,这样fd2在进程调用exec时是打开状态。
  • 函数svnc、fsvnc和fdatasvnc(头文件<unistd.h>)
    1. int fsync(int fd):只对由文件描述符fd指定的一个文件起作用,并且等待写磁盘操作结束后才返回
    2. int fdatasync(int fd):可用于数据库这样的应用程序,这种应用程序需要确保修改过的块立即写到磁盘上
    3. void sync(void):只是将所有修改过的块缓冲区排入写队列,然后就返回,不等待实际写磁盘操作结束
  • 函数fcntl(头文件<fcntl.h>):可以改变已经打开文件的属性
  • 函数ioctl(头文件<unistd.h>和<sys/ioctl.h>):
  • /dev/fd:Linux中,它把文件描述符映射成指向底层物理文件的符号链接

第四章:文件和目录

  • 引言:了解UNIX文件系统的结构以及符号链接
  • 函数stat、fstat、fstatat和lstat(头文件<sys/stat.h>):获取文件的信息
  • 文件类型
    1. 普通文件:文本或者二进制数据
    2. 目录文件:包含其他文件的名字以及指向这些文件有关信息的指针。只有内核可以直接写目录文件。进程必须使用特定函数才能更改目录。
    3. 块特殊文件:这种文件提供对设备(如磁盘)带缓冲的访问,每次访问以固定长度为单位进行。
    4. 字符特殊文件:这种类型的文件提供对设备不带缓冲区的访问,每次访问长度可变。系统中的所有设备要么是字符特殊文件,要么是块特殊文件
    5. FIFO:这种类型的文件用于进程间通信,有时也称为命名管道
    6. 套接字:这种类型的文件用于进程间的网络通信。套接字也可以用语一台宿主机上进程之间的非网络通信
    7. 符号链接:这中类型的文件指向另一个文件。

  • 设置用户ID和设置组ID
    1. 与一个进程相关联的ID有6个或更多,如下图所示:

  • 文件访问权限
    1. st_mode值包含了对文件的访问权限位,如下图所示:

  1. 前三行为所有者权限
  2. 中间三行为所属组权限
  3. 后面三行为其他用户权限
  • 新文件和目录的所有权
    1. 新文件的组ID可以是进程的有效组ID
    2. 新文件的组ID可以是它所在目录的组ID
  • 函数access和faccessat(头文件<unistd.h>)
    1. 按照实际用户ID和实际组ID进行访问权限测试,成功返回0,出错返回-1。

#include "apue.h"

#include <fcntl.h>

int main(int argc, char *argv[])

{

if(argc != 2)

err_exit("usage: a.out <pathname>");

if(access(argv[1], R_OK) < 0)

err_ret("access error for %s",argv[1]);

else

printf("read access OK\n");

if(open(argv[1], O_RDONLY) < 0)

err_ret("open error for %s", argv[1]);

else

printf("open for reading OK\n");

exit(0);

}

  • 函数umask(头文件<sys/stat.h>):为进程设置文件模式创建屏蔽字,并返回之前的值。
  • 函数chmod、fchmod和fchmodat:可以更改现有文件的访问权限
    1. chmod:在指定的文件上进行操作
    2. fchmod:对已打开的文件进行操作
    3. fchmodat:函数与chmod函数在下面两种情况下是相同的:
      1. 一种是pathname参数为绝对路径
      2. 一种是fd参数取值为AT_FDCWD而pathname参数为相对路径。
      3. 其他的,fchmodat计算相对于打开目录(由fd参数指向)的pathname。

#include "apue.h"

int main(void)

{

struct stat statbuf;

if(stat("foo", &statbuf) < 0)

err_exit("stat error for foo");

if(chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)

err_exit("chmod error for foo");

if(chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0)

err_exit("chmod error for bar");

exit(0);

}

改变foo和bar文件的权限

  • 粘着位(S_ISVTX:sticky bit)
    1. 作用,如果一个程序文件的这一位被设置了,那么当该程序第一次被执行,在其终止时,程序正文部分的一个副本仍被保存在交换区(程序的正文部分是机器指令),这使得在下次执行该程序时能较快的将其装载入内存。
    2. 在目录/tmp和/var/tmp是设置粘着位的典型候选者——任何用户都可以在这两个目录中创建文件。对这两个目录的权限通常是读、写和可执行。但是用户不应能删除或重命名属于其他人的文件,所以这两个目录的文件模式中都设置了粘着位。
  • 函数chown、fchown、fchownat和lchown:可用于更改文件的用户ID和组ID。
  • 文件长度
  • 文件截断:通过truncate和ftruncate函数截断文件
    1. 如果函数的参数length大于文件长度,将增文件的访问长度
    2. 如果函数的参数length小于文件长度,则大于文件长度的那部分将不在能访问。
  • 文件系统
    1. UFS:传统的基于BSD的UNIX文件系统
    2. PCFS:读、写DOS格式软盘的文件系统
    3. HSFS:读CD的文件系统

对于链接计数的解释

  • 函数link、linkat、unlink、unlinkat和remove
    1. link和linkat函数创建一个指向现有文件的链接
    2. unlink和unlinkat删除一个现有的目录项

#include "apue.h"

#include <fcntl.h>

int main(void)

{

if(open("tempfile", O_RDWR) , 0)

err_exit("open error");

if(unlink("tempfile") < 0)

err_exit("unlink error");

printf("file unlinked\n");

sleep(15);

printf("done\n");

exit(0);

}

用unlink对文件进行删除

  • 函数rename和renameat:文件或者目录可以用rename函数和renameat函数进行重命名
  • 符号链接:暂过
  • 创建和读取符号链接
    1. 通过symlink和symlinkat函数创建一个符号链接
    2. 通过readlink和readlinkat函数读取一个符号链接
  • 文件时间

  • 函数futimes、utimensat和utimes:更改文件的访问和修改时间
  • 函数mkdir、mkdirat和rmdir
    1. mkdir和mkdirat函数创建目录
    2. rmdir函数删除目录
  • 读目录

  • 函数chdir、fchdir和getcwd
    1. chdir和fchdir函数:更改当前工作目录
    2. getcwd函数:获取当前工作目录
  • 设备特殊文件
    1. 系统中于每个文件名关联的st_dev值是文件系统的设备号,该文件系统包含了这一文件名以及与其对应的i节点
    2. 只有特殊文件和块文件才有st_rdev值。此值包含实际设备的设备号

#include "apue.h"

#ifdef SOLARIS

#include <sys/mkdev.h>

#endif

#include <sys/types.h>

#include <sys/sysmacros.h>

int main(int argc, char *argv[])

{

int i;

struct stat buf;

for(i=1; i<argc; i++)

{

printf("%s: ", argv[i]);

if(stat(argv[i], &buf) < 0)

{

err_ret("stat error");

continue;

}

printf("dev = %d/%d", major(buf.st_dev), minor(buf.st_dev));

if(S_ISCHR(buf.st_mode) || S_ISBLK(buf.st_mode))

{

printf(" (%s) rdev = %d/%d",

(S_ISCHR(buf.st_mode)) ? "character" : "block",

major(buf.st_rdev), minor(buf.st_rdev));

}

printf("\n");

}

exit(0);

}

  • 文件访问权限位小结

第五章:标准I/O库

  • 引言:不止是UNIX系统,其他操作系统也实现了标准I/O库。库由ISO C标准说明。
  • 流和FILE对象
    1. 当用标准I/O库打开或创建文件时,我们已使一个流与一个文件相关联。
    2. fwide函数可用于设置流的定向
  • 标准输入、标准输出和标准出错:分别用stdin、stdout、stderr加以引用
  • 缓冲
    1. 标准I/O库提供缓冲的目的是尽可能减少使用read和write调用的次数,它对每个I/O流自动地进行缓冲管理,从而避免了应用程序需要考虑这一点所带来的麻烦。
    2. 标准I/O提供了以下3种类型的缓冲区:
      1. 全缓冲
      2. 行缓冲
      3. 不带缓冲
    3. 很多系统默认使用下列类型的缓冲:
      1. 标准出错是不带缓冲的
      2. 若是指向终端设备的流,则是带行缓冲的
      3. 其他的都为全缓冲
    4. 可使用函数setbuf和setvbuf更改缓冲类型

  1. 任何时候,我们可以使用函数fflush强制冲洗一个流,将所有未写的数据都被传送至内核。*若fp为NULL,此函数将导致所有输出流被冲洗。
  • 打开流

参数说明:

打开流的不同方法:

  1. 使用fclose函数关闭一个打开的流。
  • 读和写流
    1. 输入函数

  1. 判断读文件是否读完或者出错

  1. 输出函数

  • 每次一行I/O
    1. 输入一行

  1. 输出一行

  • 标准I/O的效率
  • 二进制I/O

  • 定位流

  • 格式化I/O

格式化输出的各种标志

精度修饰符

转换类型

格式化输入

  • 实现细节
  • 临时文件

#include "apue.h"

#include <errno.h>

void make_temp(char *template);

int main()

{

char good_temp[] = "/tmp/dirXXXXXX";

char *bad_temp = "/tmp/dirXXXXXX";

printf("trying to create first temp file...\n");

make_temp(good_temp);

printf("trying tp create second temp file...\n");

make_temp(bad_temp);

exit(0);

}

void make_temp(char *template)

{

int fd;

struct stat sbuf;

if((fd = mkstemp(template)) < 0)

{

err_exit("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 exist\n");

else

err_exit("stat failed");

}

else

{

printf("file exists\n");

unlink(template);

}

}

如何使用mkstemp函数

  • 内存流:暂过
  • 标准I/O的替代软件
    1. 标准I/O库的一个不足之处是效率不高,当使用每次一行函数fgets和fputs时,通常需要复制两次数据:一次是在内核和标准I/O缓冲区之间(当调用read和write时),第二次是在标准I/O缓冲区和用户程序中的行缓冲区之间。快速I/O库[AT&T 1990a中的fio(3)]避免了这一点,其方法是使读一行的函数返回指向该行的指针,而不是将该行复制到另一个缓冲区中。

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

  • 引言:对于数据文件的可移植接口
  • 口令文件

Linux系统包含其中7个字段

#include <pwd.h>

#include <stddef.h>

#include <string.h>

struct passwd *getpwnam(const char *name)

{

struct passwd *ptr;

setpwent();

while((ptr = getpwent()) != NULL)

{

if(strcmp(name, ptr->pw_name))

{

break;

}

}

endpwent();

return(ptr);

}

getpwnam的一种实现

  • 阴影文件:暂过
  • 组文件:暂过
  • 附属组ID:暂过
  • 实现区别:暂过
  • 其他数据文件:暂过
  • 登录账户记录:暂过
  • 系统标识
    1. int uname(struct utsname *name)函数:获取主机和操作系统有关的信息
    2. int gethostname(char *name, int namelen)函数:获取TCP/IP网络上的主机名
  • 时间和日期例程
    1. time_t time(time_t *calptr):获取当前时间和日期
    2. int clock_gettime(clockid_t clock_id, struct timespec *tsp):可获取指定时钟时间,它把时间表示为秒和纳秒
    3. ...自行查阅其他获取时间函数

第七章:进程环境

  • 引言:学习进程工作环境
  • main函数
    1. C程序总是从mian函数开始执行,main函数原型为:int main(int argc, char *argv[]);
    2. 在调用main函数前,先调用一个特殊的启动例程。可执行程序文件将此启动例程指定为程序的起始地址。
    3. 启动例程从内核取得命令行参数和环境变量值,为调用main函数做好准备
  • 进程终止
    1. 有8种方式使进程终止
      1. 其中五种为正常终止
        1. 从main返回
        2. 调用exit:先执行一些清理处理,然后返回内核
        3. 调用_exit或_Exit:立即进入内核
        4. 最后一个线程从其启动例程返回
        5. 从最后一个线程调用pthreadd_exit
      2. 异常终止有3种方式
        1. 调用abort
        2. 接到一个信号
        3. 最后一个线程对取消请求做出响应

  1. 内核使程序执行的唯一方法是调用一个exec函数,进程自愿终止的唯一方法是显式或隐式地调用exit或者_exit和_Exit。进程也可以非自愿地由一个信号使其终止。

#include "apue.h"

static void my_exit1(void);

static void my_exit2(void);

int main(void)

{

if(atexit(my_exit2) != 0)

err_exit("can't register my_exit2");

if(atexit(my_exit1) != 0)

err_exit("can't register my_exit1");

if(atexit(my_exit1) != 0)

err_exit("can't register my_exit1");

printf("main is done\n");

return 0;

}

static void my_exit1(void)

{

printf("first exit handler\n");

}

static void my_exit2(void)

{

printf("second exit handler\n");

}

如何使用atexit函数

  • 命令行参数:过
  • 环境表
    1. 可以通过getenv和putenv函数来访问特定的环境变量

  • C程序的存储空间布局

  1. 正文段:CPU执行的机器命令部分。正文段时可共享的,只读的。
  2. 初始化数据段:通常称为数据段,包含了程序中需明确地赋初值的变量。
  3. 栈:自动变量以及每次函数调用时所需保存的信息,被调用的函数在栈上为其自动和临时变量分配存储空间。
  4. 堆:通常在堆中进行动态存储分配
  5. *注意*:未初始化数据段的内容不存放在磁盘程序中,内核在程序开始运行前将他们都设置为0,需要存放在磁盘程序文件中的段只有正文段和初始化数据段。
  • 共享库:使用共享库可以减少可执行文件的长度,但是会增加一些运行时间开销
  • 存储空间分配
    1. ISO C说明了3个用于存储空间动态分配的函数
      1. malloc:分配指定字节数的存储区,此存储区中的初始值不确定。
      2. calloc:为指定数量指定长度的对象分配存储空间,该空间的每一位bit都初始化为0。
      3. realloc:增加或减少以前分配区的长度。当增加时,可能需要将以前分配区的内容移动到另一个足够大的区域,新增加区域的初始值不确定。
  • 环境变量

获取和设置环境变量函数

  • 函数setjmp和longjmp:暂过
  • getrlimit和setrlimit:暂过

第八章:进程控制

  • 引言
  • 进程标识:每个进程都有一个非负整型表示的唯一进程ID。

获取进程ID的函数

  • 函数fork
    1. fork创建的新进程被称为子进程,fork函数被调用一次,但返回两次,父进程中返回子进程的ID,子进程中返回值为0。
    2. 子进程可以通过函数getppid以获取其父进程的进程ID

  • 函数vfork:和fork类似
  • 函数exit
    1. 在main函数内执行return语句等效于exit
    2. 调用exit函数,此操作包括调用各终止处理程序,然后关闭所有标准I/O流等。
    3. 调用_exit或_Exit函数,其目的是为程序提供一种无需运行终止处理程序或信号处理程序而终止的方法。并不冲洗标准I/O流。
    4. 进程的最后一个线程在其启动例程中执行return语句。但是该线程的返回值不用作进程的返回值。当最后一个线程从其启动例程返回时,该进程以终止状态0返回。
    5. 3种异常终止具体如下:
      1. 调用abort。它产生SIGABRT信号,这是下一种异常终止的一种特例。
      2. 当进程接收到某些信号时
      3. 最后一个线程对“取消”请求作出响应。
    6. **不管进程如何终止,最后都会执行内核中的同一段代码,这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器。
  • 函数wait和waitpid
    1. 当调用wait和waitpid的进程可能会发生的
      1. 如果其所有子进程都还在运行,则阻塞。
      2. 如果一个子进程已终止,正在等待父进程获取其状态,则取得该子进程的终止状态立即返回。
      3. 如果它没有任何子进程,则立即出错返回。

函数

waitpid函数参数

  • 函数waitid

  • 函数wait3和wait4

函数

  • 竞争关系:当进程都企图对共享数据进行某些处理,而最后的结果又取决于进程运行的顺序时,我们认定为竞争条件。

#include "apue.h"

static void charatatime(char *);

int main(void)

{

pid_t pid;

if((pid = fork()) < 0)

{

err_exit("fork error");

}

else if(pid == 0)

{

charatatime("output from child\n");

}else {

charatatime("output from parent\n");

}

exit(0);

}

static void charatatime(char *str)

{

char *ptr;

int c;

setbuf(stdout, NULL);

for(ptr = str; (c = *ptr++) != 0; )

putc(c, stdout);

}

当直接通过fork创建子进程时,父进程和子进程哪一个先运行是不确定的,这个和CPU的调度算法有关,如果要按照一定的顺序执行父子进程,可以使用TELL_PARENT()、TELL_CHILD()、WAIT_PARENT()和WAIT_CHILD()函数

  • 函数exec
    1. 当进程调用一种exec函数时,该进程执行的程序完全替换为新程序,而新程序则从其main函数开始执行。
    2. 因为调用exec并不创建新进程,所以前后的进程ID并未改变。

  • 更改用户ID和更改组ID:暂过
  • 解释器文件:暂过
  • 函数system:执行命令行指令
  • 进程会计:暂过
  • 用户标识:通过getlogin函数获取一个用户有多个登录名,这些登录名又对应着同一个用户的ID的登录名
  • 进程调度:通过改变进程的nice值选择优先级。其他内容暂过。
  • 进程时间:任一进程可调用times函数获取它自己以及已终止子进程的时间(墙上时钟时间、用户CPU时间、系统CPU时间)

第九章:进程关系

  • 引言:介绍登录shell和所有从登录shell启动的进程之间的关系
  • 终端登录
    1. BSD终端登录

  1. Lonux终端登录
    1. 类似于BSD。在System V的init文件格式之后,有些Linux发行版的init程序使用了管理文件方式。在这些系统中,/etc/inittab包含配置信息,指定了init应当为之启动getty进程的各终端设备。
  • 网络登录
    1. 为了使同一个软件既能处理终端登录,又能处理网络登录,系统使用了一种称为伪终端的软件驱动程序,它仿真串行终端的运行行为,并将终端操作映射为网络操作。
      1. BSD网络登录
        1. Linux网络登录
          1. 除了有些版本使用拓展的因特网服务守护进程xinetd代替inetd进程外,Linux网络登录的其他方面与BSD网络登录相同。
  • 进程组:可通过函数getpgid和函数setpgid获取和加入进程组
  • 会话:是一个或多个进程组的集合。
  • 控制终端

  • 函数tcgetpgrp、tcsetpgrp和tcgetsid

  • 作业控制:暂过
  • shell执行程序:暂过
  • 孤儿进程组:暂过
  • FreeBSD实现:暂过

第十章:信号

  • 引言:信号是软件中断。很多比较重要的应用程序都需要处理信号。信号提供了一种处理异步事件的方法。
  • 信号概念

  • 函数signal
    1. 函数原型

#include "apue.h"

static void sig_usr(int);

int main(void)

{

if(signal(SIGUSR1, sig_usr) == SIG_ERR)

err_exit("can't catch SIGUSR1");

if(signal(SIGUSR2, sig_usr) == SIG_ERR)

err_exit("can't catch SIGUSR2");

for(;;)

pause();

}

static void sig_usr(int signo)

{

if(signo == SIGUSR1)

printf("received SIGUSR1\n");

else if(signo == SIGUSR2)

printf("received SIGUSR2\n");

else

err_ret("received signal %d\n", signo);

}

singnal代码测试

  • 不可靠的信号:UNIX早期的一些问题
  • 中断的系统调用:
    1. 系统调用分成两类
      1. 低速系统调用
        1. 如果某些类型文件(如读管道、终端设备和网络设备)的数据不存在,则读操作可能会被调用者永远阻塞
        2. 如果这些数据不能被相同的类型文件立即接受,则写操作可能会使调用者永远阻塞;
        3. 在某些条件发生之前打开某些类型文件,可能会发生阻塞
        4. pause函数和wait函数
        5. 某些ioctl操作
        6. 某些进程间通信函数
      2. 其他系统调用
  • 可重入函数
  • SIGCLD语义
  • 可靠信号术语和语义
  • 函数kill和raise
  • 函数alarm和pause
  • 信号集
  • 函数
  • 函数
  • 函数
  • 函数
  • 函数
  • 函数
  • 函数
  • 函数
  • 函数
  • 作业控制信号
  • 信号名和编号

第十一章:线程

  • 引言:线程控制和线程间的同步互斥
  • 线程概念:一个进程在在某一时刻只能做一件事,有了多个线程后,在程序设计时就可以把进程设计成在某一时刻能够做不止一件事,每一个线程处理各自独立的任务。
  • 线程标识:每个线程都有一个线程ID,线程ID只有在它所属的进程上下文中才有意义。
  • 线程创建

函数原型

  • 线程终止
    • 单线程可以通过3种方式退出,因此可以在不终止整个进程的情况下,停止它的控制流
      • 线程可以简单地从启动例程中返回,返回值是线程的退出码。
      • 线程可以被同一进程中的其他线程取消
      • 线程调用pthread_exit函数
        • 函数原型

        • 调用pthread_join函数自动把线程置于分离状态。其他线程可以通过此函数获取指定线程的退出码。
    • 线程和进程的比较

 

  • 线程同步
    • 互斥量
      • 初始化和销毁互斥量

      • 上锁和解锁互斥量

      • 代码示例
#include <stdlib.h>
#include <pthread.h>
#include <stdlib.h>
#include "apue.h"


pthread_mutex_t MyMutex;


int num=0;

void *work1(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&MyMutex);
        num = 10;
        
        printf("work1 output %d\n", num);
        pthread_mutex_unlock(&MyMutex);
        sleep(1);
    }
}

void *work2(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&MyMutex);
        num = 20;

        printf("work2 output: %d\n",num);
        pthread_mutex_unlock(&MyMutex);
        sleep(1);
    }
}


int main(int argc, char *argv[])
{
    pthread_t pid1;
    pthread_t pid2;

    //init mutex
    pthread_mutex_init(&MyMutex, NULL);

    int err;
    err = pthread_create(&pid1, NULL, work1, NULL);
    if(err != 0)
    {
        err_exit("create thread1 fail"); 
    }
    err = pthread_create(&pid2, NULL, work2, NULL);
    if(err != 0)
    {
        err_exit("create thread2 fail"); 
    }

    while(1)
    {
        pthread_mutex_lock(&MyMutex);
        num = 30;
        printf("main out put: %d\n", num);
        pthread_mutex_unlock(&MyMutex);
        sleep(1);
    }

    return 0;
}

    • 避免死锁
      • 产生的原因
        • 线程试图对同一互斥量加锁两次,那么它自身就会陷入死锁状态
        • 当有两个以上的互斥量的时候,如果让一个线程一直占着第一个互斥量并且试图对第二个互斥量加锁,而另一个线程一直占着第二个互斥量并且试图对第一个互斥量加锁时,也会造成死锁。
      • 解决办法
        • 避免循环等待(Avoid Circular Wait):确保线程或进程在获取资源时按照相同的顺序进行,并且不允许一个线程同时持有多个资源。这样可以避免发生循环等待的情况。

        • 使用资源分级(Resource Ordering):按照固定的顺序申请和释放资源,以避免出现资源争夺的问题。例如,所有线程都必须按照固定的顺序依次请求资源,而不是同时请求所有资源。

        • 引入超时机制(Timeout Mechanism):为获取资源设置超时时间,在规定的时间内未能成功获取资源,则放弃当前请求。这可以避免线程因为等待资源而一直处于阻塞状态。

        • 使用资源剥夺(Resource Preemption):当某些资源被其他线程持有时,如果当前线程需要这些资源才能继续执行,可以考虑先剥夺其他线程的资源,使得当前线程可以继续执行。但这需要谨慎处理,避免导致其他线程长时间等待。

        • 合理设计资源分配策略:在设计系统时,合理规划资源的分配和释放策略,避免资源过度分配或浪费,以减少潜在的资源争夺和死锁风险。

        • 使用同步机制和锁的最小化原则:只在必要时使用锁和同步机制,并尽量缩小锁的作用范围,以减少并发操作中可能出现的死锁情况。

        • 进行死锁检测和恢复:实施死锁检测算法来及时检测死锁的发生,并采取相应的措施进行恢复,例如回滚操作或终止其中一个线程。

    • 函数pthread_mutex_timedlock:此函数允许绑定线程阻塞时间,超时则返回错误码
      • 函数原型

    • 读写锁
      • 说明:与互斥量类似,不过读写锁允许更高的并行性。互斥量要么是锁住状态,要么是不加锁状态,而且一次只有一个线程可以对其加锁。读写锁可以有3种状态
        • 读模式下枷锁状态
        • 写模式下加锁状态
        • 不加锁状态
      • 一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁
      • 相关函数

    • 带有超时的读写锁
      • 函数原型

    • 条件变量
      • 是另外一种同步机制,条件变量在给多个线程提供一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。 
      • 相关函数

    • 自旋锁
      • 自旋锁(Spin Lock)是一种线程同步机制,用于保护共享资源,类似于互斥锁(Mutex)。与互斥锁不同的是,自旋锁在等待获取锁时,线程不会被阻塞挂起,而是通过循环忙等(自旋)的方式一直尝试获取锁。

        当一个线程尝试获取自旋锁时,如果锁已经被其他线程占用,则该线程会进入一个忙等的循环,不断检查锁是否被释放。它会持续执行这个循环直到锁可用。因此,自旋锁适用于以下场景:

        • 临界区的代码执行时间较短:如果临界区的代码执行速度非常快,那么自旋锁可能会更高效,因为线程在等待锁期间没有被挂起和唤醒的开销。

        • 线程竞争概率低:如果获取锁的线程竞争较少,那么自旋锁的性能可能更好。反之,如果有多个线程频繁地竞争自旋锁,可能会造成大量的忙等,浪费CPU资源。

      • 相关函数

    • 屏障
      • 屏障(Barrier)是一种线程同步机制,用于确保多个线程在某个点上达到同步。它允许线程在执行过程中等待其他线程,直到所有线程都到达屏障位置后再一起继续执行。

        屏障通常用于解决并行计算中的同步问题,特别是当一组线程需要在某个阶段完成工作后才能进入下一个阶段时。

        屏障的基本思想是将一组线程分为多个阶段,每个阶段都有一个屏障位置。当一个线程到达屏障位置时,它会被阻塞等待其他线程也到达屏障位置。只有当所有线程都到达屏障位置后,它们才会同时解除阻塞并继续向下执行。

      • 相关函数

第十二章:线程控制

  1. 引言:讲解控制线程行为方面的详细内容

  2. 线程限制:暂过

  3. 线程属性:暂过

  4. 同步属性

    1. 互斥量属性:暂过

    2. 读写锁属性:暂过

    3. 条件变量属性:暂过

    4. 屏障属性:暂过

  5. 重入:可被多个线程同时安全的调用

    1. 不能证线程安全的函数

替代的线程安全函数

    1. POSIX提供了以线程安全的方式管理FILE对象的方法。

不加锁版本基于字符的标准I/O例程

  1. 线程特定数据:是存储和查询某个特定线程相关数据的一种机制

    1. 作用
      1. 希望每个线程可以访问他自己的数据副本,而不用担心与其他线程的同步访问问题。
      2. 防止某些线程与其它线程的数据相混淆。
      3. 让基于进程的接口适应多线程环境的机制。
    2. 创建于数据关联的键,用于获取对线程特定数据的访问

取消键与线程特定数据值之间的关联关系

    1. 解决线程之间的竞争

创建键时避免出现冲突的正确方法:

#include <pthread.h>

void destructor(void*);

pthread_key_t key;
pthread_once_t init_done = PTHREAD_ONCE_INIT;
int err=0;

void thread_init(void)
{
    err = pthread_key_create(&key, destructor);
}

int threadfunc(void *arg)
{
    pthread_once(&init_done, thread_init);
    .
    .
    .
}

创建完键后,可以通过下面的函数获取线程特定数据的地址和把键值和线程特定数据关联

使用线程特定数据来维护每个线程的数据缓冲区副本:

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

  1. 取消选项:暂过

  2. 线程和信号

    1. 在线程中我们可以使用互斥量来保护标志
#include "apue.h"
#include <pthread.h>
#include <sys/select.h>
#include <signal.h>
#include <asm-generic/signal-defs.h>

int quitflag;
sigset_t mask;

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t waitloc = PTHREAD_COND_INITIALIZER;

void *thr_fn(void *arg)
{
    int err, signo;

    for(;;){
        err = sigwait(&mask, &signo);
        if(err != 0)
            err_exit("sigwait failed %d", err);
        switch(signo){
            case SIGINT:
                printf("\ninterrupt\n");
                break;
            case SIGQUIT:
                pthread_mutex_lock(&lock);
                quitflag = 1;
                pthread_mutex_unlock(&lock);
                pthread_cond_signal(&waitloc);
                return(0);
            default:
                printf("unexpected signal %d\n", signo);
                exit(1);
        }
    }
}

int main(void)
{
    int err;
    sigset_t oldmask;
    pthread_t tid;

    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGQUIT);
    if((err = pthread_sigmask(SIG_BLOCK, &mask, &oldmask)) != 0)
        err_exit("SIG_BLOCK error %d", err);

    err = pthread_create(&tid, NULL, thr_fn, 0);
    if(err != 0)
        err_exit("can't create thread %d", err);
    
    pthread_mutex_lock(&lock);
    while(quitflag == 0)
        pthread_cond_wait(&waitloc, &lock);
    pthread_mutex_unlock(&lock);

    quitflag = 0;

    if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        err_exit("SIG_SETMASK error");
    exit(0);
}

  1. 线程和fork

    1. 清除锁状态可以使用函数

    1. 如何使用pthread_atfork和fork处理程序
#include "apue.h"
#include <pthread.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)
        err_exit("can't lock lock1 in prepare handler %d", err);
    if((err = pthread_mutex_lock(&lock2)) != 0)
        err_exit("can't lock lock2 in prepare handler %d", err);
}

void parent(void)
{
    int err;

    printf("parent unlocking locks...\n");
    if((err = pthread_mutex_unlock(&lock1)) != 0)
        err_exit("can't unlock lock1 in parent handler %d", err);
    if((err = pthread_mutex_unlock(&lock2)) != 0)
        err_exit("can't unlock lock2 in parent handler %d", err);
}

void child(void)
{
    int err;

    printf("child unlocking locks...\n");
    if((err = pthread_mutex_unlock(&lock1)) != 0)
        err_exit("can't unlock lock1 in child handler %d", err);
    if((err = pthread_mutex_unlock(&lock2)) != 0)
        err_exit("can't unlock lock2 in child handler %d", err);
}

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

int main(void)
{
    int err;
    pid_t pid;
    pthread_t tid;
    
    if((err = pthread_atfork(prepare, parent, child)) != 0)
        err_exit("can't install fork handlers %d", err);
    if((err = pthread_create(&tid, NULL, thr_fn, 0)) != 0)
        err_exit("can't create thread %d", err);

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

    if((pid = fork()) < 0)
        err_exit("fork failed");
    else if(pid == 0)
        printf("child returned from fork\n");
    else
        printf("parent returned from fork\n");
    exit(0);
}

  1. 线程和I/O

    1. 通过使用pread和pwrite函数在多线程环境下对同一文件进行原子操作读写文件,不会互相受到影响

第十三章:守护进程

  1. 引言:守护进程是生存期长的一种进程。它们常常在系统引导装入时启动,仅在系统关闭时才终止。因为它们没有控制终端,所以说它们是在后台运行的。

  2. 守护进程的特征

    1. 大多数守护进程都已超级用户(root)特权运行。
    2. 所有的守护进程都没有控制终端,其终端号被设置为问号。
    3. *用户层守护进程的父进程是init进程。
  3. 编程规则

    1. 首先要做的是调用umask将文件模式创建屏蔽字设置为一个已知值(通常是0)。由继承得来的文件模式创建屏蔽字可能会被设置
  4. 出错记录

  5. 单实例守护进程

  6. 守护进程的惯例

  7. 客户进程-服务器进程模型

第十四章:高级I/O

  1. 引言

    1. 本章介绍:非阻塞I/O、记录锁、I/O、多路转接(select和poll函数)、异步I/O、readv和writev函数以及存储映射I/O(mmap)
  2. 非阻塞I/O

    1. 非阻塞I/O函数:open、read、write
    2. 对于给定的描述符,有两种为其指定非阻塞I/O的方法
      1. 如果调用open获取描述符,则可指定O_NOBLOCK标志
      2. 对于已经打开的一个文件描述符,则可调用fcntl,由该函数打开O_NOBLOCK文件状态标志
    3. 非阻塞I/O的实例:

      #include "apue.h"
      #include <errno.h>
      #include <fcntl.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(STDOUT_FILENO, O_NONBLOCK);
      
          exit(0);
      }

  3. 记录锁

    1. 功能:当第一个进程正在读或者修改文件的某个部分时,使用记录锁可以阻止其他进程修改同一文件区。
    2. fcntl函数原型:

    3. 对于记录锁cmd参数是:F_GETLK、F_SETLK或F_SETLKW:
      1. F_GETLK:判断有flockptr所描述的锁是否会被另外一把锁所排斥(阻塞)。如果存在一把锁,它阻止创建由flockptr所描述的锁,则该现有锁的信息将重写flockptr指向的信息。如果不存在这种情况,则除了将l_type设置为F_UNLCK之外,flockptr所指向结构中的其他信息保持不变。
      2. F_SETLK:设置由flockptr所描述的锁。如果我们试图获得一把读锁(l_type为F_RDLCK)或写锁(l_type为F_WRLCK),而兼容性规则阻止系统给我们这把锁,那么fcntl会立即出错返回,此时errno为EACCES或EAGAIN(一般都为EAGAIN)。
      3. F_SETLKW:这个命令是F_SETLK的阻塞版本。如果所请求的读锁或写锁因另一个进程当前已经对所请求的区域的某些部分进行了加锁而不能被授予,那么调用进程会被置为设置为休眠。如果请求创建的锁已将可用,或者休眠由信号中断,则该进程被唤醒。
    4. 对于上面的函数了解到,我们通过参数F_GETLK测试能否建立一把锁,然后用F_SETLK或F_SETLKW企图建立那把锁,这两者不是一个原子操作。因此不能保证调用之间不会有另一个进程插入并建立一把相同的锁。如果不希望在等待锁变为可用时产生阻塞,就必须处理由F_SETLK返回的可能的出错。
    5. 第三个参数指向一个flock结构的指针:

      1. 锁类型:F_RDLCK(共享读锁)、F_WRLCK(独占性写锁)或F_UNLCK(解锁一个区域)
      2. 要加锁或解锁的区域的起始字节偏移量(l_start和l_whence)
      3. 区域字节长度(l_len)
      4. 进程的ID(l_pid)持有的锁能阻塞当前进程(仅由F_GETLK返回)
    6. 关于加锁和解锁区域的说明还要注意下列几项规则:
      1. 指定区域起始偏移量的两个元素lseek函数中最后两个参数类似。
      2. 锁可以在当前文件尾端出开始或者越过尾端开始,但不能在文件起始位置之前开始
      3. 如若l_len为0,则表示锁的范围可以扩展到最大可能偏移量。这意味着不管向文件中追加多少数据,它们都可以处于锁的范围内,而且起始位置可以是文件中的任意一个位置。
      4. 对于整个文件的加锁,我们设置l_start和l_whence指向文件的起始位置,并且指定长度(l_len)为0。
  4. I/O多路转接:将描述符列表,加入到一个函数中,直到这些描述符中的一个已经准备好I/O时,函数返回。

    1. 函数select和pselect
      1. select函数原型:

      2. select函数创建三中类型的描述符集合:可读、可写和异常描述符集合
      3. 可以设置愿意等待的时间长度,单位为秒和微秒。
    2. 函数poll
      1. 函数原型:

      2. pollfd结构体原型:

      3. 可用于任何类型的文件描述符
      4. 此函数构造一个pollfd结构的数组,每个元素指定一个描述符编号以及我们对该描述符感兴趣的条件
      5. poll的events和revents标志:

  5. 异步I/O

    1. System V异步I/O
      1. System V的异步I/O信号是SIGPOLL
      2. 产生SIGPOLL信号的条件:

    2. BSD异步I/O
      1. 异步I/O是信号SIGIO和SIGURG的组合。
      2. 为了接受SIGIO信号,需要执行以下3步:
        1. 调用signal或sigaction为SIGIO信号建立信号处理程序。
        2. 以命令F_SETOWN调用fcntl来设置进程ID或进程组ID,用于接收对于该文件描述符的信号。
        3. 以命令F_SETFL调用fcntl设置O_ASTNC文件状态标志,使在该描述符上可以进行异步I/O。
    3. POSIX异步I/O
      1. 函数介绍:
      2. 实际例子对比传统I/O和POSIX异步I/O
        1. 传统I/O:
        2. POSIX异步I/O:
  6. 函数readv和writev

    1. 作用:用于在一次函数调用中读、写多个非连续缓冲区。也可将这两个函数称为散布读和聚集写
    2. 函数原型:

      1. 其中第二个参数的结构体为:

      2. readv和writev的iovec结构:

  7. 函数readn和writen

    1. 作用:分别是读、写指定的N字节数据。
    2. 函数原型:

    3. 实现原理:
  8. 存储映射I/O

    1. 作用:将磁盘文件映射到存储空间中的一个缓冲区上,就可以不使用read和write的情况下执行I/O。
    2. 函数原型:

    3. 参数说明:
      1. addr:用于指定映射存储区的起始地址。通常将其设置为0,这表示由系统选择该映射区的起始地址。
      2. fd参数:是指定要映射文件的描述符。映射前要先打开文件。
      3. len参数:是映射的字节数。
      4. off参数:是要映射字节在文件中的起始偏移量。
      5. prot参数指定了映射存储区的保护要求:

        此要求受到文件打开时的权限限制。
      6. flag参数:为影响映射存储区的多种属性:
        1. MAP_FIXED:
        2. MAP_SHARED:
        3. MAP_PRIVATE:
    4. 可通过函数mprotect更改一个现有映射的权限:
      1. 函数原型:

第十五章:进程间通信

  1. 引言

  2. 管道

  3. 函数popen和pclose

  4. 协同进程

  5. FIFO

  6. XSI IPC

    1. 标识符和键

    2. 权限结构

    3. 构建权限

    4. 优点和缺点

  7. 消息队列

  8. 信号量

  9. 共享存储

  10. POSIX信号量

  11. 客户进程-服务器进程属性

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值