UNIX环境高级编程

第1章 UNIX基础知识
  • 从严格意义上说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境,通常将这种软件称为内核(kernel)。内核的接口被称为系统调用(system call),公用函数库构建在系统调用接口之上,应用程序即可使用公用函数库也可使用系统调用。
  • 当创建一个新目录时,自动创建了两个文件名:.(称为点)和..(称为点点)。点引用当前目录,点点则引用父目录。在最高层次的根目录中,点点与点相同。
  • 文件描述符(file descriptor)是一个小的非负整数,内核用以标识一个特定进程正在存访的文件。当内核打开一个现存文件或创建一个新文件时,它就返回一个文件描述符。当读、写文件时,就可使用它。
  • 每当运行一个新程序时,所有的shell都为其打开三个文件描述符:标准输入(standard input)、标准输出(standard output)以及标准错误(standard error)。
  • 函数open、read、write、lseek以及close提供了不带缓冲的I/O,这些函数都使用文件描述符。
程序和进程

程序(program)是一个存储在磁盘上某个目录中的可执行文件。内核使用exec函数(7个exec函数之一),将程序读入内存,并执行程序。

程序的执行实例被称为进程(process),某些操作系统用任务(task)表示正在被执行的程序。
UNIX系统确保每个进程都有一个唯一的数字标识符,称为进程ID(process ID)。进程ID总是一非负整数。

有三个用于进程控制的主要函数: forkexecwaitpid, exec函数有7种变体,但经常把它们统称为exec函数
与进程相同,线程也用ID标识。但是线程ID只在她所属的进程内起作用。一个进程中的线程ID在另一个进程中没有意义。

第3章 文件 I/O
3.1 引言

  文件I/O函数:打开文件,读文件,写文件.
  常用到五个函数:open, read, write, lseek, close.
  本章描述的函数都是:不带缓冲的I/O(unbuffered I/O),属于不带缓冲 是指每个readwrite都是调用内核中一个系统调用。

3.2 文件描述符

  对于内核而言,所有打开的文件都是通过文件描述符引用的.文件描述符是一个非负整数。当打开
  一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。
  当读或写一个文件的时候,使用opencreat返回的文件描述符标示该文件,将其参数传给readwrite.
  通常文件描述符0与标准输入关联,1与标准输出关联,2与标准错误关联,替换成STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO以提高可读性。

3.3 函数open和openat

  调用open函数可以打开或创建一个文件。

#include<fcntl.h>
int open(const char *path, int oflag, .../*mode_t mode*/);
int openat(int fd, const char *path, int oflag, .../*mode_t mode*/)
//若成功,返回文件描述符,若出错,返回-1

   path参数是要打开或创建文件的名字。
   oflag参数:

数字常量含义
0O_RDONLY只读打开
1O_WRONLY只写打开
2O_RDWR读写打开
\O_EXEC只执行打开
\O_SEARCH只搜索打开

  由openopenat函数返回的文件描述符一定是最小未用的文件描述符数值。

  fd参数把openopenat函数区分开:
  1)path参数指绝对路径,fd被忽略,此时openat函数相当于open函数。
  2)path指相对路径,fd参数指出了相对路径名在文件系统中的起始地址.fd参数是通过打开相对路径文件名所在的文件目录获取。
  3)path参数指定了相对路径名,fd参数具有特殊值AT_FDCWD,这种情况下,路径名在当前工作目录中获取,openat在操作上与open类似。

3.4 函数create
#include <fcntl.h>
int creat(const char *path, mode_t mode);
//若成功,返回为只写打开的文件描述符,若出错返回-1

  注意,此函数等效于:

open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);

  在早期的UNIX系统版本中, open的第二个参数只能是012。没有办法打开一个尚未存在的文件,因此需要另一个系统调用create以创建新文件。现在, open函数提供了选择项O_CREATO_TRUNC,于是也就不再需要creat函数了。

  creat的一个不足之处是它只能以只写的方式创建文件。在提供open的新版本之前,如果想创建一个临时文件,然后再读这个临时文件,必须先调用creat,close然后再调用open,现在可以直接调用open实现:

