本文帮你在 Unix 下玩转 C 语言

  • shell是一种特殊的应用程序(命令行解释器),他为运行其他应用程序提供了一个接口。

  • posix规范了操作系统是什么样

  • 每个进程都有一个工作目录(又叫当前目录),相对路径都是从工作目录开始解释。

  • Ctrl+D是文件结束字符

  • read读指定字节数;fgets是读取一行

  • 三个进程控制函数:fork exec waitpid。 waitpid【此函数获取信息,释放资源】父进程等待子进程终止,可以得到子进程何时终止。system函数是在exec外包了一层。

  • execlp要求参数以null结束,换行符不可以

  • 线程id只在它所属进程内起作用,在另一个进程中无意义,可以使用线程id引用相应的线程。

  • 一个用户可以属于多至16个组

  • ctrl+c中断键,ctrl+\退出键,等价于kill函数。kill(pid, SIGTERM)向另一进程发信号,发起信号的必须是该进程的所有者。

  • (gdb)set follow-fork-mode child使gdb进入子进程,事实证明不设置(默认调试父进程)这句无法进入pid==0的语句块。子进程exit后无法再设置断点gdb信息丢失,此时run可能启动的不是父进程而是孙进程。

  • fork会复制fork开始直到函数结束的代码【共享代码正文,但复制全部变量】

  • 日历时间:1970至今秒数,time_t类型用于保存这种时间。

  • 进程时间:cpu时间,clock_t类型用于保存这种时间。

  • 系统cpu时间是进程执行内核程序的时间。执行用户指令的时间是用户cpu时间。两者之和是cpu时间。时钟时间【墙上钟时间】,是进程运行的时间总量,和进程数有关。time ls【ls可换成任意程序名】 查看时间。

  • clock_t times(struct tms* buf)成功返回墙上钟时间【必须使用相对值,做差】

  • 库函数不一定调用系统调用。应用程序可以直接调用系统调用,也可以通过C库函数调用系统调用。

  • ISO C标准有24个头文件(包括stdlib.h,stdio.h)。

  • 接口即协议。

  • 很多程序需要为路径分配存储区

  • 守护进程:后台运行且不与终端相连接的一种进程。

  • 与文件或目录无关的选项用sysconf确定,与文件或目录有关的选项用pathconf,fpathconf确定。

  • unix系统的大多数文件I/O只需要用到5个函数:open close read write lseek,都是不带缓冲的I/O。不带缓冲指的是read write都调用内核的一个系统调用。不带缓冲的io不是iso c的组成部分,是posix的组成部分。

  • 对内核而言,所有的打开的文件都通过文件描述符(非负整数)引用。0 1 2 分别是输入 输出 错误 的描述符。文件描述符变化范围0-OPEN_MAX(表示每个进程可以打开OPEN_MAX个文件)。

  • open函数:int fileId【返回最小的未用文件描述符数值】 = open(tmpPtr->_fileName【文件名】,O_RDWR【读、写打开】|O_CREAT【如果不存在则创建】, 0666【配合O_CREATE指定新文件访问权限】);

  • close(fileId);关闭文件同时释放进程加在该文件上的所有记录锁。进程终止时内核自动关闭它打开的文件。

  • 返回文件偏移量【偏移量始终存在,读、写操作从它指向的位置开始】=lseek(fileId,offset【每一个打开的文件都有一个当前文件偏移量,默认0,除非指定O_APPEND】,SEEK_SET【将偏移量设为文件开始处offset字节】)

  • lseek返回-1说明文件描述符对应的文件是管道、fifo或网络套接字。某些设备允许负的偏移量。

  • od -c 文件名 【-c表示以字符方式打印文件内容】 ls -ls 查看文件占用多少个磁盘块

  • nRead【返回读到字节数】 = read(flag_fd【文件描述符】, buffer【读取数据到buffer中】, length【一次读取字节数】) 【成功返回前,偏移量增加读到的字节数】

  • int bytes_write【返回写入字节数】 = write(fileHandle,ptr,writeSize【写入字节数】) 【写操作从当前偏移量开始,成功后偏移量自动增加写入字节数】

  • 测量文件读写由于缓存机制,在第一次之后可能不准确。

  • 每个进程都有一张打开文件描述符表->文件表(当前文件偏移量)->v节点信息

  • 可能有多个文件描述符指向同一文件表项,多个文件表项指向一个v节点表。多进程读同一个文件没有问题,但是写同一个文件会有问题->原子操作。

  • open中用O_CREAT和O_EXCL可以将测试和创建合并为一个原子操作。原子操作指多步组成的操作要么执行完所有步骤,要么一步也不执行。

  • 先lseek再write不可能是原子操作。两个函数间内核可以挂起进程。

  • pread(..., off_t offset) pwrite(..., off_t offset) 相当于顺序调用lseek和read,与顺序调用的区别:无法中断、不更新文件指针

  • O_APPEND方式打开文件,每次write,文件偏移量自动定位到文件尾。

  • 新的文件描述符 = dup(int filedes) dup2(int filedes【被复制】,int filedes2【指定数值】) 复制现存的文件描述符,与参数filedes共享同一个文件表项。fcntl(..)也可以复制文件描述符。

  • sync将块缓冲区排入写队列,不等实际写磁盘。fsync对单一文件起作用,等写磁盘结束返回,更新属性。fdatasync只影响文件的数据部分。

  • fcntl(..)返回值和命令有关,可以返回文件状态,文件描述符。可以修改文件状态。

  • 5<>temp表示在文件描述符5上打开文件供读写。

  • 终端I/O是 ioctl的最大使用方面。

  • digit1 > &digit2表示要将digit1重定向至描述符2的同一个文件。

  • shell从左到右处理命令

  • struct stat sA【stat结构体包含磁盘号,所有者,访问修改时间等属性】; int retA【返回小于0表示文件不存在】 = stat(fileNameA.c_str(),&sA【stat函数将填写sA】); ls -l就是使用的stat(...)函数

  • lstat(...)的增强功能是检测符号链接

  • 文件类型信息包含在stat结构的st_mode成员中,有下面几种类型。
    普通文件【包含某种数据的文件,数据是文本还是二进制对内核而言无区别,对文件内容的解释由处理该文件的应用程序进行。例外:二进制可执行文件遵守内核理解的统一格式】
    目录文件【包含其他文件的名字以及指向与这些文件有关信息的指针】
    块设备文件【磁盘,提供对设备带缓冲的访问】
    字符设备文件【键盘,提供对设备不带缓冲的访问】
    FIFO 又名管道文件,shell里的竖线 | 【用于进程间通信】
    套接字【这种文件用于进程间的网络通信,也可用于一台机上进程间的非网络通信】
    符号链接【这种文件类型指向另一个文件】

  • 进程间通信(IPC)对象也表示为文件:消息队列、信号量、共享存储对象。

  • 执行一个程序时exec(...)会保存有效用户ID和有效组ID。通常有效ID==实际ID。

  • 当文件的有效用户ID设置为文件所有者ID时,如果所有者为root,即使被一个普通用户执行,该进程也具有超级权限。

  • 文件访问权限:第一个规则是我们用名字打开一个文件时,对该名字包含的每一个目录,包括她可能隐含的当前工作目录(./)都应具有执行权限。对文件有适当的权限,取决于以何种方式打开。

  • 对目录的读权限使我们可以获得该目录所有文件名列表。对目录的执行权限使我们可以通过该目录,也就是【搜索】该目录,寻找一个特定的文件名。

  • 创建文件需要对目录有写权限和可执行权限。删除文件需要对目录有写和可执行权限【实际是减少文件i节点的一个连接数而已,文件本身还存在】,对文件本身不需要有读、写权限【删除对文件本身没读没写】。

  • 新文件的用户ID设置为进程的有效用户ID。新文件的组ID可以是:1.进程的有效组ID。2.它所在目录的组ID。

  • access(pathname, mode)按实际用户ID,和实际组ID进行访问权限测试。只有root能chown.

  • umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);设置进程的文件模式创建屏蔽字
    creat("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |S_IROTH | S_IWOTH)
    先后运行上面两行的运行结果-rw------- bar
    chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) fchmod(fildes, mod)
    运行结果-rw-r--r-- bar

  • -rw-rwSrw- S表示设置组ID【有效用户ID和组ID】位已设置【有效用户ID变成文件所有者的ID】,同时,组执行位则未设置。

  • linux交换区==windows下虚拟内存

  • chmod a+t 目录 设置粘住位

  • 粘住位:可执行程序正文副本保存在交换区。目录设置了粘住位,只有对目录有写权限加上1.拥有文件;2.拥有目录;3.root三种之一 才可以删除或更名目录下的文件。

  • chown fchown lchown更改文件用户ID

  • truncate(pathnane, length) ftruncate(filedes, length) 截断文件或创建空洞

  • 磁盘可分为多个区,不同的区有不同的文件系统。

  • 目录项【目录/文件名】->i节点->实际数据块。多个目录项可以指向同一个i节点,例如软连接和目录本身指向的是同一块数据,就应该指向的是同一个i节点。mv只更改目录名,不移动实际数据。

  • link(existpath, newpath)创建一个新的目录项,增加链接数;unlink(pathname)删除目录项,将pathname所引用的文件链接数减1. 目录项删除后看不见文件,但文件仍有可能占据磁盘空间直至被内核删除。

  • ./a.out & 表示后台运行

  • 如果打开文件的进程数和链接数同时为0,内核会删除该文件。

  • remove(pathname)解除对一个目录或文件的链接。rename(oldname, newname)

  • 普通目录项和文件本身的关系是硬链接,她直接指向文件的i节点。符号链接是指向一个文件的间接指针,可以跨不同的文件系统。要注意函数是否跟随符号链接。

  • symlink(actualpath, sympath)创建符号链接。readlink(pathname, buf, bufsize)打开链接本身,并读链接中的名字。

  • ls -lt默认按文件修改时间排序,-u按访问时间,-c按更改状态时间。utime(pathname, times)更改文件的访问和更改时间。

  • mkdir(pathname, mode)创建一个新的空目录。rmdir(pathname)删除一个空目录。

  • DIR *opendir(pathname) struct dirent *readdir(DIR *dp) chdir(...)【当前工作目录是进程的一个属性,chdir只影响进程本身】 fchdir(...) getcwd(...)获得完整绝对路径

  • 标准I/O库【ISO C标准】文件操作围绕文件描述符,也可以理解成围绕流(stream)。当用标准io打开或创建一个文件时,我们已使一个流与文件关联。标准IO最终都要调用read,write。

  • FILE结构包含:文件描述符、缓冲区指针、缓冲区长度、当前缓冲区字节数以及出错标志。文件指针是FILE*

  • 标准输入输出的文件指针是stdin,stdout,stderr。io库提供缓冲的目的是减少使用read和write的调用次数。标准io函数通常调用malloc获得缓冲区。

  • setbuf(FILE* , buf, mode ,size)更改缓冲类型。 fflus(FILE *fp)此函数使该流所有未写的数据都被传送至内核。

  • FILE *fdopen(filedes, type)使已有的文件描述符和流关联。

  • FILE *freopen(pathname, type, FILE *restrict fp)在一个指定的流上打开文件,若流已打开,先关闭流。一般用于将指定文件打开为一个预定义的流:输入,输出,错误。

  • FILE *fopen(pathname, type【r+b,加号表示读和写】)打开一个指定的文件,b区分文本和二进制,对unix无用。 cache中用的都是open

  • int fclose(FILE *fp)关闭一个打开的流。

  • 读写结构fread fwrite

  • 一次一行fgets fputs

  • 一次一个字符getc(FILE* fp) fgetc(FILE* fp) getchar(void) getchar等价于getc(stdin)。 出错或到达文件尾,三个函数返回同样的值,为了区分需要ferror(FILE *fp) feof(FILE *fp) void clearerr(FILE *fp)

  • int ungetc(int c, FILE *fp)将字符再压送回流中。

  • 一次一个字符输出函数putc(int c, FILE) fputc(int c, FILE) putchar(int c)

  • 调用函数时间长于调用宏;一次系统调用比普通函数调用更费时间。

  • 每次一行io的函数:char fgets(buf,n,FILE)从指定流读。 char* gets(buf)从标准输入读。 输出:fputs(str, FILE) puts(*str)

  • 二进制IO:size_t fwrite(&struct[2], sizeof(struct), 4, FILE* fp) 将一个数组的第2-5号元素写在一个文件上

  • fread(..) 只能处理统一系统上的数据,异构系统通过网络互连时不行,不同系统间交换二进制数据需要使用较高级的协议。

  • 定位流:ftell(FILE) fseek(FILE, offset, whence【SEEK_SET】) rewind(FILE*) fgetpos(..) fsetpos(...) 文件描述符是用lseek

  • int fileno(FILE* fp)获得文件描述符 , FILE* tmpfile(void)创建临时文件

  • $TMPDIR=.. ./a.out 在程序名前指定环境变量

  • int printf(format,...)将格式化数据写到标准输出。 vprintf(format, va_list arg) vfprintf(...,va_list arg) vsprintf vsnprintf 可变参数表(...) 变成了arg

  • int fprintf(FILE* fp, format, ...)写至指定的流

  • int sprintf(buf, format)将格式化的字符串写入数组buf中,在数组尾端自动加null字节

  • int snprintf(buf, size_t n,format, ...) 与sprintf基本一样,n指定了缓冲区长度,超过就丢弃避免溢出

  • int scanf(format, ...) sscanf(cmdline【源】, "%s %s", opt[0], opt[1]【目的】)

  • int fscanf(FILE *fp, format, ...)

  • int sscanf(buf, format, ...) vscanf(format, va_list arg)等等

  • struct passwd getpwnam(const char name)获取口令文件项

  • struct tm结构用于保存时间的年月日的形式。

  • gettimeofday(struct timeval *res, NULL)微秒级

  • time_t time(time_t* calptr)返回当前时间,calptr不空,时间值放其中。

  • struct tm* localtime(const time_t* ptr)他和gmtime(..)的区别是他把日历时间转换为本地时区时间,gmtime转换成国际标准时间。

  • time_t mktime(struct tm* ptr)以本地时间作为参数,将其转换为time_t值【日历时间:1970至今】。

  • char asctime(struct tm)返回指向年月日字符串的指针。char ctime(time_t * prt)返回指向日历时间的指针。

  • size_t strftime(...)按格式控制时间字符串

  • timeval指定秒和微秒、timespec指定秒和纳秒

  • 正常终止一个程序:exit()默认包含关闭流操作【FILE存储区清0】 在main中exit(0)等价于return(0)

  • int atexit(void (*func)(void))登记的函数会被exit函数以相反的顺序调用。

  • 内核执行程序的唯一方法是exec。进程自愿终止的唯一方法exit【显式或隐式】。非自愿的需要给一个信号终止。

  • extern char **environ环境指针 getenv putenv 访问特定环境变量

  • a.out的符号表段、调试信息段、动态共享库链接表段不装载到进程执行的程序映像中。 size a.out 报告正文段、数据段、bss段的长度

  • cc -static hello1.c 阻止使用共享库。 cc默认使用共享库

  • void* malloc(size_t size)存储区初始值不确定

  • void* calloc(size_t nobj, size_t size)为指定数量指定长度的对象分配存储区,初始化为0

  • void* realloc(void* ptr, size_t newsize)更改以前的长度,新增部分初始值不确定,老部分保留内容。

  • void free(void* ptr)

  • goto语句不能跨越函数,setjump longjmp可以处理嵌套调用中出错情况【否则只能检查返回值逐层返回麻烦】。在希望返回的位置int setjmp(jmp_buf env【env为全局才多个函数可见】),出错位置int longjmp(env,8)。

  • "str1" "str2"等价于"str1str2"。为IO库分配缓冲区应全局静态或动态alloc分配。

  • 编译器进行优化时,它有时会取一些值的时候,直接从寄存器里进行存取,而不是从内存中获取,这种优化在单线程的程序中没有问题,但到了多线程程序中,由于多个线程是并发运行的,就有可能一个线程把某个公共的变量已经改变了,这时其余线程中寄存器【线程各自有寄存器】的值已经过时,但这个线程本身还不知道,以为没有改变,仍从寄存器里获取,就导致程序运行会出现未定义的行为。

  • 自动变量不想回滚,可以定义为volatite【易失】属性。易失属性告诉编译器不要优化,在其他地方会改变值,优化可能将这个变量一直放到寄存器中。

  • int getrlimit(int resource, struct rlimit *rlptr) setrlimit可以查询和更改资源限制。shell中的ulimit。

  • getpid getppid【获得父进程id】 getuid geteuid getgid getegid【有效组id】

  • pid_t fork(void) fork返回两次 clone,父返子,子返0.父子进程执行fork之后的代码,父子共享正文不共享数据,共享文件表和i节点。写时复制(copy-on-write)写时复制,只读时共用。

  • vfork在子进程调用exec或exit之前,他在父进程的空间中运行,调用exec或exit之后父进程才继续运行。

  • 标准IO库printf是带缓冲的。标准输出连到终端是行缓冲【打印】,否则是全缓冲。定向到文件是全缓冲

  • 信号可由进程自身产生【abort】、其他进程【kill(pidid,sig)】或内核产生。

  • 父进程提前终止的的子进程由init【init中默认有wait】进程领养。内核为每个终止的子进程保存了一定量的信息,父进程用wait或waitpid得到这些信息。

  • 一个长期运行的进程fork很多子进程,除非手动wait,否则会出现很多僵死进程。

  • pid_t wait(int* statloc) pid_t waitpid(pid_t pid【等待pid号进程】, int* statloc【指向终止状态】, int options)【成功返回进程id、0、失败返回-1】。 如果收到SIGCHLD信号调用wait,可以立刻返回,如果任意时刻调wait,可能会阻塞直到有一个子进程终止。

  • wait3 wait4比waitpid多了一个功能是最后一个参数会返回所有子进程使用资源情况的汇总。

  • 竞争条件:多进程处理共享数据,数据的结果和处理顺序有关。父等子死用wait,子等父死 【轮询】while(getppid() != 1) sleep(1);等于1是init进程id。为1说明父进程死了,被init接管了。

  • exec不创建新进程,进程ID不变。exec只是用一个全新的程序替换当前进程的正文、数据、队、栈。execl execv execle execve execlp execvp

  • 所有.c文件查找字符串abort的指令$ grep abort .//.c

  • 任何时候都可以调用int setuid(uid_t uid)做下两种操作:有效用户ID=实际用户ID;有效用户ID=保存的设置用户ID【exec复制有效用户ID得来的(一般是文件所有者?)】。设置用户ID的程序,fork后,exec之前要改回普通权限,不应使用system函数。

  • suspend $fg 作业控制

  • shell中awk实际是shell先fork->exec(awk)->wait来运行的。

  • char* getlogin(void)获取登录名。找到运行该程序的用户登录名getpwuid(getuid())

  • 网络登录telnetd进程fork,父负责网络连接的通信,子执行login,父子间通过伪终端相连接。

  • 进程组是一个或多个进程的集合。getpgid setpgid设置指定进程或调用进程的组ID。

  • 会话是一个或多个进程组的集合。进程调用pid_t setsid(void)建立一个新会话。进程是首进程、组长、断开所有控制终端。pid_t getsid(pid_t pid)返回会话首进程的进程组ID。

  • secureCRT是终端,对应前台进程组【终端(终端也是文件描述符)关联的进程】,控制进程组【shell】,后台进程组【&让其后台运行】,加在一起是会话。

  • 一个作业是几个进程的集合,通常是一个进程的管道线。vi main.c【前台启动了一个作业】 pr *.c | lpr & | make all & 【后台启动了两个作业,两个作业调用的所有进程都后台运行】

  • 终端驱动程序产生信号进而影响前台进程组的方法:中断字符ctrl+C 退出字符ctrl+\ 挂起字符 ctrl+Z。 可以用信号使后台作业暂停,fg %1 使1号作业转为前台作业

  • 进程属于进程组,进程组属于一个会话,会话可能有也可能没有控制终端。前台进程组ID是终端的属性,不是进程的属性。

  • 管道线的最后一个进程是shell的子进程,执行管道的其他命令都是最后一个进程的子进程。前端进程组ID==sessionID时说明他是后台进程。

  • 不是孤儿进程组的条件:该进程组中有一个进程,其父进程属于同一个会话的另一个组。

  • 信号SIGKILL SIGSTOP不能忽略,不能捕捉。

  • core文件复制进程终止时的存储映像。

  • kill命令和kill函数只是将一个信号送给一个进程或组,进程是否终止取决于信号的类型,以及进程是否安排了捕捉该信号。$kill -USR1 7216

  • void (signal(int signo, void (func)(int)))(int)成功返回信号以前的处理配置,失败返回SIG_ERR。不改变信号的处理方式,就不能确定信号当前的处理方式。func指定SIG_IGN或SIG_DEL表示忽略或默认处理。

  • exec使捕捉失效,捕捉函数的地址可能无意义。

  • 进程捕捉到信号执行信号处理函数func,执行完后执行发生信号时正在执行的代码【pause函数能使进程挂起直到捕捉到一个信号】。处理第一次信号后是否将信号动作复位为默认值?

  • read write部分数据时被中断算成功还是失败可以选择。

  • 在信号处理程序中调用一个不可重入函数,结果是不可预见的。

  • raise(int signo) == kill(getpid(), int signo) 进程将信号发送给其他进程需要权限。向一个不存在的进程发空信号,kill返回-1.

  • 进程ID会重新使用。

  • signal函数的语义与实现有关,最好使用sigaction函数。sigaction(signo【要检测或修改的具体动作的信号编号】, &act【非空则修改其动作】, &oact【非空则返回信号的上一个动作】)

  • sigemptyset(&act.sa_mask)【初始化由set指向的信号集清除其中所有信号】

  • 在处理一个给定的信号时,如果这种信号再次发生,通常并不将他们排队,会被阻塞到对前一个信号处理结束为止。阻塞结束后内核只传递这种信号一次。【屏蔽字为0代表没有信号阻塞,执行哪个信号的处理函数屏蔽哪个信号】

  • unix低速系统调用阻塞期间【磁盘IO一般不阻塞】如果接受到一个信号,则该低速系统调用被中断。

  • void abort(void)使异常程序终止。子进程终止会向父进程发送SIGCHLD信号。sig2str str2sig是信号编号和信号名相互转换函数。

  • 多线程程序在单处理器运行仍然能改善响应时间和吞吐量。

  • 线程ID只在它所属的进程环境中有效,因此可以不唯一。

  • pthread_t pthread_self(void)获得自身线程的ID。主线程可以用线程ID控制哪个线程处理哪些作业。新线程和主线程之间有竞争,使用主线程返回的线程id并不安全。线程ID如果很长那估计是地址。

  • 如果任一线程调用exit,_Exit,_exit整个进程就会终止。线程终止方式:1.启动函数返回。2被同进程其他线程取消【线程可以忽略取消】。3 pthread_exit

  • void pthread_exit(void* rval_ptr) int pthread_join(pthread_t thread, void** rval_ptr【保存线程的终止状态】)【阻塞直到线程返回】

  • 线程里面声明的东西别往出带。线程从线程函数返回终止,清理程序就不被调用。waitpid==pthread_join。pthread_detach使线程分离【对线程终止状态不感兴趣】,分离线程终止时资源被回收,分离的线程无法pthread_join。

  • 加锁的一种场景:对引用计数加1、减1以及检查是否为0之前都要锁住互斥量。【引用数类似文件的link】

  • 读写锁以读模式锁住是共享模式【并发读】,以写模式锁住是独占模式【独自写】。

  • 线程的虚拟地址空间是多个线程共用,如果线程多,会不够用。递归类型互斥量可以递归加锁。

  • 线程和信号都涉及函数可重入的问题。信号:捕捉函数如果向全局数据写会错。线程:多个线程同时调用同一函数。每个线程有各自的信号屏蔽字。信号处理函数进程内共享。

  • errno被重新定义为线程私有数据。键用来保护线程私有数据。

  • 包含多线程的进程fork时只有fork的线程被复制进子进程,锁的情况无法控制,如果马上exec就可以避免。

  • pread(...)使偏移量的设置和数据读取成为一个原子操作。

  • ps -a【显示其他用户所拥有的进程状态】x【没有控制终端的进程状态】j【会话ID、进程组ID。。】

  • 创建守护进程两次fork,就不是会话首进程,不会取得控制终端。fork保证子进程不是进程组组长,可以setsid。

  • lockfile(fd)对文件加锁 lockf(lockfd, F_TLOCK, 0L)可以确保只有一个守护进程运行。

  • 低速系统调用是可能会使进程永远阻塞的一类系统调用。

  • 非阻塞IO:操作无法完成,立即出错返回【不停在那,以便进行下一步处理,类似trylock试加锁】。两种方法指定描述符为非阻塞IO:open时O_NONBLOCK;fcntl(fd, cmd, &lock)打开 O_NONBLOCK标志。

  • 记录锁==字节范围锁:当一个进程读或修改文件某部分时,可以阻止其他进程修改同一文件区。有些系统中文件的最后状态取决于写该文件的最后一个进程。

  • 同一进程可对同一字节范围重复加锁,新锁换老锁。可以测试另一个进程是否对某记录加锁。

  • 锁是与进程、文件两者相关联的。fork出的子进程不继承父进程对文件的锁【避免父子同时写一个文件】。exec新程序继承原程序的锁。

  • 某些unix提供系统调用跟踪特性。

  • STREAM:构造内核设备驱动程序和网络协议包的一种通用方法。

  • IO多路转接【两个描述符】:执行阻塞read,阻塞是对整个进程阻塞,如果多个通路【一般是无限循环读、写】想要相互不影响,就得fork多个进程,每个进程处理各自的文件描述符。用非阻塞read轮询描述符消耗cpu。

  • IO多路转接思想:构造一张描述符表,调用一个函数,直到表中描述符中的一个已经准备好IO时,该函数返回,告诉进程哪些描述符可以IO。主要用于终端IO和网络IO。select(0, NULL, NULL, NULL, &tv)相当于sleep了,精确到微秒。

  • 异步IO:基本思想,告诉内核,当一个描述符已准备好可以进行IO时,用一个信号通知它【它指应用程序】。系统V异步IO调ioctl设置信号处理,只对STREAMS设备和STREAMS管道起作用。 BSD异步IO设置信号SIGIO处理程序,调fcntl设置O_ASYNC文件为异步IO。只对终端和网络描述符有效。

  • 存储映射IO:【将一个给定文件映射到一个存储区域】unsigned char* mmapBuf = (unsigned char)mmap(NULL【区域起始地址】, fileSize, PROT_READ【映射区可读】, MAP_SHARED ,fd【被映射文件】, 0【映射字节在文件中起始偏移量】) munmap((char)mmapBuf, fileSize)【解除映射】 msync冲洗到磁盘

  • IPC【InterProcess Communication】:各种管道、消息队列、信号量、共享存储、套接字、STREAMS【仅后两种支持不同主机进程间通信】

  • 管道:半双工【数据只能在一个方向上流动】;只能在公共祖先的进程间使用,通常fork后父子间使用。int pipe(int filedes[2]) fork后f[0]<-【内核自动?】-f[1] 有两份,各关闭一个

  • sh -c cmdstring表示shell将扩展字符串中的特殊字符【*.c】。

  • ${PAGE:-more}如果PAGE已定义,使用,否则用more。

  • 对管道标准IO默认全缓冲。

  • 通过FIFO【命名管道】不相关的进程也能交换数据。int mkfifo(pathname, mode_t mode)类似于创建普通文件。

  • 创建IPC结构:msgget semget shmget 要指定一个键key_t。 ftok可由一个路径名和项目ID产生一个键key_t ftok(path, id)

  • msgctl semctl shmctl修改uid、gid、mode 三种IPC都有内置限制,可通过配置内核修改。 ipcs查看 ipcrm删除

  • msgrcv可以是非先进先出

  • 信号量的值代表对应资源是否可以使用。semop(_ID, buf[]【数组中操作要么都执行,要么都不执行】, 1)表示等待信号量、释放资源、获取资源。semctl取信号量信息、设置信号量信息。

  • 使用信号量【实际上是同步原语而不是IPC】,先创建一个包含一个成员的信号量集合,信号量值赋初值1.分配资源时sem_op为-1调用semop,释放资源sem_op为1调用semop。每次设置SEM_UNDO,以处理进程终止还有未释放资源的情况。

  • shmget既可以创建,也可以引用已有的【msgget一样】。 shmctl IPC_RMID减少引用数,不真正删除。shmid和pickey不一样,shmget返回值是shmid 。shmdt脱接不删除,引用数减一。

  • 套接字用于不同计算机上的进程相互通信,其它进程运行位置透明。可以采用许多网络协议,TCP/IP常见。

  • 创建一个套接字int socket(int domain【例如ipv4internet网域】, int type, int protocol)返回套接字文件描述符。

  • int shutdown(int sockfd, int how)禁止套接字上的输入输出。uint32_t htonl(uint32_t hostint32)等进行处理器字节序和网络字节序的转换。

  • inet_pton( AF_INET, host.c_str(), &m_addr.sin_addr)将文本字符串转换成网络字节序的二进制地址

  • poll select函数能检查文件描述符的状态,用来决定是否对文件描述符执行某种操作。

  • setsockopt(_sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeo, len)

  • int fattach(int fileds, const char* path)使STREAMS管道和文件系统中的一个名字关联。

  • unix域套接字用于同一机器上进程间通信。int ssocketpair(int domain, int type, int protocol, int sockfd[2])

  • 终端IO:函数tcgetattr tcsetattr 终端IO控制函数大多tc开头

  • 很多数据库实现都采用两个文件:索引文件和数据文件。

你可能感兴趣的:(本文帮你在 Unix 下玩转 C 语言)