《 Unix环境高级编程 》笔记
本书全面介绍了UNIX系统的程序设计界面—*<系统调用界面和标准C库>*提供的许多函数。
前15章着重于理论知识的阐述,主要内容包括*
在此基础上,分别按章介绍了多个应用实例,包括如何*<创建数据库函数库,PostScript 打印机驱动程序,调制解调器拨号器及在伪终端上运行其他程序的程序>*等。
译者序(电子版无)
译者简介(电子版无)
前言(电子版无)
【第1章 UNIX基础知识】
一、操作系统为程序提供服务
二、登录名
/etc/passwd目录保存用户信息,现在都转移到其他目录中
三、Shell
/bin/sh Bourne shell
/bin/csh C shell
四、文件系统: 文件和目录的层次安排
目录是一个包含目录项(文件名+属性信息)的文件
文件名: / 和 null 不能当文件名
路径名
*ourhdr.h 标准系统头文件 常数及函数原型 见附录B(021.PDF)
.引用当前目录
..引用父目录
man手册
command(number) man手册中command第number部分 e.g. ls(1)
man 1 用户e.g. man 1 ls
man 2 程序员
man 3 系统管理
工作目录:当前工作目录
起始目录:从口令文件取得
五、文件描述符
小的非负整数,标识特定进程正在存访的文件
标准输入、标准输出和标准出错
ls > file.list 重定向
不用缓存的I/O
标准I/O
六、程序
存放在磁盘文件中的可执行文件。使用exec函数读程序到存储器中
进程:程序的执行实例。
进程ID:标识进程的非负整数
七、进程控制
fork() 创建一个新进程
exec()
waitpid()
八、
unistd.h包含许多unix系统服务的函数原型
类属指针:void *
原始系统数据类型:以_t结尾的数据类型被称为原始系统数据类型
出错处理
返回一个负值
九、
用户ID为0 具有超级权限
信号:通知进程已发生某种条件的一种技术
处理信号:忽略 按系统默认方式处理 提供函数处理
UNIX时间值 UTC
十、系统调用和库函数
系统调用:程序向内核请求服务的入口点
库函数调用可以替换,系统调用不可以替换
系统调用通常提供一种最小界面,而库函数通常提供比较复杂的功能
【第2章 UNIX标准化及实现】
UNIX标准化(界面接口) ANSI C、 IEEE POSIX、 X/Open XPG3 FIPS
UNIX实现 SVR4、 4.3+BSD
标准和实现的关系
限制 - 通过方法限制,提高程序可移植性
编译时选择项
编译时限制
运行时限制
ANSI C限制 POSIX限制 XPG3限制
sysconf()、pathconf() 和fpathconf()函数
sysconf() pathconf()其中pathconf()第一个参数为unistd.h文件路径(/usr/include/unistd.h)
FIPS 151-1要求
功能测试宏:feature test macro
基本系统数据类型:
标准之间的冲突:ansi c没有考虑宿主操作系统
【第3章 文件I/O】
1 open(),read(),write(),lseek(),close()
2 不带缓存的I/O
3 文件描述符:非负整数,内核返回到进程,用于标识文件
#include
#include
#include
4 open函数
int open(const char* pathname, int oflag, ... /*,mode_t mode*/)
O_WRONLY O_RDONLY O_RDWR
O_APPEND到文件结尾
O_CREAT重新创建
O_EXCL测试文件是否存在
O_TRUNC文件存在截断长度为0
O_NOCTTY终端设备 则不将此设备分配作为此进程的控制终端
O_NONBLOCK FIFO、块特殊文件或字符特殊文件 非阻塞方式打开
5 creat函数
int creat(const char* pathname, mode_t mode);
<=>open(pathname, O_WRONLY|O_CREAT|O_TRUNC, mode);
6 close函数
#include
close(int filedes)
7 seek函数
#include
#include
off_t lseek(int filedes, off_t offset, int whence);
失败返回-1
whence: SEEK_SET 开始 SEEK_CUR 当前 SEEK_END 结尾
8 read函数
#include
ssize_t read(int filedes, void* buff, size_t nbytes);
已到文件尾为0,失败返回-1
9 write函数
#include
ssize_t write(int filedes, const void* buff, size_t nbytes);
失败返回-1
10 进程表项 文件表 v节点表
11 原子操作(atomic operation):多步操作组成的操作,或者执行完所有步,或者一步也不执行
12 dup和dup2函数
复制一个现存的文件描述符
#include
int dup(int filedes);<=>fcnt1(filedes, F_DUPFD, 0);
int dup2(int filedes, int filedes2);<=>close(filedes2);+fcnt1(filedes, F_DUPFD, filedes2);+原子操作
成功返回新的文件描述符,失败为-1
13 fcntl函数
#include
#include
#include
int fcntl(int filedes, int cmd, .../* int arg */);
失败返回-1
改变已打开的文件的性质
cmd=F_DUPFD 复制描述符
cmd=F_GETFD或F_SETFD 获取/设置文件描述符标记
cmd=F_GETFL或F_SETFL 获取/设置文件状态标志
cmd=F_GETOWN或F_SETOWN 获取/设置异步I/O有权
cmd=F_GETLK,F_SETLK或F_SETLKW 获取/设置记录锁
通过O_ACCMODE屏蔽字,取
14 ioctl函数
I/O杂务箱,完成其他I/O函数不能完成的功能
#include
#include
int ioctl(int filedes, int request,...);
出错返回-1
【第4章 文件和目录】
零
struct stat
{
mode_t st_mode; /* file type & mode (permissions) */
ino_t stino; /* i-node number (serial number */
dev_t st_dev; /* device number (filesystem) */
nlink_t st_nlink; /* number of links */
uid_t st_uid; /* user ID of owner */
gid_t stgid; /* group ID of owner */
off_t st_size; /* size in byte, for regular files */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last file status change */
time_t st_blksize; /* best I/O block size */
long st_blocks; /* number of 512-byte blocks allocated */
};
/* linux中位于/usr/include/bits/stat.h中 */
一、stat fstat lstat函数
#include
#include
int stat(const char* pathname, struct stat* buf);
给定文件名,返回相关文件的信息结构
int fstat(int filedes, struct stat* buf);
给定文件描述符,返回相关文件的信息结构
int lstat(const char* pathname, struct stat* buf);
给定文件是连接时,返回连接文件信息
二、文件类型
普通文件(二进制,文本)
目录文件(仅仅内核有写权限)
设备文件
字符特殊文件(character special file)-用于系统中某些类型的设备
块特殊文件(block special file) - 用于磁盘设备FIFO
FIFO:命名管道,用于进程通信
套接口(socket):网络通信
符号连接(symbolic link)
文件类型宏
三、access函数
#include
int access(const char* pathname, int mode);
成功为0,出错返回-1
mode= R_OK(读) W_OK(写) X_OK(执行)
四、粘住位 S_ISVTX
五、chown fchown lchown函数
用于更改文件的用户ID和组ID
#include
#include
int chown(const char* pathname, uid_t owner, gid_t group);
int fchown(int filedes, uid_t owner, gid_t group);
int lchown(const char* pathname, uid_towner, gid_t group);
更改文件的用户ID和组ID
成功0,失败-1
六、文件截短
#include
#include
int truncate(const char* pathname, off_t length);
int ftruncate(int filedes, off_t length);
把文件截短为length,如果比原文件长,则用空洞'/0'补充
失败返回-1成功返回0
七、文件系统 ※
磁盘-分区-文件系统-自举块+超级块+i表(i节点)+目录块和数据块
i节点指向数据(文件在目录中)
目录文件指向i节点(目录文件描述)
#include
添加,原来存在则失败,失败-1成功0
int link(const char* existingpath, const char* newpath);
删除,连接数为0时才能被删除
int unlink(const char* pathname);
#include
int remove(const char* pathname);
解除文件连接 文件时,与unlink相同;目录时,与rmdir相同
int rename(const char* oldname, const char* newname); 重命名
八、符号连接
ln -s path symbo e.g. ln -s /no/such/file myfile
#include
创建连接int symlink(const char* actualpath, const char* smpath);
读连接 int readlink(const char* pathname, char* buf, int bufsize);
九、文件的时间
st_atime 文件数据的最后存取时间 st_mtime 文件数据的最后修改时间 st_ctime i节点状态的最后更改时间
#include
#include
修改文件存取和修改时间
int utime(const char* pathname, const struct utimbuf* times);
#include
#include
十、目录
创建目录和删除空目录
int mkdir(const char* pathname, mode_t mode);
int rmdir(const char* pathname);
读目录
#include
#include
DIR* opendir(const char* pathname);
struct dirent* readdir(DIR* dp);
?void rewinddir(DIR* dp);
int closedir(DIR* dp);
struct dirent
{
ino_t d_ino; //i-node number
char d_name(NAME_MAX+1); //null-terminated filename
}
/////////////////////////////////////////////
e.g.
DIR* dp;
struct dirent* dirp;
if (NULL == (dp = opendir(path))) /* maybe can't read */
while (NULL != (dirp = readdir(dp)))
{
//dirp->d_name;
}
closedir(dp);
/////////////////////////////////////////////
#include
更改当前工作目录
int chdir(const char* pathname);
int fchdir(int filedes);
得到当前工作目录
char* getcwd(char *buf, size_t size);
十一、设备号
主设备号,次设备号
用宏major()和minor()来取
/////////////////////////////////////////////
e.g.
struct stat stat;
int major, minor;
lstat(pathname, &stat);
major = major(stat.st_dev);
minor = minor(stat.st_dev);
/////////////////////////////////////////////
十二、缓存
#include
void sync(void)将修改的缓存排入队列,执行后不等I/O操作完成立即返回
int fsync(int filedes)确保修改的块直接写入磁盘,执行后等待I/O操作完成后返回
【第5章 标准I/O库】
一、
标准库中的I/O操作中流与文件相结合;FILE对象结构 用于管理流
预定义的三个流STDIN_FILENO标准输入,STDOUT_FILENO标准输出,STDERR_FILENO标准出错
stdin, stdout, stderr
//////////////////////////////////////////
e.g.
#include
int main()
{
char writebuf[] = "this is test stdin, stdout, stderr/n";
char readbuf[10] = {0};
fwrite(writebuf, sizeof(writebuf), sizeof(char), stdout);
fread(readbuf, 9, sizeof(char), stdin);
printf("readbuf = %s/n", readbuf);
return 0;
}
//////////////////////////////////////////
#inlcude
二、缓存:全缓存,行缓存,不带缓存
修改缓存
void setbuf(FILE* fp, char* buf);
int setvbuf(FILE* fp, char* buf, int mode, size_t size);
强制刷新流,使未缓存中未写入的数据写入内核
int fflush(FILE* fp);
三、打开和关闭
FILE* fopen(const char* pathname, const char* type);
FILE* freopen(const char* pathname, const char* type, FILE* fp);
FILE* fdopen(int filedes, const char* type);
int fclose(FILE* fp);
四、读写流
读一个字节
int getc(FILE *fp);是宏
int fgetc(FILE *fp);函数
int getchar(void);
成功返回字符,出错或到文件结尾返回EOF
int ferror(FILE* fp);
int feof(FILE* fp);
非0为真 0为假
输出一个字节
int putc(int c, FILE* fp);
int fputc(int c, FILE* fp);
int putchar(int c);
成功为C,出错为EOF
每次一行
char* fgets(char* buf, int n, FILE* fp);
char* gets(char* buf);※不推荐使用
char* fputs(const char* str, FILE* fp);
char* puts(const char* str);
二进制I/O
size_t fread(void* ptr, size_t size, size_t nobj, FILE* fp);
size_t fwrite(const void* ptr, size_t size, size_t nobj, FILE* fp);
成功返回操作对象数,失败-1
定位流
long ftell(FILE* fp);
成功为当前文件位置,出错返回-1L
int fseek(FILE* fp, long offset, int whence);
成功0 失败非0
void rewind(FILE* fp);
设置流到文件的起始位置
int fgetpos(FILE* fp, fpos_t* pos);
int fsetpos(FILE* fp, const fpos_t* pos);
成功为0,失败非0
格式化I/O
int printf(const char* format, ...);
输出到标准输出流
int fprintf(FILE* fp, const char* format, ...);
输出到指定流 成功返回输出字符数,出错为负值
int sprintf(char* buf, const char* format, ...);
输出到数组 返回存入数组的字符数
int vprintf(const char* format, va_list arg);
int vfprintf(const char* format, va_list arg);
int vsprintf(char* buf, const char* format, va_list arg);
int scanf(const char* format, ...);
int scanf(FILE* fp, const char* format, ...);
int sscanf(const char* buf, const char* format, ...);
int fileno(FILE* fp); 返回文件描述符
五、临时文件
char* tmpnam(char* ptr)
产生一个与现在文件名不同的一个有效路径名字符串
FILE* tmpfile(void)
建立临时文件
char* tempnam(const char* directory, const char* prefix);
directory目录 prefix前缀
六、标准I/O的替代软件
标准I/O的不足要复制两次:内核到标准I/O的缓存、标准I/O的缓存到用户程序
替代
fio(快速IO库)
sfio
ASI(Alloc Stream Interface)。
【第6章 系统数据文件和信息】
一、
/* The passwd structure. */
struct passwd
{
char *pw_name; /* Username. */
char *pw_passwd; /* Password. */
__uid_t pw_uid; /* User ID. */
__gid_t pw_gid; /* Group ID. */
char *pw_gecos; /* Real name. */
char *pw_dir; /* Home directory. */
char *pw_shell; /* Shell program. */
};
/etc/passwd文本文件
#include
#icnlude
struct passwd* getpwuid(uid_t uid);
struct passwd* getpwnam(const char* name);
查看passwd文件
struct passwd* getpwent(void);
void setpwent(void);
void endpwent(void);
影子口令/etc/shadow或者/etc/master.passwd
组文件
/etc/group
#include
#include
int getgroups(int gidsetsize, gid_t grouplist[]);
int setgroups(int* groups, const gid_tgrouplist[]);
int initgroups(const char* initername, gid_t basegid);
二、
主机/etc/hosts
网络(记录网络数据信息的数据文件)/etc/networks
协议/etc/protocols
服务(网络服务器提供的服务的数据文件)/etc/services
get读下一个记录
set打开相应数据文件
end关闭相应数据文件
三、
登录会计:登录和注销事件
/var/adm或者/var/run/utmp /var/log/wtmp
系统标识
#include
int uname(struct utsname* name)返回系统和主机信息
int gethostname(char* name, int namelen);主机名
四、
时间和日期历程
#include
time_t time(time_t* carptr)返回当前日期
struct tm* gmtime(const time_t* carptr)转化为国际标准时间
struct tm* localtime(const time_t* calptr)转化为本地时间
//转化为形如 Tue Jan 14 17:49:03 1992/n/0
char* asctime(const struct tm* calptr)
char* ctime(const time_t calptr);
size_t strftime(char* buf, siz_t maxsize, const char* format, const struct tm* tmptr);格式化时间函数
【第7章 UNIX进程的环境】
一、进程调用main()函数 进程终止的方式
内核->用户进程:“exec()”调用“c起动例程”调用“main()”调用“用户函数”...->调用“exit()”调用“_exit()”退出。
#include
void exit(int status);
#include
void _exit(int status); //直接进入内核
#include
int atexit((void* func)(void));登记函数,exit()以相反的顺序调用,
二、使用环境变量
命令行参数argv[argc] == NULL
环境表 name=value
#include
char* getenv(const char* name) //得到环境变量
未找到返回NULL
int putenv(const char* str);
取"name=value"的形式放入环境表中
int setenv(const char* name, const char* value, int rewrite);
将name设置为value rewrite=0时,删除原来的定义
三、存储器布局
高地址
命令行参数和环境变量
栈
↓
...
↑
堆
未初始化的数据-由exec赋初值0 bss
初始化的数据-又exec从程序文件中读到 data
正文 text
低地址
size exec e.g. size /bin/cc /bin/sh
四、分配另外的存储空间
#include
void* malloc(size_t size); //分配但不初始化
void* calloc(size_t nobj, size_t size); //分配后初始化为0 nobj元素个数 size元素大小
原型:extern void *calloc(int num_elems, int elem_size);
用法:#include
功能:为具有num_elems个长度为elem_size元素的数组分配内存
说明:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。
void* realloc(void* ptr, size_t newsize); //改变(增加,减少)已经分配的长度
失败返回NULL
void free(void* ptr)
跳转 goto语句
#include
int longjmp(jmp_buf env) setjmp(jmp_buf env, int val)
跳转时变量的"回滚"
查询和更改进程的资源限制
getrlimit()和setrlimit()
【第8章 进程控制】
一、进程标识——进程ID
非负整数
ID=0 交换进程(swapper)负责调度,内核的一部分,系统进程
ID=1 init进程/etc/init或/sbin/init,读与系统有关的初始化文件/etc/rc*
ID=2 精灵进程(pagedaem on),系统进程
二、fork()
#include
#include
pid_t fork(void);
返回:子进程中为0,父进程中为子进程ID,出错为-1
子进程是父进程的复制品;写时复制(Copy-On-Wirte,COW)技术
三、vfork()
#include
pid_t vfork();
没有copy-on-write,调用exec之前在父进程的空间里运行
四、进程终止
正常终止
exit()<=>return
异常终止
abort() 产生SIGABRT信号
终止后将其终止状态返回给父进程,父进程在子进程之前终止则子进程被init进程领养
僵死(zomebie)进程:一个已经终止但其父进程没有对终止的子进程做善后处理(释放子进程占有的资源)的进程。是一种临时的状态
///////////////////////////////////////
main()
{
for (;;) /*制作一个死循环*/
fork(); /*开始创建一个子进程*/
}
//////////////////////////////////////////
五、wait()函数
#include
#include
pid_t wait(int* statloc);
pid_t waitpid(pid_t pid, int* statloc, int options);
成功返回进程ID,失败-1
WIFEXITED(status)检测进程状态
pid = -1 等待任一子进程
pid > 0 等待其进程与pid相等
pid =0 等待其组ID,等于调用进程的组ID的任一子进程
pid < -1 等待其组ID进程中的绝对值等于pid的进程
options WNOHANG 不阻塞 立即返回 WUNTRACED支持组作业
#include
#include
#include
#include
pid_t wait3(int* statloc, int options, struct rusage* rusage);
pid_t wait4(pid_t pid, int* statloc, int options, struct rusage* rusage)
参考手册 getrusage(2)
六、竞争条件
当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,则我们认为这发生了竞态条件( race condition)。
定期询问(polling)
while(getppid() != 1)
sleep(1);
信号机制 和 各种形式的进程间通信(IPC)
///////////////////伪代码////////////////////////////////////////////////
TELL_WAIT(); /* set things up for TELL_XXX & WAIT_XXX */
if (0 > (pid = fork))
{
//fork error
}
else if (0 == pid)
{
//child does whatever is neccessary ...
TELL_PARENT(getpid()); //tell parent we are done
WAIT_PARENT(); //and wait for parent
//and child continues on its way ...
exit(0);
}
//parent does whatever is neccessary ...
TELL_CHILD(pid); //tell child we are done
WAIT_CHILD(); //and wait for child
//and parent continues on its way ...
exit(0);
////////////////////////////////////////////////////////////////////////
七、exec()函数
#include
int execl(const char* pathname, const char* arg0, ... /*(char*) 0*/);
int execv(const char* pathname, char* constgarv[]);
int execle(const char* pathname, const char* arg0, ... /*(char*)0, char* const envp[]*/);
int execve(const char* pathname, char* const argv[], char* const envp[]);//系统调用
int execlp(const char* filename, const char* arg0, ... /*(char*)0*/);
int execvp(const char* filename, char* const argv[]);
p表示filename做参数, l表示取一个参数表与v互斥,v表示argv[],e表示envp[]
char* const envp[](e.g. "path=/home/jack")
八、更改用户ID和组ID
#include
#include
//设置
int setuid(uid_t uid);
int setgid(gid_t gid);
//切换
int setreuid(uid_t* uid, uid_t euid);
int setregid(gid_t* gid, gid_t egid);
//更改有效id
int seteuid(uid_t uid);
int setgid(gid_t gid);
九、解释器文件
1 解释器文件是一个文本文件
2 起始行形式为!# pathname [optional-agrument]
3 本质:执行起始行的程序并把本身当作参数传递给起始行的那个程序
4 awk
////////////////////////////////
e.g. awkeg
#! awk -f
BEGIN
{
for(i = 0; i < argv; i++)
printf "ARGV[%d] = %s", i, ARGV[i]
exit
}
5 好处:隐藏某些执行文件是用脚本编写的事实;提高效率;可以使用除/bin/sh以外的其他shell来编写shell脚本
十、system函数
#include
int system(const char* cmdstring)
系统依赖性很强
e.g. system("date > file");
e.g. system("ls -a");
十一、进程结束后,内核会写一个进程的会计记录
u_short compt 在
struct acct
{
char ac_flag; /* Accounting flags. */
u_int16_t ac_uid; /* Accounting user ID. */
u_int16_t ac_gid; /* Accounting group ID. */
u_int16_t ac_tty; /* Controlling tty. */
u_int32_t ac_btime; /* Beginning time. */
comp_t ac_utime; /* Accounting user time. */
comp_t ac_stime; /* Accounting system time. */
comp_t ac_etime; /* Accounting elapsed time. */
comp_t ac_mem; /* Accounting average memory usage. */
comp_t ac_io; /* Accounting chars transferred. */
comp_t ac_rw; /* Accounting blocks read or written. */
comp_t ac_minflt; /* Accounting minor pagefaults. */
comp_t ac_majflt; /* Accounting major pagefaults. */
comp_t ac_swaps; /* Accounting number of swaps. */
u_int32_t ac_exitcode; /* Accounting process exitcode. */
char ac_comm[ACCT_COMM+1]; /* Accounting command name. */
char ac_pad[10]; /* Accounting padding bytes. */
};
accton命令
/sbin/accton filename turn on
TO DO STH
/sbin/accton turn off
十二、用户标识
#include
char* getlogin(void); //得到用户名
十三、进程时间
#include
/* Structure describing CPU time used by a process and its children. */
struct tms
{
clock_t tms_utime; /* User CPU time. */
clock_t tms_stime; /* System CPU time. */
clock_t tms_cutime; /* User CPU time of dead children. */
clock_t tms_cstime; /* System CPU time of dead children. */
};
clock_t times(struct tms* buf)
【第9章 进程关系】
一、登录
1 终端登录
登入 内核--->init[进程ID=1]--->(fork)init--->(exec)getty--->(exec)->login
登出 进程1=init->登录sell<->终端设备驱动程序<->使用终端的用户
2 网络登录
login 进程1=init->inetd(来自telnet客户的TCP连接请求)->(fork)inetd->(exec)telnetd
logout 进程1=init->登录sell<->伪终端设备驱动程序->使用终端的用户
二、进程组
#include
#include
int setpgid(pid_t pid, pid_t pgid);
进程组id等于进程id,此进程为进程组组长
三、对话期(session)
多个进程组够成对话期
pid_t setid(void);
对话期—控制终端
四、作业控制
0 一个作业是几个进程的集合
1 控制哪一个作业可以使用终端,哪些作业在后台运行。
2 只有一个作业可以在前台运行使用终端,后台可以运行多个作业。
3 作业标识
4 命令在后台运行#command &
5 作业状态 [stop] [done]
6 stty nostop 后台作业输出不输出到终端 stty -nostop 去除
7 fd %1 作业切换到前台 bg %1作业切换到后台
五、孤儿进程组
shell 执行程序(图)
孤儿进程:一个父进程已终止的进程
init管理孤儿进程组
【第10章 信号】
一、信号是软件中断,SIGxxx被定义为正整数
SIGABRT 异常终止
SIGALRM 超时
SIGBUS 硬件故障
SIGCHLD 子进程状态改变
SIGCONT 使暂停进程继续
SIGEMT 硬件故障
SIGFPE 算术异常
SIGHUP 连接断开
SIGILL 非法硬件指令
SIGINFO 键盘状态请求
SIGINT 终端中断符
SIGIO 异步I / O
SIGIOT 硬件故障
SIGKILL 终止
SIGPIPE 写至无读进程的管道
SIGPOLL 可轮询事件(poll)
SIGPROF 梗概时间超时(setitimer)
SIGPWR 电源失效/再起动
SIGQUIT 终端退出符
SIGSEGV 无效存储访问
SIGSTOP 停止作业
SIGSYS 无效系统调用
SIGTERM 终止
SIGTRAP 硬件故障
SIGTSTP 终端挂起符作业
SIGTTIN 后台从控制tty读作业
SIGTTOU 后台向控制tty写作业
SIGURG 紧急情况
SIGUSR1 用户定义信号
SIGUSR2 用户定义信号
SIGVTALRM 虚拟时间闹钟(setitimer)
SIGWINCH 终端窗口大小
SIGXCPU 超过CPU限制(setrlimit)
SIGXFSZ 超过文件长度限制(setrlimit)
处理方式:忽略,捕捉,默认
二、signal()函数
#include
void (*signal (int signo, void (func)(int)))(int);
typedef void SIGFUN(int)
SIGFUN* signal(int, SIGFUN)
信号不稳定,容易丢失
三、中断调用
四、可再入函数
可再入函数:已知使用静态数据结构;调用malloc()或free();标准I/O函数
信号处理程序重复调用不可再入函数,可能会破坏数据结构
五、发送信号函数
#include
#include
int kill(pid_t pid, int signo);
int raise(int signo); //向自身发信号
六、时钟和暂停
#include
unsigned int alarm(unsigned int seconds); //seconds秒后产生SIGALRM信号
int pause(void); //挂起知道捕捉到一个信号
利用alarm()和pause()实现sleep()和带有时间限制调用read()
七、信号集
信号集:表示多个信号的数据结构
#include
int sigemptyset(sigset_t* set); //初始化信号集
int sigfillset(sigset_t* set); //填充所有信号
int sigaddset(sigset_t* set, int signo); //加入信号
int sigdelset(sigset_t* set, int signo); //删除信号
int sigismember(const sig_t* set, int signo); //信号集中是否是信号集的成员
int sigprocmask(int how, const sigset_t* set, sigset_t* oset); //检测或更改信号屏蔽字
int sigpending(sigset_t* set); //返回被阻塞和未决的信号集
sigpending()原子操作,使用set信号集,然后pause();返回后恢复原来的信号集
int sigaction(int signo, const struct sigaction* act, struct sigaction* act); //检查修改与指定信号相关连的处理动作
sigsetjmp() siglongjmp()
int sigsuspend(const sigset_t* mask); //恢复信号屏蔽字
八、函数实现
#include
异常退出
void abort(void); //使进程异常退出 用信号+kill()函数实现
系统函数
system() 信号+exec()函数实现
unsigned int sleep(unsigned int seconds); //可靠实现
九、作业控制信号
【第11章 终端I/O】
一、处理方式
1 规范化处理:以行为单位进行处理
2 非规范方式处理:没有单位
二、检测和更改中断设备特性
struct termios
终端设备函数
tcgetattr() tcsetattr() cfgetispeed() cfgetospeed() cfsetispeed() cfsetospeed() tcdrain() tcflow() tcflush() tcsendbreak() tcgetpgrp() tcsetpgrp()
三、特殊处理字符
四、获得和设置终端属性
#include
int tcgetattr(int filedes, struct termios* termptr);
int tcsetattr(int filedes, int opt, struct termios* termptr);
五、波特率(位/秒)
speed_t cfgetispeed(const struct termios* termptr);
speed_t cfgetospeed(const struct termios* termptr);
int cfsetispeed(struct termiot* esrmptr, speed_t speed);
int cfsetospeed(struct termiot* esrmptr, speed_t speed);
六、行控制函数
int tcdrain(int filedes);
int tcflow(int filedes, int action);
七、终端标识
#include
char* ctermid(char* ptr); //设备被映射成文件,通过copy文件名得到终端名 提高向其他操作系统的可移植性
#include
int isatty(int filedes); //tcgetattr()函数实现
char* ttyname(int filedes);
八、方式
规范方式
getpass()实现
非规范方式
九、终端的窗口大小
struct winsize
十、终端性能
termcap,terminfo curses
【第12章 高级I/O】
一、非阻塞I/O
void set_fl()
{
int val;
if ( (val = fcntl(fd, F_GETFL, 0)) < 0)
{
perror("fcntl F_GETFL error");
}
val |= flags; //turn on flags
if (fcntl(fd, F_SETFL, val) < 0)
{
perror("fcntl F_SETFL error");
}
clr_fl()
{
//同上
val &= ~flags;
//.同上
}
O_NONBLOCK
二、记录锁
int fcntl(int filedes, int cmd, .../*struct flock* flockptr*/);
struct flock /* /usr/include/bits/fctnl.h */
{
short int l_type; /* Type of lock: F_RDLCK, F_WRLCK, or F_UNLCK. */
short int l_whence; /* Where `l_start' is relative to (like `lseek'). */
__off_t l_start; /* Offset where the lock begins. */
__off_t l_len; /* Size of the locked area; zero means until EOF. */
__pid_t l_pid; /* Process holding the lock. */
};
#include
#include
struct flock lock
fcntl(fd, cmd, &lock)
cmd = F_GETLK F_SETLK F_SETLKW
三、锁死 ——相互等待对方持有并不释放的资源,造成锁死
锁与进程(进程终止锁释放)、文件(文件关闭锁释放)
由fork产生的子进程不继承父进程的锁,调用exec后,新进程可以继承执行程序的锁
四、流
流是系统V提供的构造内核设备驱动程序和网络协议包的一种通用方法。
输入输出基于消息
#include
int putmsg(int filedes,const struct strbu*fctlptr,const struct strbufdataptr,int flag);
int putmsg(int filedes,const struct strbu*fctlptr,const struct strbufdataptr,int band,int flag);
流操作int ioctl()函数 stream io(7)手册
五、I/O多路转接(I/O multiplexing)
先构造一张有关描述符的表,然后调用一个函数,他要到这些描述符中的一个准备好进行I/O时返回。在返回时,它告诉进程哪一个描述符已经准备好可以进行I/O。
第13章 精灵进程
精灵进程(daemon)是生存期长的一种进程。系统引导装入时启动,在系统关闭时终止。没有终端
?精灵进程是否是守护进程
一、ps精灵进程
ps -axj ps -efjc
PPID PID PGID SID TT TPGID UID COMMAND
1 108 108 108 ? -1 0 cron
sendmail是标准邮递精灵进程
update程序定期将内核缓存中的内容写到硬盘上(每隔30秒,即30秒调用sync(2)一次)
corn在指定的日期和时间执行指定的命令
inetd监听系统的网络界面
lpd处理对系统提出的各个打印请求
二、精灵进程的特征
以超级用户id=0的优先权运行
没有控制终端
进程组的的首进程 对话期的首进程(update例外)
组和对话期的唯一进程
三、编写规则
1 调用fork()然后终止exit
2 调用setsid以创建一个新对话期 使进程:成为新对话期的首进程、成为一个新进程组的首进程、没有控制终端
3 工作目录更改为根目录
4 创建屏蔽字设置为0
5 关闭不再需要的文件描述符
/**********************************************************/
#include
#include
#include
#include "ourhdr.h"
int daemon_init(void)
{
pid_t pid;
if ((pid = fork()) <0)
return(-1);
else if (pid != 0)
exit(0); //parent goes bye-bye*/
//child continues
setsid() //became session leader
chdir("/"); //change working directory
umask(0); //clear our file mode creation mask
return(0);
}
int main(int argc, char argv[])
{
daemon_init();
sleep();//睡眠状态
}
$a.out
$ps -axj
PPID PID PGID SID TT TPGID UID COMMAND
1 735 735 735 ? -1 224 a.out
?四、出错记录
1 SVR4 log设施
/var/adm/streams/error.mm-dd
2 4.3+BSD syslog设施 void openlog() void syslog() void closelog(void)
五、客户机-服务器模型
【第14章 进程间通信】
IPC类型7种
一、管道(半双工)
1 特点:半双工(一个方向上流动)在具有共同祖先的进程之间使用
#include
int pipe(int filedes[2]);//filedes[0]为读而打开->filedes为写而打开
2 PIPE_BUF管道缓存大小
3 实现
#include "ourhdr.h"
int main(void)
{
int n, fd[2];
pid_t pid;
char line[MAXLINE];
if (pipe(fd) < 0)
err_sys("pipe error");
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid > 0) //parent
{
close(fd[0]);
write(fd[1], "hello world/n", 12);
}
else //child
{
close(fd[1]);
n = read(fd[0], line, MAXLINE);
write(STDOUT_FILENO, line, n);
}
if ((pid = fork())<0)
err_sys("fork error");
else if(pid > 0) //parent
{
close(fd[0]);
write(fd[1], "hello word/n", 12);
}
else //child
{
close(fd[1]);
n = read(fd[0], line, MAXLINE);
write(STDOUT_FILENO, line, n);
}
exit(0);
}
4 应用
(1)向分页程序中传送文件程序
(2)父进程与子进程的同步
(3)过滤程序
(4)popen() pclose()的实现
(5)协同进程(同一个横须产生某个过滤程序的输入,同时又读取该过滤程序的输出时,这个过滤程序就叫做协同进程)
5
FILE* popen(const char* cmdstring, const char type);
int pclose(FILE* p);
popen():先执行fork(),然后调用exec以执行cmdstring,并返回一个标准I/O文件的指针.
popen(const char* cmdstring "r")
如果type="r",则文件指针连接到cmdstring的标准输出
popen(const char* cmdstring "w")
如果type="w",则文件指针连接到cmdstring的标准输入
二、FIFO(命名管道)
#include
#include
int mkfifo(const char* pathname, mode_t mode);
(1) FIFO由shell命令使用以便将数据从一条管道线传送到另一条,为此无需创建中间临时文件。
e.g. 输出文件(infile)->prog1->prog3
->prog2
输出文件(infile)->prog1->tee->FIFO->prog3
->prog2
mkfifo fifo1
prog3 < fifo1 &
prog1 < infile | tee fifo1 | prog2
(2)客户机-服务器应用程序中 客户机和服务器之间传递数据
三、系统V的IPC(消息队列+信号量+共享内存器)
1 IPC结构
(1)每个内核中的IPC结构都用一个非负整数的标识符(identifier)加以引用。
(2)无论何时创IPC结构都应指定一个关键字(key),数据类型为key_t,在
(3)关键字(key)被规定为long,关键字由内核变换成标识符。
2 服务器与客户机IPC结构的会合
(1)服务器内存中建立IPC结构,建立一个关键字IPC_PRVATE的文件。客户机取文件
(2)在一个公用头文件中定义一个客户机和服务器都认可的关键字
(3)客户机和服务器认同一个路径名和课题ID,然后调用ftok()将这两个值变为关键字,然后用方法(2)来使用
3 创建新的IPC结构
key指定为IPC_PRIVATE
key当前未与特定类型的IPC结构相结合,flag中指定了IPC_ CREAT位。
4 许可权结构
struct ipc_perm
{
__key_t __key; /* Key. */
__uid_t uid; /* Owner's user ID. */
__gid_t gid; /* Owner's group ID. */
__uid_t cuid; /* Creator's user ID. */
__gid_t cgid; /* Creator's group ID. */
unsigned short int mode; /* Read/write permission. */
unsigned short int __pad1;
unsigned short int __seq; /* Sequence number. */
unsigned short int __pad2;
unsigned long int __unused1;
unsigned long int __unused2;
};
/* linux中位于/usr/include/bits/ipc_perm.h中 */
限制值在ipc_perm /etc/conf/cf.d/mtune中
5 特点
IPC结构是在系统范围内起作用的,没有访问计数。系统维护删除消息队列
IPC结构并不按名字为文件系统所知,不使用文件描述符。不得不引进新函数和命令进行维护 不能使用多路轮转I/0函数
优点:可靠,受控制,面向记录,可以用非先入先出方式处理
四、消息队列
消息队列是消息的链接表,存放在内河中并由消息队列标识符号标识。队列ID
/* Structure of record for one message inside the kernel.
The type `struct msg' is opaque. */
struct msqid_ds
{
struct ipc_perm msg_perm; /* structure describing operation permission */
__time_t msg_stime; /* time of last msgsnd command */
unsigned long int __unused1;
__time_t msg_rtime; /* time of last msgrcv command */
unsigned long int __unused2;
__time_t msg_ctime; /* time of last change */
unsigned long int __unused3;
unsigned long int __msg_cbytes; /* current number of bytes on queue */
msgqnum_t msg_qnum; /* number of messages currently on queue */
msglen_t msg_qbytes; /* max number of bytes allowed on queue */
__pid_t msg_lspid; /* pid of last msgsnd() */
__pid_t msg_lrpid; /* pid of last msgrcv() */
unsigned long int __unused4;
unsigned long int __unused5;
};
/* linux中位于/usr/include/bits/msq.h中 */
#include
#include
#include
int msgget(key_t key, int size, int flag); //创建或打开消息队列
#include
int msgctl(int msqid, int cmd, struct msqid_ds* buf);
IPC_STAT 读msqid_ds
IPC_SET 写msqid_ds
IPC_RMID 清空消息
#include
#include
#include
int msgsnd(int msqid, const void* ptr, siz_t nbytes, int flag); //发送
int msgrcv(int msqid, void* ptr, size_t nbytes, long type, int flag); //取得
五、信号量
1 是一个计数器,用于多进程对共享数据对象的存取
2 过程
(1)测试控制该资源的信号量
(2)若此信号量的值为正,则进程可以使用该资源。信号量减1
(3)若信号量为0,则进程睡眠,直到信号量大于0。进程被唤醒后,返回至(1)
加减操作必须是原子操作在内核中进行
3 造成复杂性的原因:
(1)信号量并非是一个非负值,而被定义为含有一个或多个信号量值的集合
当创建一个信号量时,要指定该集合中的各个值
(2)创建信息量(semget)与对其赋初值(semctl)分开 不能原子地创建一个信号量集合
(3)即使没有进程正在使用,它们仍然是存在的
struct semid_ds内核心为信号量设置的结构
#include
#include
#include
int semget(key_t key, int nsems, int flag); //获得信号量
int semctl(int semid int semnum, int cmd, uion semunarg); //操作
int semop(int semid, struct sembuf semoparray[], siz_t nops); //自动执行信号量集合上的数组操作
如果多个进程共享一个资源,则可使用信号量或记录锁。记录锁操作慢于信号量操作,但更简易,进程终止时,系统自动处理遗留的锁
六、共享内存器
struct shmid_ds
#include
#include
#include
int shmget(key_t key, int size, int flag); //获得一个共享存储标识符
int shmctl(int shmid int cmd, struct shmid_ds* buf); //操作
int shmdt(void* addr); //连接共享存储段到它的地址空间
实例 打印不同类型的数据所存放的位置
/****************************************************************/
#include
#include
#include
#include "ourhdr.h"
#define ARRAY_SIZE 40000
#define MALLOC_SIZE 100000
#define SHM_SIZE 100000
#define SHM_MODE (SHM_R | SHW_W) //user read/write
char array[ARRAY_SIZE];
int main(void)
{
int shmid;
char *ptr, *shmptr;
printf("array[] from %x to %x/n", &array[0], &array[ARRAY_SIZE]);
printf("stack around %x/n", &shmid);
if ((ptr = malloc(MALLOC_SIZE)) == NULL)
err_sys("malloc error");
printf("malloced from %x to %x/n", ptr, prt+MALLOC_SIZE);
if ((shmid = shmget(IPC_PRIVATE, SHM_SIZE, SHM_MODE)) < 0)
err_sys("shmget error");
if ((shmptr = shmat(shmid, 0, 0)) == (void*)-1)
err_sys("shmat error");
printf("shared memory attached from %x to %x/n", shmptr, shmptr+SHM_SIZE);
if(shmctl(shmid, IPC_RMID, 0) < 0)
err_sys("shmctl error);
exit(0);
}
/***************************************************************/
在父、子进程间使用/dev/zero存储映射I/O的IPC
/dev/zero 0的无限资源
匿名存储映射 mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0);
七、客户机-服务器属性
【第15章 高级进程间通信】
一、流管道
全双工的。
1 SVR4版
int s_pipe(int fd[2])
{
return (pipe(fd));
}
2 BSD版
#include
#include
int s_pipe(int fd[2])
{
return (socketpair(AF_UNIX, SOCKSTEAM, 0, fd));
}
二、传送文件描述符
1 协议
2 函数
int send_fd(int spipefd, int filedes) ;
int send_err(int spipefd, int status, const char* errmsg) ;/* 两个函数返回:若成功则为0,若出错则为-1 */
int recv_fd(int spipefd, ssize_t (*userfunc)(int, const void *, size_t)); /* 返回:若成功则为文件描述符,若出错则<0 */
3 实现
(1)SVR4版
(2)BSD版
三、open服务器
第一版
第二版
【第16章 数据库函数库】
一、历史
1 V7提供dbm(3)
2 4.3+BSD提供db(3)
//Linux man 3 db
二、函数库
#include "db.h"
DB *db_open(const chapr a t h* n a m e, int o f l a g, int m o d e); //打开数据库 /* 返回:若成功则为DB结构的指针,若失败则为NULL */
void db_close(DB* db); //关闭数据库
int db_store(DB *db, const char* key, const char* data, int flag); //插入、替换记录 /* 返回:若成功则为0,若错误则为非0(见下) */
flag DB_INSERT 加一条新记录,记录存在则返回1
DB_REPLACE 替换一条已有的记录 记录不存在则返回-1
char *db_fetch(DB* db, const char* key); //取记录 /* 返回:若成功则为指向数据的指针,若记录没有找到则为NULL */
int db_delete(DB* db, const char* key); //删除记录 /* 返回:若成功则为0,若记录没有找到则为- 1 */
void db_rewind(DB* db); //回到数据库第一条记录
char *db_nextrec(DB* db, char* key); //顺序的读每一条记录 /* 返回:若成功则返回指向数据的指针,若到达数据库的尾端则为NULL */
三、设计
1 算法?
2 集中和非集中
(1) 集中式由一个进程作为数据库管理者,所有的数据库访问工作由此进程完成。库函数通过IPC机制与此中心进程进行联系。
(2) 非集中式每个库函数独立申请并发控制(加锁),然后调用它自己的I/O函数。
3 并发
(1)粗锁:最简单的加锁方法是将这两个文件中的一个作为锁,并要求调用者在对数据库进行操作前必须获得这个锁。我们将这称为粗锁(coarse locking)。限制了最大程度的并发。
(2)细锁:在操作一条记录前必须先获得此记录所在散列链的读锁或写锁。
四、源码?
五、性能?
?informax数据库
?Berkeley DB数据库
?mysql数据库
?第17章 与PostScript 打印机通信
?第18章 调制解调器拨号器
?第19章 伪终端
转自:http://hi.baidu.com/aquei/blog/item/15149c22c53105f7d7cae2ee.html