open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
3.5 函数close

  可以调用close函数关闭文件

#include<unistd.h>
int close(int fd);//成功返回0,出错返回-1

  关闭一个文件时还会释放该进程加在该文件上的所有记录锁。
  当一个进程终止时,内核自动关闭它所有的打开文件,很多程序都利用了这一功能而不显式地用close关闭打开文件。

3.6 函数lseek

  每个打开文件都有一个与其相关联的“当前文件偏移量”(current file offset)。它是一个非负整数,用以度量从文件开始处计算的字节数。通常,读、写操作都从当前文件位移量处开始,并使位移量增加所读或写的字节数。按系统默认,当打开一个文件时,除非指定O_APPEND选择项,否则该位移量被设置为0。

  可以调用lseek显式地为一个打开文件设置文件偏移量:

#include<unistd.h>
off_t lseek(int fd, off_t offset, int whence);
//若成功,返回新的文件偏移量

对参数offset的解释与参数whence的值有关。

  • whenceSEEK_SET,将文件的偏移量设置为距文件开始处offset
  • whenceSEEK_CUR,将文件的偏移量设置为当前值加offset
  • whenceSEEK_END,将文件的偏移量设置为文件长度加上offset, offset可为正可为负

  可以用下列方式确定打开文件的当前偏移量。

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

  lseek仅将当前的文件偏移量记录在内核中,它并不引起任何的I/O操作.偏移量用于下一个读写操作。

3.7 函数read
#include <unistd.h>
ssize_t read(int fd, void *buf,size_t nbytes);
//返回:读到的字节数,若已到文件尾返回0,若出错,返回-1

  若read成功。返回读到的字节数。如果已到达文件尾端,返回0.
ssize_t:带符号的返回值。

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

  若成功,返回已写的字节数;若出错,返回-1,其返回值通常与参数nbytes相同,否则表示出错。

3.9 I/O的效率

  大多数文件系统为改善性能都采用了某种预读技术(read already)。Linux ext4文件系统,其磁盘块长度4096字节,4096之后继续增加缓冲区长度对时间几乎没有影响。

3.10 文件共享

  UNIX系统支持在不同的进程间共享打开相同的文件。每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述符表。内核为所有打开文件维持一张文件表:文件状态标志、偏移、指向v节点表项指针。每个打开文件(或设备)都有一个v节点结构,包含了文件类型和对此文件进行各种操作函数的指针。

3.12 函数dup和dup2
int dup(int fd);
int dup2(int fd, int fd2);
//成功返回新的文件描述符,错误返回-1

调用 dup(fd);等效于fcntl(fd, F_DUPFD, 0);
调用dup2(fd, fd2);等效于close(fd2); fcntl(fd, F_DUPFD, fd2);

3.13 函数sync和fsync

传统的UNIX系统实现在内核中设有缓冲区高速缓存或页高速缓存,大多数磁盘I/O都通过缓冲区进行。当我们向文件写入数据时,内核通常先将数据复制到缓冲区,排入队列,晚些时候再写入磁盘。这种方式被称为延迟写(delay write).

int fsync(int fd);
void sync(void);
//成功返回0,错误返回-1

sync只是将所有修改过的块缓冲区排入写队列,然后就返回,并不等待实际写磁盘操作结束。update的系统守护进程周期性地调用sync函数(一般30秒)。保证了定期冲洗flush内核的块缓冲区。fsync函数等待写磁盘操作结束才返回。

第4章 文件和目录
4.2 函数 stat、fstat、fstatat 和 lstat

  本章主要讨论4个stat函数以及他们的返回信息,使用stat函数最多的地方可能就是ls -l命令,用其可以获得相关文件的所有信息。

#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);
//All four return: 0 if OK, −1 on error

一旦给出pathnamestat函数将返回与此命名文件有关的信息结构。
第二个参数buf是一个指针,它指向一个我们必须提供的结构stat,基本形式如下:

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 */
   gid_t st_gid;           /* group ID of owner */
   off_t st_size;          /* size in bytes, for regular files */
   truct 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 */
};
4.3 文件类型

