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);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

luuyiran

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

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

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

打赏作者

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

抵扣说明:

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

余额充值