IPC学习(3)--记录锁(守护进程)
时间:2006-04-03 来源:rwen2012
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* struct flock *arg */);
struct flock {
short l_type; //F_RDLCK, F_WRLCK, F_UNLCK
short l_whence; //SEEK_SET, SEEK_CUR, SEEK_END
off_t l_start; //relative starting offset in bytes
off_t l_len; //#bytes, 0 means until end-f-file
off_t l_pid; //PID returned by F_GETLK
};
命令有如下三个:
F_SETLK 加锁或解锁,非阻塞方式
F_SETLKW 加锁,阻塞方式
F_GETLK 获取锁的信息,如读锁或写锁,加锁进程的PID
需要注意:
1. F_GETLK的作用是获取用F_SETLK出锁后的信息,而不是用来判断一个文件是否被加锁,因为,假如用F_GETLK测试文件没有上锁,于是偿试用 F_SETLK加锁,但可能的情况是,在测试与其上锁之间的时间里,另一个进程对它上了锁,一样会导致出锁。总之,就不是原子操作。
2. 子进程不会继承父进程的记录锁,假设子进程继承了父进程的锁,那跟没有加锁没什么差别了--有多个进程读写该文件。
下面的例子是一个守护进程的模板,记录锁用于保证只有一个进程副本在运行,注意上面说到的第2点,子进程不继承父进程的记录锁,所以必须在子进程中对文件加锁。
//===================================
#include <syslog.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <signal.h>
#include <sys/types.h>
#include <errno.h>
#define MAXFD 256
#define PIDFILE "pidfile"
int main(int argc, char **argv)
{
int i, pidfd;
pid_t pid;
char line[256];
struct flock lock;
/********************************/
//daemonize...
if ( (pid = fork()) < 0){
printf("fork error\n");
return (-1);
}
else if (pid)
_exit(0);
//child one continue...
if (setsid() < 0) //become the session leader
return (-1);
signal(SIGHUP, SIG_IGN);
if ( (pid = fork()) < 0){
printf("the second fork error\n");
return (-1);
}
else if (pid)
_exit(0);
/************************************/
//Make sure there is only one copy on running, use fcntl lock here,
// also, you can use semaphore
pidfd = open(PIDFILE, O_RDWR | O_CREAT, 0644);
if (pidfd < 0){
printf("open error\n");
exit(-1);
}
lock.l_type = F_WRLCK;
lock.l_start = 0;
lock.l_whence = SEEK_SET;
lock.l_len = 0;
if (fcntl(pidfd, F_SETLK, &lock) < 0)
if (errno == EACCES || errno == EAGAIN){
printf("%s is already running?\nexit...\n", PIDFILE);
return (-1);
} else {
printf("unable to lock %s\n", PIDFILE);
return (-1);
}
snprintf(line, sizeof(line), "%ld\n", (long)getpid());
ftruncate(pidfd, 0);
write(pidfd, line, strlen(line));
/***********************************/
chdir("/");
//close off file descriptors
for (i=0; i<pidfd; i++)
close(i);
//redirect stdin, stdout and stderr to /dev/null
open("/dev/null", O_RDONLY);
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);
//write the log to syslogd
// openlog(argv[0], LOG_PID, 0);
// syslog(LOG_DAEMON | LOG_INFO, "lock file %s ok\n", PIDFILE);
//closelog();
//do_what_you_want_here()
sleep(15);
return 0;
}
//===================================
下面简要说说创建守护进程的规则:
1、在后台运行
为避免挂起控制终端,要将daemon放入后台执行,其方法是,在进程中调用fork使父进程终止,让daemon在子进程中后台执行。具体就是调用fork ,然后使父进程exit 。这样做实现了下面几点:
第一,如果该精灵进程是由一条简单shell 命令起动的,那么使父进程终止使得shell认为这条命令已经执行完成。
第二,子进程继承了父进程的进程组ID ,但具有一个新的进程ID,这就保证了子进程不是一个进程组的首进程。这对于下面就要做的setsid调用是必要的前提条件。
2、脱离控制终端,登录会话和进程组
登录会话可以包含多个进程组,这些进程组共享一个控制终端,这个控制终端通常是创建进程的登录终端、控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。
其方法是在第一点的基础上,调用setsid()使进程成为会话组长:
需要说明的是,当进程是会话组长时,setsid()调用会失败,但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离,由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
具体是操作就是:
a. 成为新对话期的首进程
b. 成为一个新进程组的首进程
c. 没有控制终端。
3、禁止进程重新打开控制终端
现在,进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端。可以通过fork()一个进程,使之不再成为会话组长来禁止进程重新打开控制终端。
4、关闭打开的文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在地文件系统无法卸下以及无法预料的错误。一般来说,必要 的是关闭0、1、2三个文件描述符,即标准输入、标准输出、标准错误。因为我们一般希望守护进程自己有一套信息输出、输入的体系,而不是把所有的东西都发 送到终端屏幕上。
5、改变当前工作目录
将当前工作目录更改为根目录。从父进程继承过来的当前工作目录可能在一个装配的文件系统中。因为精灵进程通常在系统再引导之前是一直存在的,所以如果精灵进程的当前工作目录在一个装配文件系统中,那么该文件系统就不能被拆卸。
另外,某些精灵进程可能会把当前工作目录更改到某个指定位置,在此位置做它们的工作。例如,行式打印机假脱机精灵进程常常将其工作目录更改到它们的spool 目录上。
6、重设文件创建掩码
将文件方式创建屏蔽字设置为0 。由继承得来的文件方式创建屏蔽字可能会拒绝设置某些许可权。例如,若精灵进程要创建一个组可读、写的文件,而继承的文件方式创建屏蔽字,屏蔽了这两种许可权,则所要求的组可读、写就不能起作用。
7、处理SIGCHLD 信号
处理SIGCHLD信号并不是必需的。但对于某些进程,特别是服务器进程往往在请求到来时生产子进程出来请求。如果父进程不等待子进程结束,子进程将 成为僵尸进程,(zombie)而仍占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在系统V下可以简单的将 SIGCHLD信号的操作设为SIG-IGN:
signal(SIGCHLD,SIG_IGN);
这样,内核在子进程结束时不会产生僵尸进程,这一点与BSD4不同,在BSD4下必须显示等 待子进程结束才能释放僵尸进程。
守护进程的错误输出
守护进程不属于任何终端,所以当需要输出某些信息时,它无法像一般程序那样将信息直接输出到标准输出和标准错误输出中。我们很大时候也不希望每个守护 进程将它自己的出错消息写到一个单独的文件中。因为对于系统管理人员而言,要记住哪一个守护进程写到哪一个记录文件中,并定期的检查这些文件,他一定会为 此感到头疼的。所以,我们需要有一个集中的守护进程出错记录机制。目前很多系统都引入了syslog记录进程来实现这一目的。
自伯克利开发了BSD syslog并广泛应用以来,BSD syslog 机制被大多数守护进程所使用。我们下面介绍BSD syslog 的用法。有三种方法产生记录消息:
1 内核例程可以调用log函数。任何一个用户进程通过打开和读/dev/klog设备就可以读取这些消息。因为我们无意编写内核中的例程,所以不再进一步说明此函数。
2 大多数用户进程(守护进程)调用syslog函数以产生记录消息。我们将在下面说明其调用序列。这使消息发送至Unix域数据报套接口/dev/log。
3 在此主机上,或通过TCP/IP网络连接到此主机的某一其他主机上的一个用户进程可将记录消息发向UDP端口514。
注意:syslog 函数并不产生这些UDP数据报——它们要求产生此记录消息的进程具有显式的网络编程。
通常,syslog守护进程读取三种格式的记录消息。此守护进程在启动时读一个配置文件。一般来说,其文件名为/etc/syslog.conf,该 文件决定了不同种类的消息应送向何处。例如,紧急消息可被送向系统管理员(若已登录),并在控制台上显示,而警告消息则可记录到一个文件中。该机制提供了 syslog函数,其调用格式如下
#include <syslog.h>;
void openlog (char*ident,int option ,int facility);
void syslog(int priority,char*format,……)
void closelog();
int fcntl(int fd, int cmd, ... /* struct flock *arg */);
struct flock {
short l_type; //F_RDLCK, F_WRLCK, F_UNLCK
short l_whence; //SEEK_SET, SEEK_CUR, SEEK_END
off_t l_start; //relative starting offset in bytes
off_t l_len; //#bytes, 0 means until end-f-file
off_t l_pid; //PID returned by F_GETLK
};
命令有如下三个:
F_SETLK 加锁或解锁,非阻塞方式
F_SETLKW 加锁,阻塞方式
F_GETLK 获取锁的信息,如读锁或写锁,加锁进程的PID
需要注意:
1. F_GETLK的作用是获取用F_SETLK出锁后的信息,而不是用来判断一个文件是否被加锁,因为,假如用F_GETLK测试文件没有上锁,于是偿试用 F_SETLK加锁,但可能的情况是,在测试与其上锁之间的时间里,另一个进程对它上了锁,一样会导致出锁。总之,就不是原子操作。
2. 子进程不会继承父进程的记录锁,假设子进程继承了父进程的锁,那跟没有加锁没什么差别了--有多个进程读写该文件。
下面的例子是一个守护进程的模板,记录锁用于保证只有一个进程副本在运行,注意上面说到的第2点,子进程不继承父进程的记录锁,所以必须在子进程中对文件加锁。
//===================================
#include <syslog.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <signal.h>
#include <sys/types.h>
#include <errno.h>
#define MAXFD 256
#define PIDFILE "pidfile"
int main(int argc, char **argv)
{
int i, pidfd;
pid_t pid;
char line[256];
struct flock lock;
/********************************/
//daemonize...
if ( (pid = fork()) < 0){
printf("fork error\n");
return (-1);
}
else if (pid)
_exit(0);
//child one continue...
if (setsid() < 0) //become the session leader
return (-1);
signal(SIGHUP, SIG_IGN);
if ( (pid = fork()) < 0){
printf("the second fork error\n");
return (-1);
}
else if (pid)
_exit(0);
/************************************/
//Make sure there is only one copy on running, use fcntl lock here,
// also, you can use semaphore
pidfd = open(PIDFILE, O_RDWR | O_CREAT, 0644);
if (pidfd < 0){
printf("open error\n");
exit(-1);
}
lock.l_type = F_WRLCK;
lock.l_start = 0;
lock.l_whence = SEEK_SET;
lock.l_len = 0;
if (fcntl(pidfd, F_SETLK, &lock) < 0)
if (errno == EACCES || errno == EAGAIN){
printf("%s is already running?\nexit...\n", PIDFILE);
return (-1);
} else {
printf("unable to lock %s\n", PIDFILE);
return (-1);
}
snprintf(line, sizeof(line), "%ld\n", (long)getpid());
ftruncate(pidfd, 0);
write(pidfd, line, strlen(line));
/***********************************/
chdir("/");
//close off file descriptors
for (i=0; i<pidfd; i++)
close(i);
//redirect stdin, stdout and stderr to /dev/null
open("/dev/null", O_RDONLY);
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);
//write the log to syslogd
// openlog(argv[0], LOG_PID, 0);
// syslog(LOG_DAEMON | LOG_INFO, "lock file %s ok\n", PIDFILE);
//closelog();
//do_what_you_want_here()
sleep(15);
return 0;
}
//===================================
下面简要说说创建守护进程的规则:
1、在后台运行
为避免挂起控制终端,要将daemon放入后台执行,其方法是,在进程中调用fork使父进程终止,让daemon在子进程中后台执行。具体就是调用fork ,然后使父进程exit 。这样做实现了下面几点:
第一,如果该精灵进程是由一条简单shell 命令起动的,那么使父进程终止使得shell认为这条命令已经执行完成。
第二,子进程继承了父进程的进程组ID ,但具有一个新的进程ID,这就保证了子进程不是一个进程组的首进程。这对于下面就要做的setsid调用是必要的前提条件。
2、脱离控制终端,登录会话和进程组
登录会话可以包含多个进程组,这些进程组共享一个控制终端,这个控制终端通常是创建进程的登录终端、控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。
其方法是在第一点的基础上,调用setsid()使进程成为会话组长:
需要说明的是,当进程是会话组长时,setsid()调用会失败,但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离,由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
具体是操作就是:
a. 成为新对话期的首进程
b. 成为一个新进程组的首进程
c. 没有控制终端。
3、禁止进程重新打开控制终端
现在,进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端。可以通过fork()一个进程,使之不再成为会话组长来禁止进程重新打开控制终端。
4、关闭打开的文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在地文件系统无法卸下以及无法预料的错误。一般来说,必要 的是关闭0、1、2三个文件描述符,即标准输入、标准输出、标准错误。因为我们一般希望守护进程自己有一套信息输出、输入的体系,而不是把所有的东西都发 送到终端屏幕上。
5、改变当前工作目录
将当前工作目录更改为根目录。从父进程继承过来的当前工作目录可能在一个装配的文件系统中。因为精灵进程通常在系统再引导之前是一直存在的,所以如果精灵进程的当前工作目录在一个装配文件系统中,那么该文件系统就不能被拆卸。
另外,某些精灵进程可能会把当前工作目录更改到某个指定位置,在此位置做它们的工作。例如,行式打印机假脱机精灵进程常常将其工作目录更改到它们的spool 目录上。
6、重设文件创建掩码
将文件方式创建屏蔽字设置为0 。由继承得来的文件方式创建屏蔽字可能会拒绝设置某些许可权。例如,若精灵进程要创建一个组可读、写的文件,而继承的文件方式创建屏蔽字,屏蔽了这两种许可权,则所要求的组可读、写就不能起作用。
7、处理SIGCHLD 信号
处理SIGCHLD信号并不是必需的。但对于某些进程,特别是服务器进程往往在请求到来时生产子进程出来请求。如果父进程不等待子进程结束,子进程将 成为僵尸进程,(zombie)而仍占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在系统V下可以简单的将 SIGCHLD信号的操作设为SIG-IGN:
signal(SIGCHLD,SIG_IGN);
这样,内核在子进程结束时不会产生僵尸进程,这一点与BSD4不同,在BSD4下必须显示等 待子进程结束才能释放僵尸进程。
守护进程的错误输出
守护进程不属于任何终端,所以当需要输出某些信息时,它无法像一般程序那样将信息直接输出到标准输出和标准错误输出中。我们很大时候也不希望每个守护 进程将它自己的出错消息写到一个单独的文件中。因为对于系统管理人员而言,要记住哪一个守护进程写到哪一个记录文件中,并定期的检查这些文件,他一定会为 此感到头疼的。所以,我们需要有一个集中的守护进程出错记录机制。目前很多系统都引入了syslog记录进程来实现这一目的。
自伯克利开发了BSD syslog并广泛应用以来,BSD syslog 机制被大多数守护进程所使用。我们下面介绍BSD syslog 的用法。有三种方法产生记录消息:
1 内核例程可以调用log函数。任何一个用户进程通过打开和读/dev/klog设备就可以读取这些消息。因为我们无意编写内核中的例程,所以不再进一步说明此函数。
2 大多数用户进程(守护进程)调用syslog函数以产生记录消息。我们将在下面说明其调用序列。这使消息发送至Unix域数据报套接口/dev/log。
3 在此主机上,或通过TCP/IP网络连接到此主机的某一其他主机上的一个用户进程可将记录消息发向UDP端口514。
注意:syslog 函数并不产生这些UDP数据报——它们要求产生此记录消息的进程具有显式的网络编程。
通常,syslog守护进程读取三种格式的记录消息。此守护进程在启动时读一个配置文件。一般来说,其文件名为/etc/syslog.conf,该 文件决定了不同种类的消息应送向何处。例如,紧急消息可被送向系统管理员(若已登录),并在控制台上显示,而警告消息则可记录到一个文件中。该机制提供了 syslog函数,其调用格式如下
#include <syslog.h>;
void openlog (char*ident,int option ,int facility);
void syslog(int priority,char*format,……)
void closelog();
相关阅读 更多 +