1)普通文件(regular file)
2)目录文件(directory file).包含了其他文件的名字以及指向与这些文件有关信息的指针。对一个目录文件具有读权限的任一进程都可以读该目录的内容,但只有内核可以直接写目录文件。
3)块特殊文件(block special file)
4)字符特殊文件(character special file).系统中的所有设备要么是字符特殊文件,要么是块特殊文件。
5)FIFO.用于进程间通信,有时也称为命名管道(named pipe).
6)套接字(socket).用于进程间网络通信。
7)符号链接(symbolic link).指向另一个文件。

可用宏确定文件类型:

MacroType of file
S_ISREG()regular file
S_ISDIR()directory file
S_ISCHR()character special file
S_ISBLK()block special file
S_ISFIFO()pipe or FIFO
S_ISLNK()symbolic link
S_ISSOCK()socket
S_TYPEISMQ()消息队列
S_TYPEISSEM()信号量
S_TYPEISSHM()共享存储对象

实例4-3:

int main(int argc,char*argv[]){
    int i;
    struct stat buf;
    char *ptr;
    for(i=1;i<argc;i++){
        printf("%s:",argv[i]);
        if (lstat(argv[i],&buf)<0){
            err_ret("lstat error");
            continue;
        }
        if (S_ISREG(buf.st_mode))
            ptr = "regular";
        else if (S_ISDIR(buf.st_mode))
            ptr = "directory";
        else if (S_ISCHR(buf.st_mode))
            ptr = "character special";
        else if (S_ISBLK(buf.st_mode))
            ptr = "block special";
        else if (S_ISFIFO(buf.st_mode))
            ptr = "fifo";
        else if (S_ISLNK(buf.st_mode))
            ptr = "symbolic link";
        else if (S_ISSOCK(buf.st_mode))
            ptr = "socket";
        else
            ptr = "** unknown mode **";
        printf("%s\n", ptr);
    }
    exit(0);
}

编译运行:

$ ./a.out   /etc/passwd   /etc  /dev/log  /dev/tty
> /var/lib/oprofile/opd_pipe   /dev/sr0   /dev/cdrom
/etc/passwd: regular
/etc: directory
/dev/log: socket
/dev/tty: character special
/var/lib/oprofile/opd_pipe: fifo
/dev/sr0: block special
/dev/cdrom: symbolic link

4.5 文件访问权限

st_mode值也包含了对文件的访问权限位。所有文件类型(目录、字符特别文件等)都有访问权限(access permission).
每个文件有9个访问权限,如下:

st_mode maskMeaning
S_IRUSRuser-read
S_IWUSRuser-write
S_IXUSRuser-execute
S_IRGRPgroup-read
S_IWGRPgroup-write
S_IXGRPgroup-execute
S_IROTHother-read
S_IWOTHother-write
S_IXOTHother-execute
  • 打开任一类型的文件时,对该名字包含的每一个目录具有执行权限。例如;为了打开文件/usr/include/stdio.h ,需要对目录 //usr/usr/include 具有执行权限。对于目录的读权限和执行权限的意义是不相同的。读权限允许我们读目录,获得在该目录中所有文件名的列表。
  • 删除一个现有文件,必须对该文件的目录具有写权限和执行权限,对该文件本身不需要读写权限。

进程每次打开、创建或删除一个文件时,内核就进行文件访问权限测试。

第5章 标准 I/O
5.1 引言

本章讲述标准I/O库,标准I/O库是由Dennis Ritchie在1975年左右编写的,令人惊讶的是,45年来,几乎没有对标准I/O库进行修改。

5.2 流和FILE对象

当用标准I/O库打开或创建一个文件时,我们已使一个流与一个文件相关联。
只有两个函数可改变流的定向。freopen函数清除一个流的定向,fwide函数可用于设置流的定向。

5.4 缓冲

标准I/O库提供缓冲的目的是尽可能减少使用read和write调用的次数。
标准I/O提供了3种类型的缓冲。
1)全缓冲。填满标准I/O缓冲区后才进行实际I/O操作。
2)行缓冲。当在输入和输出中遇到换行符时,标准I/O库执行I/O操作。终端通常使用行缓冲。
3)不带缓冲。标准错误流stderr通常是不带缓冲的,这样错误信息就可以尽快显现出来。

#include <stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict buf, int mode,size_t size);
/* 成功返回0;出错返回非0 */

可以使用setbuf函数打开或关闭缓冲机制,参数buf必须指向一个长度为BUFSIZE的缓冲区(定义在<stdio.h>),通常此后该流就是全缓冲的。关闭缓冲,将buf设置为NULL.
使用setvbuf,可以精确地说明所需的缓冲类型,用mode参数实现。
任何时候我们都可以强制冲洗一个流

#include <stdio.h>
int fflush(FILE *fp);
/** 成功返回0;出错返回EOF **/
5.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 **/

fdopen函数取一个已有的文件描述符,并使一个标准的I/O流与该描述符相结合。常用于由创建管道和网络通信通道函数返回的描述符。
调用fclose关闭一个打开的流。

#include <stdio.h>
int fclose(FILE *fp);
5.6 读和写流

一旦打开了流,则可在3种不同类型的非格式化I/O中进行选择。
1)每次一个字符的I/O。
2)每次一行的I/O。
3)直接I/O。

以下三个函数可用于一次读一个字符。

#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
/** **/

函数getchar等同于getc(stdin),getc可被实现为宏,fgetc不能。

对应上面所述的每个输入函数都有一个输出函数:

#include <stdio.h>
int putc(int c, FILE *fp);
int fputc(int c,FILE *fp);
int putchar(int c);
5.7 每次一行I/O
#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);
/** 成功返回buf,若已到达文件尾端或出错,返回NULL **/

这两个函数都指定了缓冲区的地址,读入的行送入其中。
gets从标准输入读,fgets从指定的流读。
gets是一个不推荐使用的函数,可能造成缓冲区溢出。

fputs和puts提供每次输出一行的功能。

#include <stdio.h>
int fputs(const char *restrict str, FILE *restrict fp);
int puts(const char *str);
5.9 二进制I/O
size_t fread(void *restrict 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);
第7章 进程环境
7.2 main函数

C程序总是从main函数开始执行,main函数原型是:

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

argc是命令行参数的数目,argv是指向参数的各个指针所构成的数组。

7.3 进程终止

_exit_Exit立即进入内核,exit则先执行一些清理处理,然后返回内核。
main函数返回一个整型值与用该值调用exit是等价的。

exit(0);等价于return (0);

7.5 环境表

每个程序都接收到一张环境表,环境表也是一个字符指针数组。

7.6 C程序的存储空间布局
  • 正文段。CPU执行的机器指令部分。
  • 初始化数据段。
  • 未初始化数据段。BSS段,block started by symbol
  • 栈。
  • 堆。堆位于未初始化数据段和栈之间。
第8章 进程控制
8.2 进程标识

ID为0的进程通常是调度进程,常常被称为交换进程(swapper)。该进程是内核的一部分,它并不执行任何磁盘上的程序,因此也被称为系统进程。进程ID 1通常是init进程,在自举过程结束时由内核调用。

#include <unistd.h>
pid_t getpid(void);  //返回值:调用进程的进程ID
pid_t getppid(void); //返回值:调用进程的父进程ID
8.3 函数fork

一个现有的进程可以调用fork函数创建一个新进程。

#include <unistd.h>
pid_t fork(void);
//返回值:子进程返回0,父进程返回子进程ID;若出错,返回-1

fork函数被调用一次,但返回两次。父进程和子进程共享正文段,子进程获得父进程数据空间、堆、和栈的副本。
一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的,这取决于内核所使用的调度算法。

  • strlen()不包含终止null字节的字符长度。
  • sizeof()包括null.

父进程和子进程共享一个文件偏移量。

fork有以下两种用法。

1)一个父进程希望复制自己,使父进程和子进程同时执行不同的代码。

2)一个进程要执行一个不同的程序。shell,子进程从fork返回后立即调用exec

8.4 函数vfork

可移植的应用程序不应该使用这个函数。

vfork函数用于创建一个新进程,而该新进程的目的是exec一个新程序。shell的基本部分就是这样。

vfork保证子进程先运行,在它调用execexit之后父进程才可能被调度运行。
对于父进程已终止的所有进程,它的父进程都改变为init进程,我们称这些进程由init进程收养。

内核为每个终止进程保存了一定量的信息,所以当终止进程的父进程调用waitwaitpid时,可以得到这些信息。

8.6 函数waitwaitpid

当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。

#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
8.10 函数exec

当进程调用一种exec函数时,该进程执行的程序完全替换为新程序,而新程序则从其main函数开始执行。exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。

第10章 信号
10.1 引言

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

10.2 信号的概念

在头文件<signal.h>中,信号名都被定义为正整数常量(信号编号)。

很多条件可以产生信号:

  • 按终止键。
  • 硬件异常产生信号:除数为0、无效内存引用。
  • 进程调用kill(2)函数可将任意信号发送给另一个进程或进程组。
  • 用户可用Kill(1)命令将信号发送给其他进程。此命令只是kill函数的接口。常用此命令终止一个失控的后台进程。
  • 当检测到某种软件条件已经发生,并将其通知有关进程时也产生信号。例如SIGURG(在网络连接上传来带外的数据)、SIGPIPE(在管道的读进程已终止后,一个进程写此管道)以及SIGALRM(进程所设置的定时器已经超时)。

信号是异步事件的经典实例。产生信号的事件对进程而言是随机出现的。在某个信号出现时,可以告诉内核按下列3种方式之一进行处理:

(1)忽略此信号。SIGKILLSIGSTOP不能被忽略,他俩向内核和超级用户提供了使进程终止或停止的可靠方法。

(2)捕捉信号。为了做到这一点,要通知内核在某种信号发生时,调用一个用户函数。注意,不能捕捉SIGKILLSIGSTOP信号。

(3)执行系统默认动作。绝大多数信号的系统默认动作是终止该进程。

10.3 函数signal

UNIX系统信号机制最简单的接口是signal函数。

#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
                        成功返回以前的信号处理配置,出错返回SIG_ERR
10.6 可重入函数

Single UNIX Specification 说明了在信号处理程序中保证调用安全的函数。这些函数是可重入的并被称为是异步信号安全的。除了可重入以外,在信号处理操作期间,它会阻塞任何会引起不一致的信号发送。
不可重入函数的几个特性:

  1. 使用静态数据结构
  2. 调用mallocfree
  3. 是标准I/O函数。标准I/O库的很多实现都以不可重入方式使用全局数据结构。
10.9 函数killraise

kill函数将信号发送给进程或进程组。raise函数则允许进程向自身发送信号。

#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
                    //OK return 0;  Error return -1

调用raise(signo);等价于调用kill(getpid(), signo);

kill的pid参数有以下4种不同情况:

  • pid>0 将该信号发送给进程ID为pid的进程。
  • pid==0 发送给同一进程组的所有进程。
  • pid<0 发送给ID等于pid绝对值,而且发送进程具有权限向其发送信号的所有进程。
  • pid==-1 将信号发送给发送进程有权限向他们发送信号的所有进程。
10.10 函数alarmpause

使用alarm函数可以设定一个定时器(闹钟时间),在将来的某个时刻该定时器会超时。当定时器超时时,产生SIGALRM信号。如果忽略或不捕捉此信号,则其默认动作是终止调用该alarm函数的进程。

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

每个进程只能有一个闹钟时间。

pause函数使调用进程挂起直至捕捉到一个信号。

#include <unistd.h>
int pause(void);

只有执行了一个信号处理程序并从其返回时,pause才返回。在这种情况下,pause返回-1,errno设置为EINTR

第13章 守护进程
#include "apue.h"
#include <syslog.h>
#include <fcntl.h>
#include <sys/resource.h>

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)
        err_quit("%s: can't get file limit", cmd);

    /*
     * Become a session leader to lose controlling TTY.
     */
    if ((pid = fork()) < 0)
        err_quit("%s: can't fork", cmd);
    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)
        err_quit("%s: can't ignore SIGHUP", cmd);
    if ((pid = fork()) < 0)
        err_quit("%s: can't fork", cmd);
    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)
        err_quit("%s: can't change directory to /", cmd);

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

#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);
}
本书全面介绍了UNIX系统的程序设计界面—系统调用界面和标准C库提供的许多函数。 本书的前15章着重于理论知识的阐述,主要内容包括UNIX文件和目录、进程环境、进程控制、 进程间通信以及各种I/O。在此基础上,分别按章介绍了多个应用实例,包括如何创建数据库函数库, PostScript 打印机驱动程序,调制解调器拨号器及在伪终端上运行其他程序的程序等。 本书内容丰富权威, 概念清晰精辟,一直以来被誉为UNIX编程的“圣经”,对于所有UNIX程序员—无论是初学者还是专家级人士 —都是一本无价的参考书籍。 目 录 译者序 译者简介 前言 第1章 UNIX基础知识 1 1.1 引言 1 1.2 登录 1 1.2.1 登录名 1 1.2.2 shell 1 1.3 文件和目录 2 1.3.1 文件系统 2 1.3.2 文件名 2 1.3.3 路径名 2 1.3.4 工作目录 4 1.3.5 起始目录 4 1.4 输入和输出 5 1.4.1 文件描述符 5 1.4.2 标准输入、标准输出和标准 出错 5 1.4.3 不用缓存的I/O 5 1.4.4 标准I/O 6 1.5 程序和进程 7 1.5.1 程序 7 1.5.2 进程和进程ID 7 1.5.3 进程控制 7 1.6 ANSI C 9 1.6.1 函数原型 9 1.6.2 类属指针 9 1.6.3 原始系统数据类型 10 1.7 出错处理 10 1.8 用户标识 11 1.8.1 用户ID 11 1.8.2 组ID 12 1.8.3 添加组ID 12 1.9 信号 12 1.10 UNIX时间值 14 1.11 系统调用和库函数 14 1.12 小结 16 习题 16 第2章 UNIX标准化及实现 17 2.1 引言 17 2.2 UNIX标准化 17 2.2.1 ANSI C 17 2.2.2 IEEE POSIX 18 2.2.3 X/Open XPG3 19 2.2.4 FIPS 19 2.3 UNIX实现 19 2.3.1 SVR4 20 2.3.2 4.3+BSD 20 2.4 标准和实现的关系 21 2.5 限制 21 2.5.1 ANSI C限制 22 2.5.2 POSIX限制 22 2.5.3 XPG3限制 24 2.5.4 sysconf、pathconf 和fpathconf 函数 24 2.5.5 FIPS 151-1要求 28 2.5.6 限制总结 28 2.5.7 未确定的运行时间限制 29 2.6 功能测试宏 32 2.7 基本系统数据类型 32 2.8 标准之间的冲突 33 2.9 小结 34 习题 34 第3章 文件I/O 35 3.1 引言 35 3.2 文件描述符 35 3.3 open函数 35 3.4 creat函数 37 3.5 close函数 37 3.6 lseek函数 38 3.7 read函数 40 3.8 write函数 41 3.9 I/O的效率 41 3.10 文件共享 42 3.11 原子操作 45 3.11.1 添加至一个文件 45 3.11.2 创建一个文件 45 3.12 dup和dup2函数 46 3.13 fcntl函数 47 3.14 ioctl函数 50 3.15 /dev/fd 51 3.16 小结 52 习题 52 第4章 文件和目录 54 4.1 引言 54 4.2 stat, fstat和lstat函数 54 4.3 文件类型 55 4.4 设置-用户-ID和设置-组-ID 57 4.5 文件存取许可权 58 4.6 新文件和目录的所有权 60 4.7 access函数 60 4.8 umask函数 62 4.9 chmod和fchmod函数 63 4.10 粘住位 65 4.11 chown, fchown和 lchown函数 66 4.12 文件长度 67 4.13 文件截短 68 4.14 文件系统 69 4.15 link, unlink, remove和rename 函数 71 4.16 符号连接 73 4.17 symlink 和readlink函数 76 4.18 文件的时间 76 4.19 utime函数 78 4.20 mkdir和rmdir函数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luuyiran

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值