星海's Blog

老头初学编程
APUE学习笔记(一)
APUE学习笔记(三)

APUE学习笔记(二)

星海 posted @ 2011年7月31日 09:56 in APUE && UNP , 4676 阅读
第11章:线程

/* POSIX线程的测试宏是_POSIX_THREADS,也可以把_SC_THREADS常数用于调用syconf汉
 *函数,从而在运行时确定是否支持线程。*/

#include	<pthread.h>
int pthread_equal(ptrread_t tid1, pthread_t tid2);
pthread_t pthread_self(void);

/* 在POSIX线程的情况下,程序开始运行时,它也是以单进程中的单个控制线程启动的。
 */

pthread_creat(pthread_t *restrict tidp, const pthread_attr_t *restrict attr,
              void *(*start_rtn)(void *), void *restrict arg); /* void *,可传结构 */
/* 新创建的线程可以放问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字
 * ,但是该线程的未决信号集将被清除。
 * 注意:pthread函数在调用失败时通常会返回错误码。
 * 每个线程都提供errno的副本,这只是为了与使用errno的现有函数兼容。
 */

/* 单个线程在不终止进程(exit)的情况下,可有3种办法停止它的控制流,退出:
 * 1,线程只是从启动例程中返回,返回值是线程的退出码。
 * 2,线程可以被同一进程中的其他线程取消。
 * 3,线程调用pthread_exit。
 */
void pthread_exit(void *rval_ptr);        /* rval为无类型指针。 */

int pthread_join(pthread_t thread, void **rval_ptr);
/* 调用进程将一直阻塞,直到指定的线程调用pthread_exit,从启动例程中返回或者被取消。
 * 如果线程被取消,用rval_ptr指定的内存单元就被置为PTHREAD_CANCELED.
 * 函数调用成功则返回0,否则返回错误编号。
 *
 * 例如线程函数 return (void *)1, pthread_exit((void *)2);
 * 其他线程可以调用int *tret, pthread_join(&(tret))获取已终止线程的退出码,可用printf (int)tret打印出来。
 * 也可以返回结构,但注意结构使用的内存在调用者完成调用以后必须仍然是有效的。
 */
int pthread_cancel(pthread_t tid);              /* 请求取消同一进程中的其他线程。 */

void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);
/* 如果把execute参数设置为0,清理函数将不被调用。无论何种情况,
 * pthread_cleanup_pop都将删除上次push调用建立的清理处理程序。
 * 这里,APUE中文版中介绍的不全,当调用pthread_exit或其他线程调用
 * pthread_cancel取消该进程时,pthread_clennup_pop自动执行清理函数。
 */

int pthread_detach(pthread_t tid);               /* 使线程进入分离状态 */

/* 在设计时需要规定所有的线程必须遵守相同的数据访问规则,只有这样,互斥机制才
 *才能正常工作。

 * 以下函数成功返回0,失败返回错误编号。
 *
 * PTHREAD_MUTEX_INTIALIZER 常量,只对静态分配的互斥量。
 */
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const
                       pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutext_t *mutex);
int pthread_mutex_trylock(pthread_mutext_t *mutex);
int pthread_mutex_unlock(pthread_mutext_t *mutex);

/* 死锁原因:对同一个互斥量加锁两次,两个线程都在相互请求对另一个线程拥有的资源等等。
 * 一个线程试图以与另一个线程相反的顺序锁住互斥量,才能出现死锁。
 * 如果锁的粒度太粗,就会出现很多线程阻塞等待相同的锁,源自并发性的改善微乎其微。
 * 如果锁得粒度太细,那么过多的锁开销会使系统性能受到影响。
 *
 * 一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
 * 当读写锁在读加锁状态时,如果线程希望以写模式对此锁进行加锁,它必须阻塞直到
 * 所有的线程释放该锁。
 * 实际实现中,如果有写锁请求,读写锁通常会阻塞随后的读模式锁请求。
 */

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
                        const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);


/* 条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
 * 线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到
 * 这种改变。
 *
 * 可以把常量PTHREAD_COND_INTIALIZER赋给静态分配的条件变量。
 */

int pthread_cond_init(pthread_cont_t *restrict cond,
                      pthread_condattr_t *restrict attr);

int pthread_cond_destroy(pthread_cond_t *cond);

int pthread_cond_wait(pthread_cond_t *restrict cond,
                      pthread_mutex_t *restrict mutex); /* 原子操作 */

int pthread_cond_timedwait(pthread_cond_t *restrict cond,
                           pthread_mutex_t *restrict mutex,
                           const struct timespec *restrict timeout);

struct timespec {
        time_t tv_sec;
        long tv_nsec;
}               /* 使用这个结构时,时间值是一个绝对数而不是一个相对数 */

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
/* 先unlock再broadcast,还是先broadcast再unlock要根据具体情况而定。
 * 第一种,程序可以在unlock和broadcast之间获取互斥锁,然后使条件失效。最后释放
 * 互斥锁。接着当调用pthread_cond_broadcast时,条件不再为真,线程无需运行。
 * 第二种,广播后,等待线程会被调度以运行。如果程序运行在多处理机上,由于还持
 还持有互斥锁,一些线程就会运行而且马上阻塞。
 */

/* 第12章:线程控制
 */

#include	<stdio.h>
#include	<pthread.h>

int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);

int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr,
                                int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
/* PTHREAD_CREAT_DETACHED, PTHREAD_CREATE_JOINABLE */

int pthread_attr_getstack(const pthread_attr_t *restrict attr,
                          void **restrict stackaddr,
                          size_t *restrict stacksize);
int pthread_attr_setstack(pthread_attr_t *attr,
                          void *stackaddr, size_t *stacksize);
/* stackaddr线程属性被定义为栈的内存单元的最低地址,但这并不必然是栈的开始位置
 * 。对于某些处理器来说,栈是从高地址向低地址方向伸展的,那么statcksize就是栈
 * 栈的结尾而不是开始。pthread_attr_getstackaddr和pthread_attr_setstackaddr过时。
 */

int pthread_attr_getstacksize(const pthread_attr_t *restrict attr,
                              size_t *restrict stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

/* 线程属性guardsize控制着线程末尾之后用以避免栈溢出的扩展内存的大小,默认设置
 * 默认设置为PAGESIZE个字节。如果对线程属性stackaddr做了修改,系统就会假设我们
 我们会自己管理栈,并使警戒缓冲区机制无效,等同于把guardsize线程属性设为0。
 */
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,
                              size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);

int pthread_getconcurrency(void); /* 返回当前并发度,如果操作系统正控制着并发
				     度(即之前没有调用过pthread_setconcureency函数),则返回0 */
int pthread_setconcureency(int level);          /* level为0的话,撤销之前set所产生的作用 */

int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);

/* 进程共享属性:
 *
 * _SC_THREAD_PROCESS_SHARED传给sysconf检查平台是否支持进程共享这个属性。
 * 在进程中,多个线程可以访问同一个同步对象,默认行为。互斥量属性为PTHREAD_PROCESS_PRIVATE。
 * 相互独立的多个进程可以把同一个内存区域映射到各自独立的地址空间。这时,多个
 多个进程访问共享数据通常也需要同步。如果进程共享互斥量属性设为PTHREAD_PROCESS_SHARED,从多个进程共享的内存区域中分配的互斥量就可以用于这些进程的同步。
 */
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr,
                                 int *restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);

/* 互斥量类型属性:
 */

int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,
                              int *restrict type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);

/* PTHREAD_MUTEX_NORMAL
 *
 *        This  type  of  mutex does not detect deadlock. A thread attempting to relock this
 *        mutex without first unlocking it shall deadlock.  Attempting  to  unlock  a  mutex
 *        locked  by  a different thread results in undefined behavior. Attempting to unlock
 *        an unlocked mutex results in undefined behavior.
 *
 * PTHREAD_MUTEX_ERRORCHECK
 *
 *        This type of mutex provides error checking. A thread  attempting  to  relock  this
 *        mutex  without  first unlocking it shall return with an error. A thread attempting
 *        to unlock a mutex which another thread has locked shall return with  an  error.  A
 *        thread attempting to unlock an unlocked mutex shall return with an error.
 *
 * PTHREAD_MUTEX_RECURSIVE
 *
 *        A  thread attempting to relock this mutex without first unlocking it shall succeed
 *        in locking the mutex. The relocking deadlock which can occur with mutexes of  type
 *        PTHREAD_MUTEX_NORMAL  cannot occur with this type of mutex. Multiple locks of this
 *        mutex shall require the same number of unlocks to release the mutex before another
 *        thread  can acquire the mutex. A thread attempting to unlock a mutex which another
 *        thread has locked shall return with an error.  A thread attempting  to  unlock  an
 *        unlocked mutex shall return with an error.
 *        如果需要把现有的单线程接口放到多线程环境中,递归互斥量是非常有用的。
 *        然而由于递归锁的使用需要一定技巧,它只应在没有其他可行方案下使用。
 *
 * PTHREAD_MUTEX_DEFAULT
 *
 *        Attempting to recursively lock a mutex of this type results in undefined behavior.
 *        Attempting to unlock a mutex of this type which was  not  locked  by  the  calling
 *        thread  results  in undefined behavior.  Attempting to unlock a mutex of this type
 *        which is not locked results in undefined behavior. An implementation may map  this
 *        mutex to one of the other mutex types.
 */

int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);

int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr,
                                  int *restrict pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);

int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
int pthread_condattr_getpshared(pthread_condattr_t *restrict attr, int *restrict pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);


/* 如果一个函数在同一时刻可以被多个线程安全地调用,就称该函数是线程安全的。
 * 如果一个函数对多个线程来说是可重入的,则说这个函数是线程安全的。但并不能但
 * 但并不能说明对信号处理程序来说该函数也是可重入的。如果函数对异步信号处理程
 * 程序的重入是安全地,那么就可以说函数是 异步-信号 安全地。
 */
/* 以线程安全的方式干里FILE对象的方法。 */

int ftrylockfile(FILE *fp);
void flockfile(FILE *fp);
void funlockfile(FILE *fp);

/* 进程中的所有线程都可以访问进程的整个地址空间。除了使用寄存器以外,线程没有
 * 线程没有办法阻止其他线程访问它的数据,线程私有数据也不例外。
 * 虽然底层的实现部份不能阻止这种访问能力,但管理线程私有数据的函数可以提高线
 * 程间的数据独立数据独立性。
 */

int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));
/* 创建的键存放在keyp指向的内存单元。这个键可以被进程中的所有线程使用,但每个
 * 线程把这个键与不同私有数据地址相关联。创建新键时,每个线程的数据地址设为NULL。
 * destructor 为析构函数。
 */

int pthread_key_delete(pthread_key_t *key);
/* 取消键与线程私有数据值之间的关联
 * 注意:调用pthread_key_delete并不会激活与key相关联的析构函数*/

pthread_once_t initflag = PTHREAD_ONCE_INIT;  /* initflag必须是全局或静态变量 */
int pthread_once(pthread_once_t *initflag, void (*initfn)(void));

/* 把键和线程私有数据关联起来
 */
void *pthread_getspecific(pthread_key_t key);   /* 返回线程私有数据值 */
int pthread_setspecific(pthread_key_t key, const void *value);

/* 取消选项
 * PTHREAD_CANCEL_ENABLE, PTHREAD_CANCEL_DISABLE,PTHREAD_CANCEL_ASYNCHRONOUS,
 * PTHREAD_CANCEL_DEFERRED。使用异步取消时,线程可在任意时间取消。
 */

int pthread_setcancelstate(int state, int *oldstate);
/* 原子操作。
 * 在默认情况下,线程在取消请求发出后还是继续运行,直到线程到达某个取消点。
 * 取消点是线程检查是否被取消并按照请求进行动作的一个位置。*/

/* 每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有信号共享的。
 * 这意味着,尽管单个线程可以阻止某些信号,但当线程修改了与某个信号相关的处理
 处理行为之后,所有线程都必须!! 共享这个处理行为的改变。

 线程必须用pthread_sigmask来阻止信号发送。
 */

#include	<signal.h>

int pthread_sigmask(int how, const sigset_t *restrict set,
                    sigset_t *restrict oset);       /* 失败时返回错误码,而sigprocmask设置errno返回-1 */
int sigwait(const sigset_t *restrict set, int *restrict signop);
/* set参数指定了线程等待的信号集,signop指向的整数将作为返回值,表明接收到的信号值
 * 如果信号集中的某个信号在sigwait调用时处于未决状态,那么sigwait将无阻塞返回
 * ,返回之前,sigwait将从进程中移除那些处于未决状态的信号。且恢复之前的信号屏
信号屏蔽字。
 *
 * 为避免错误动作,线程在调用sigwait之前,必须阻塞那些它正在等待的信号。
 *
 * 如果多个线程调用sigwait等待同一信号,则线程阻塞,只有一个线程可以从sigwait返回。
 * 如果信号被捕获(例如进程使用sigaction建立了一个信号处理程序),而且线程在
 * sigwait调用中等待同一信号,则未定义以哪种方式递送信号。但不能出现两者皆可的
 * 情况。
 */

int pthread_kill(pthread_t thread, int signo);

/* 要清除锁状态,可通过调用pthread_atfork函数建立fork处理程序。*/

int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
// prepare处理程序由父进程在fork子进程前调用,任务是获取父进程定义的所有锁。
//
// parent处理程序是在fork子进程以后,但在fork返回之前在父进程环境中调用,这个
// fork处理程序的任务是对prepare fork处理程序获得的所有锁进行解锁。
//
// child fork处理程序在fork返回之前在子进程环境中调用,也必须释放prepare fork处
// 处理程序获得的所有锁。
//
// 多次调用pthread_atfork可设置多套fork处理程序。此时,
// parent和child fork处理程序是以他们注册时的顺序进行调用的。而parent fork处理
// 处理程序的调用顺序与注册时的顺序相反。

//freebsd上可能导致程序崩溃。

#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>

extern char **environ;

pthread_mutex_t env_mutex;
static pthread_once_t init_done = PTHREAD_ONCE_INIT;

static void
thread_init(void)
{
        pthread_mutexattr_t attr;

        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        pthread_mutex_init(&env_mutex, &attr);
        pthread_mutexattr_destroy(&attr);
}

int
getenv_r(const char *name, char *buf, int buflen)
{
        int i, len, olen;

        pthread_once(&init_done, thread_init);
        len = strlen(name);
        pthread_mutex_lock(&env_mutex);
        for (i = 0; environ[i] != NULL; i++) {
                if ((strncmp(name, environ[i], len) == 0) &&
                    (environ[i][len] == '=')) {
                        olen = strlen(&environ[i][len+1]);
                        if (olen >= buflen) {
                                pthread_mutex_unlock(&env_mutex);
                                return(ENOSPC);
                        }
                        strcpy(buf, &environ[i][len+1]);
                        pthread_mutex_unlock(&env_mutex);
                        return(0);
                }
        }
        pthread_mutex_unlock(&env_mutex);
        return(ENOENT);
}
/*
* 要使getenv_r可重入,需要改变接口,调用者必须提供自己的缓冲区,避免其他线程的
* 干扰,但是这样还不足以使getenv_r成为线程安全地。还需要在搜索请求的字符串时保
* 护环境不被修改。我们可以使用互斥量,通过getenv_r和putenv函数对环境列表的访问
* 进行序列化。
*/
/*
 * ===  FUNCTION  ======================================================================
 *         Name:  同步信号处理
 *  Description:  普通进程中,唯一可运行的控制线程就是主线程和信号处理程序,所以阻
 所以阻塞信号足以避免错失标志修改。
 		  但在线程中,需要使用互斥量来保护标志。

		  以下程序,
		  POSIX.1中,异步信号发送到进程以后,进程中没有阻塞该信号的被选中,接收。
		  Linux kernel 2.4中,异步信号发送到特定的线程,因为Linux每个线程作为独立进程,
		  用clone(2)共享资源,这样系统就不能选择当前没有阻塞该信号的线程。这样一
		  来,线程有可能注意不到该信号。
		  信号产生于终端驱动程序的话,发送给进程组,下列程序可运行。
		  但如果调用kill的话,Linux就不能如预期的一样工作。

		  kernel 2.6实现经我测试和POSIX.1实现一样,没有问题。具体情况还要以后探讨。
 * =====================================================================================
 */
#include "apue.h"
#include <pthread.h>
#include	<error.c>

int			quitflag;	/* set nonzero by thread */
sigset_t	mask;

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t waitloc = PTHREAD_COND_INITIALIZER;

void *
thr_fn(void *arg)
{
        int err, signo;

        for (;;) {
                err = sigwait(&mask, &signo);
                if (err != 0) {
                        err_exit(err, "sigwait failed");
                }
                switch (signo) {
                case SIGINT:
                        printf("\ninterrupt\n");
                        break;

                case SIGQUIT:
                        pthread_mutex_lock(&lock);
                        quitflag = 1;
                        pthread_mutex_unlock(&lock);
                        pthread_cond_signal(&waitloc);
                        return(0);

                default:
                        printf("unexpected signal %d\n", signo);
                        exit(1);
                }
        }
}

int
main(void)
{
        int		err;
        sigset_t	oldmask;
        pthread_t	tid;

        sigemptyset(&mask);
        sigaddset(&mask, SIGINT);
        sigaddset(&mask, SIGQUIT);
        if ((err = pthread_sigmask(SIG_BLOCK, &mask, &oldmask)) != 0) { /* 主进程阻塞,线程sigwait会自动解开处理 */
                err_exit(err, "SIG_BLOCK error");
        }

        err = pthread_create(&tid, NULL, thr_fn, 0);
        if (err != 0) {
                err_exit(err, "can't create thread");
        }

        pthread_mutex_lock(&lock);
        while (quitflag == 0) {
                pthread_cond_wait(&waitloc, &lock);
        }
        pthread_mutex_unlock(&lock);

        /* SIGQUIT has been caught and is now blocked; do whatever */
        quitflag = 0;

        /* reset signal mask which unblocks SIGQUIT */
        if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
                err_sys("SIG_SETMASK error");
        }
        exit(0);
}
/* 第13章:守护进程
 */

/* 守护进程也称daemon精灵进程。
 *
 * 父进程ID为0的通常是各种内核进程
 * ID 1:init 系统守护进程,负责启动各运行层次特定的系统服务
 * keventd守护进程为在内核中运行计划执行的函数提供进程上下文。
 * kswaped守护进程也称为页面调出守护进程(pageout daemon)。它通过将脏页面以递
 * 低俗写到磁盘上从而使这些页面在需要时仍可回收使用,支持虚拟子系统。
 * bdflush/kupdated将告诉缓存中的数据冲洗到此盘上。
 * 当可用内存达到下限时,bdflush守护进程将脏缓存区从缓冲池(buffer cache)中噢
 中冲洗到磁盘上。每隔一定时间间隔,Kupdated将脏页面冲洗到磁盘上。以便在系统失
 效时减少丢失的数据。
 */

/* 守护进程编程规则
 *
 * 1,umask将文件模式创建屏蔽字设置为0
 * 2,调用fork,使父进程退出(exit),这样做:第一,如果该守护进程是做为shell命
 * 命令启动,则父进程终止使得shell认为这条命令已经执行完毕。第二,保证了子进程不是进程组长。
 * 3,调用setsid创建新会话(有人建议在此前再次调用一次fork,然后exec,以保证该
 * 守护进程不是会话首进程,从而防止它取得控制终端,或者无论何时打开终端设备都
 * 都支持O_NOCTTY
 * 4,将当前工作目录更改为根目录。打印机假脱机守护进程有时改为他们的SPOOL目录
 * 5,关闭不再需要的文件描述符。(这一点被很多人诟病,认为是第二版作者的话作者
 作者的画蛇添足)
 * 6,某些守护进程打开/dev/null,使其具有文件描述符0, 1, 2。
 */


#include	<stdio.h>
#include	<stdlib.h>
#include	<unistd.h>
#include	<signal.h>
#include	<syslog.h>
#include	<fcntl.h>
#include	<sys/resource.h>
#include	<sys/stat.h>

void daemonize(const char *cmd)
{
        int i, fd0, fd1, fd2;
        pid_t pid;
        struct rlimit rl;
        struct sigaction sa;

        umask(0);

        if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
                perror("can't get fileno limit");
        }

        if ((pid = fork()) < 0) {
                perror("fork error");
        } else if (pid != 0) {
                exit(0);
        }

        setsid();

        sa.sa_handler = SIG_IGN;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        if (sigaction(SIGHUP, &sa, NULL) < 0) {
                perror("can't ignore SIGHUP");
        }

        if ((pid = fork()) < 0) {
                perror("2nd fork error");
        } else if (pid != 0) {
                exit(0);
        }

        if (chdir("/") < 0) {
                perror("chdir error");
        }

        if (rl.rlim_max == RLIM_INFINITY) {
                rl.rlim_max = 1024;
        }
        for (i = 0; i < rl.rlim_max; i++) {
                close(i);
        }

        fd0 = open("/dev/null", O_RDWR);
        fd1 = dup(0);
        fd2 = dup(0);

        openlog(cmd, LOG_CONS, LOG_DAEMON);
        if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
                syslog(LOG_ERR, "unexpected file destriptors %d %d %d", fd1, fd2, fd2);
        }
        exit(1);
}

/* 有三种方法产生日志消息:
 * 1,内核例程调用log函数。任何一个用户进程打开然后读/dev/klogd设备就可设备就
设备就可以读取这些消息。
 * 2,大多数用户进程(守护进程)调用syslog(3)函数以产生日志消息。
 * 3,在此主机上的一个用户进程,或通过TCP/IP网络连接到此主机的其他主机上的一个
 * 用户进程可将日志消息发向UDP端口514。
 */


#include	<sys.log.h>

void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
/* priority为facility和level的组合
   format参数以及其他参数传至vsprintf函数进行格式化,%m替换成对应于errno值的出错消息字符串。*/
void closelog(void);
int setlogmask(int maskpri);

/*
 *    option
 *        The option argument to openlog() is an OR of any of these:
 *
 *        LOG_CONS       Write directly to system console if there is an error while
 *                       sending to system logger.
 *
 *        LOG_NDELAY     Open the connection immediately (normally,  the  connection
 *                       is opened when the first message is logged).
 *
 *        LOG_NOWAIT     Don't  wait  for child processes that may have been created
 *                       while logging the message.  (The GNU  C  library  does  not
 *                       create  a  child  process,  so this option has no effect on
 *                       Linux.)
 *
 *        LOG_ODELAY     The converse of LOG_NDELAY; opening of  the  connection  is
 *                       delayed  until  syslog()  is called.  (This is the default,
 *                       and need not be specified.)
 *
 *        LOG_PERROR     (Not in POSIX.1-2001.)  Print to stderr as well.
 *
 *        LOG_PID        Include PID with each message.
 *
 *    facility
 *        The facility argument is used to specify what type of program  is  logging
 *        the  message.  This lets the configuration file specify that messages from
 *        different facilities will be handled differently.
 *
 *        LOG_AUTH       security/authorization messages (DEPRECATED  Use  LOG_AUTH-
 *                       PRIV instead)
 *
 *        LOG_AUTHPRIV   security/authorization messages (private)
 *
 *        LOG_CRON       clock daemon (cron and at)
 *
 *        LOG_DAEMON     system daemons without separate facility value
 *
 *        LOG_FTP        ftp daemon
 *
 *        LOG_KERN       kernel  messages  (these  can't be generated from user pro-
 *                       cesses)
 *
 *        LOG_LOCAL0 through LOG_LOCAL7
 *                       reserved for local use
 *
 *        LOG_LPR        line printer subsystem
 *
 *        LOG_MAIL       mail subsystem
 *
 *        LOG_NEWS       USENET news subsystem
 *
 *        LOG_SYSLOG     messages generated internally by syslogd(8)
 *
 *        LOG_USER (default)
 *                       generic user-level messages
 *
 *        LOG_UUCP       UUCP subsystem
 *
 *    level
 *        This determines the importance of the message.  The levels are,  in  order
 *        of decreasing importance:
 *
 *        LOG_EMERG      system is unusable
 *
 *        LOG_ALERT      action must be taken immediately
 *
 *        LOG_CRIT       critical conditions
 *
 *        LOG_ERR        error conditions
 *
 *        LOG_WARNING    warning conditions
 *
 *        LOG_NOTICE     normal, but significant, condition
 *
 *        LOG_INFO       informational message
 *
 *        LOG_DEBUG      debug-level message
 *
 *        The  function  setlogmask(3)  can be used to restrict logging to specified
 *        levels only.
 *
 */

/* 如果守护进程调用chroot,须在调用chroot之前调用选项为LOG_NDELAY的openlog。它
 *它打开特殊设备文件并生成描述符,FTPD这样的守护进程中经常见到。
 */

/*
 * ===  FUNCTION  ======================================================================
 *         Name:  FILE.PID
 *  Description:  保证只运行某守护进程的一个副本
 * =====================================================================================
 */
#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<syslog.h>
#include	<errno.h>
#include	<sys/stat.h>
#include	<fcntl.h>
#include	<unistd.h>

#define LOCKFILE "/var/run/daemon.pid"
#define LOCKMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

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);
}

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); /* 注意,这里才截短为0 */
        sprintf(buf, "%ld", (long)getpid());
        write(fd, buf, strlen(buf) + 1);
        return 0;
}


/*
 * ===  FUNCTION  ======================================================================
 *  Description:  守护进程重读配置文件(使用已被屏蔽的SIGHUP信号)。
 * =====================================================================================
 */
void
reread (void)
{
}

void sigterm(int signo)
{
        syslog(LOG_INFO, "got SIGTERM; exiting");
        exit(0);
}

void sighup(int signo)
{
        syslog(LOG_iNFO, "Re-reading config file");
        reread();
}

int main(int argc, char *argv[])
{
        char *cmd;
        struct sigaction sa;

        if (cmd = strrchr(argv[0], '/') == NULL) {
                cmd = argv[0];
        } else {
                cmd++;
        }

        daemonize(cmd);

        if (already_running()) {
                syslog(LOG_ERR, "daemon already running");
                exit(1);
        }

        sa.sa_handler = sigterm;
        sigemptyset(&sa.sa_mask);
        sigaddset(&sa.sa_mask, SIGHUP);
        sa.sa_flags = 0;
        if (sigaction(SIGTERM, &sa, NULL) < 0) {
                syslog(LOG_ERR, "can't catch SIGTERM: %s", strerror(errno));
                exit(1);
        }

        sa.sa_handler = sighup;
        sigemptyset(&sa.sa_mask);
        sigaddset(&sa.sa_mask, SIGTERM);
        sa.sa_flags = 0;
        if (sigaction(SIGHUP, &sa, NULL) < ) {
                syslog(LOG_ERR, "can't catch SIGHUP: %s", streeror(errno));
                exit(1);
        }

        /*  proceed with the rest of the daemon
         */

        return 0;
}
/* 第14章:高级I/O
 */

/* 记录锁的功能是:当一个进程正在读或修改文件的某个部份时,它可以组它可以组它
 *它可以阻止其他进程修改同一文件区。UNIX系统内核根本没有文件记录这种概念。更适
 合的术语是字节范围锁(byte-range locking)。
 */


#include	<fcntl.h>

int fcntl(int filedes, int cmd, ... /* struct flock *flockptr */);
struct flock {
        short l_type;  /* F_RDLCK, F_WRLCK, F_UNLCK */
        off_t l_start;
        short l_whence;
        off_t l_len;
        pid_t l_pid;  /* only returned with F_GETLK, */
}
/* 如果 l_len为0,则表示锁的区域从其起点至最大可能便宜量为止。
 * 如果一个进程对一个文件区间已经有了一把锁,后来该进程又企图
 * 在同一文件区间再加一把锁,那么新锁将替换老锁。*/

/*
 * F_GETLK,判断由flockptr描述的锁是否会被另一把锁所排斥。如果已有一把锁,则把
 * 现存锁的信息写到flockptr所指向的结构中。如果不存在这种情况,则除了将l_type
 * 设置为F_UNLCK外,flockptr所指向结构中的其他信息不变。
 * 进程不能使用F_GETLK来测试它自己是否持有一把锁。它绝不会报告调用进程自己持有
 * 自己持有的锁。
 *
 * F_SETLK,设置由flockptr所指向的锁。如果与原有锁冲突,fcntl立即出错返回,
 * EACCES或EAGAIN。 也用来解锁(F_UNLCK)。
 *
 * F_SETLKW 阻塞加锁版本。
 */

/*
 * 如果两个进程相互等待对方持有并且锁定的资源时,则两个进程处于死锁状态。
 * 如果一个进程已经控制了文件中的一个加锁区域,然后它又试图对另一个进程控制的
 * 区域加锁,则它就会休眠。这种情况下,有死锁的可能性。
 *
 * 检测到死锁时,内核必须选择一个进程接收出错返回。不一定是哪个进程。
 */

/*
 * 关于记录锁的自动继承和释放有3条规则:
 * 1,当一个进程终止时,它所建立的锁全部释放。 任何时候关闭一个描述符时,该紧
 *    进程通过这一描述符可以引用的文件上的任何一把锁都被释放(这些锁都是该进程设置的)。

 fd1 = open(pathname, ...);
 lockfd1;
 fd2 = open(pathname, ...)
 close(fd2)               关闭fd2后,锁被释放。
 * 2,由fork产生的子进程不继承父进程所设置的锁。
 * 3,在执行exec后,新程序可以继承原执行程序的锁。但是注意:如果对一个文件描述
 * 符设置了close-on-exec标志,那么当作为exec的一部份关闭该文件描述符时,对相应
 * 文件的所有锁都被释放了。
 *
 */

/*
 * 内核必须独立于当前文件偏移量或文件尾端而记住锁。
 *
 * 异步IO,轮询,I/O多路转接(I/O multiplexing)。
 * 多路转接:先构造一张有关描述符的列表。知道这些描述符的一个已准备好进行I/O时
 * ,该函数返回。
 */

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);
// 如果三个字符集指针都为NULL,则select提供了较sleep更精确的计时器
// nfds的意思是最大描述符+1。也可将第一个参数设置为 FD_SETSIZE常量,说明了最大
// 描述符数。
// 异常状态包括:a,在网络连接上到达的带外数据,或者b 在处于数据包模式的位置伪终端上发生了某些状态。
//
// 对于读/写和异常状态,普通文件描述符总是返回准备好。

void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set); /* 声明一个fd_set字符集后,必须用FD_ZERO清除其所有位,然后设置。 */

#include <sys/select.h>

int pselect(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, const struct timespec *timeout,
            const sigset_t *sigmask);

(i)    select() uses a timeout that is a struct timeval (with seconds and
                microseconds), while pselect() uses a struct timespec (with seconds and nanoseconds).

        (ii)   select() may update the timeout argument to indicate how much time was left.
        pselect() does not change this argument.

        (iii)  select() has no sigmask argument, and behaves as pselect() called with
        NULL sigmask.

#include	<sys/time.h>
        struct timeval {
                long    tv_sec;         /* seconds */
                long    tv_usec;        /* microseconds */
        };

/* select
 * timeout == NULL 永远等待。如果捕捉到一个信号,就中断,select返回-1。如果所致
 所指定的描述符中的一个已经准备好,就返回就绪的描述符数。
 *
 * timeout->tv_sec == 0 && timeout->tv_usec == 0,完全不等待,测试后立即返回。
 * timeout->tv_sec != 0 || timeout->tv_usec != 0,等待指定时间后立即返回。
 *
 */

struct timespec {
        long    tv_sec;         /* seconds */
        long    tv_nsec;        /* nanoseconds */
};


#include	<poll.h>
int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);
int ppoll(struct pollfd *fds, nfds_t nfds,
          const struct timespec *timeout_ts, const sigset_t *sigmask);
struct pollfd {
        int   fd;         /* file descriptor */
        short events;     /* requested events */
        short revents;    /* returned events */
};



/* POLLIN There is data to read.
 *
 * POLLPRI
 *        There  is  urgent  data  to read
 * 	  不阻塞地可读高优先级数据。
 *        (e.g., out-of-band data on TCP
 *        socket; pseudo-terminal master in packet mode has seen state change
 *        in slave).
 *
 * POLLOUT
 *        Writing now will not block.
 *
 * POLLRDHUP (since Linux 2.6.17)
 *        Stream socket peer closed connection, or shut down writing half of connection.  The _GNU_SOURCE feature test macro  must  be
 *        defined (before including any header files) in order to obtain this definition.
 *
 *
 * POLLRDNORM
 *        Equivalent to POLLIN.
 *
 * POLLRDBAND
 *        Priority band data can be read (generally unused on Linux).
 *
 * POLLWRNORM
 *        Equivalent to POLLOUT.
 *
 * POLLWRBAND
 *        Priority data may be written.
 *
 * 以下三个异常状态测试,即使在events字段中没有指定这三个值,如果相应条件发生
 * ,也在revents中返回它们。
 *
 * POLLERR
 *        Error condition (output only).
 *
 * POLLHUP
 *        Hang up (output only).
 *
 * POLLNVAL
 *        Invalid request: fd not open (output only).描述符不引用一打开文件。
 */

/*
 * poll不更改events成员。
 * timeout == -1 永远等待(阻塞)
 * timeout == 0  不等待
 * timeout > 0   等待timeout毫秒。
 * */
/*
 * 异步IO,Linux请额外参考aio
 * I/O多路复用,Linux请额外参考epoll,
 * 这两方面以后会加到我的blog里
 * http://sd44.is-programmer.com
 */


#include	<sys/uio.h>
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);

struct iovec {
        void *iov_base;
        ssize_t iov_len;
}

/*
 * 存储映射I/O(Memory-mapped I/O)
 */

#include	<sys/mman.h>

void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off);
/* 若成功,返回映射区的起始地址,若出错返回MAP_FAILED。
 *
 * addr参数通常设置为0,由系统选择该映射区其实地址。
 * len是映射的字节数
 * off是要映射字节在文件中的偏移量。与addr通常应当是系统虚拟页长度的倍数。一般为0
 * prot参数可指定为 PROT_READ, PROT_WRITE, PROT_EXEC, PROT_NONE任意组合的按位
 * 或,但对指定映射区的保护要求不能超过文件open模式的访问权限。
 *
 * flag参数:
 * MAP_FIXED,返回值必须等于addr,因为不利于移植,一般不用它。
 * MAP_SHARED,指定存储操作修改映射文件,相当与对文件write。
 * MAP_PRIVATE,与MAP_SHARED必须二选一。本标志说明,对映射区的操作导致创建该映
 * 射文件的一个私有副本。一种用途是调试程序。
 *
 * 如果映射区的长度大于文件的长度,对文件大小以外的映射区内容所做的变动不会在
 * 文件中反映出来。于是我们不能用mmap将数据添加到文件中。如果要做到这一点,必
 * 须先家常该文件。
 * 如果访问映射区的某个部份,而在访问时这一部份内容实际上已不存在,则产生
 * SIGBUS信号。
 *
 * 子进程继承存储映射区,exec后的新程序补集成。
 */
int mprotect(void *addr, size_t len, int prot); /* 更改显存的映射存储区权限 */
int msync(void *addr, size_t len, int flags);   /* 成功返回0,失败返回-1 */
/*        The  flags  argument  may  have  the bits MS_ASYNC, MS_SYNC, and MS_INVALIDATE set.
 *        MS_ASYNC specifies that an update be scheduled, but the call returns immediately.
 *        MS_SYNC asks for an update and waits for it to complete.
 *        MS_INVALIDATE asks to invalidate other  mappings  of the same file
 *        (so that they can be updated with the fresh values just written).
 */

int munmap(void *addr, size_t len);  /*进程终止时或调用munmap解除映射。关闭文件描述符filedes并不解除。 */


#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<unistd.h>
#include	<fcntl.h>
#include	<sys/mman.h>
#include	<sys/stat.h>

/*
 * ===  FUNCTION  ======================================================================
 *         Name:  main
 *  Description:
 * =====================================================================================
 */
int
main ( int argc, char *argv[] )
{
        int fdin, fdout;
        void *src, *dst;

        struct stat statbuf;

        if ((fdin = open(argv[1], O_RDONLY)) < 0) {
                perror("fuck read");
        }
        if ((fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0) {
                perror("argv2 error");
        }

        if (fstat(fdin, &statbuf) < 0) {
                perror("fstat error");
        }

        lseek(fdout, statbuf.st_size - 1, SEEK_SET); /* 也可用ftruncate来设置输出文件的长度。*/
        write(fdout, "", 1);
        /* 防止对相关存储区第一次引用时产生SIGBUS信号*/

        if ((src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fdin, 0)) < 0) {
                perror("mmap fdin fucking error");
        }

        if ((dst = mmap(0, statbuf.st_size, PROT_WRITE, MAP_SHARED, fdout, 0)) < 0) {
                perror("mmap fdout error");
        }

        memcpy(dst, src, statbuf.st_size);
        msync(dst, statbuf.st_size, MS_SYNC);
        if ( munmap(src, statbuf.st_size) < 0) {
                perror("munmap error");
        }
        if ( munmap(src, statbuf.st_size) < 0) {
                perror("munmap error");
        }

        return EXIT_SUCCESS;
}				/* ----------  end of function main  ---------- */
/* 第15章:进程间通信
 */

/* 1,管道
 * 历史上,管道是半双工的。他们只能在具有公共祖先的进程之间使用。可以用
 * S_ISFIFO宏来测试是否为管道。
 * 单个进程中的管道几乎没有任何用处。通常,调用pipe的进程接着调用fork。这样就
 * 创建了从父进程到子进程的IPC通道。
 *
 * 当管道的一端被关闭后,两条规则起作用:
 * (1),当读一个写端已被关闭的管道时,在所有数据都被读取后,read返回0,以指示
 * 到达了文件结束处。
 * (2),如果写一个读端已被关闭的管道,则产生信号SIGPIPE。如果忽略该信号或者捕
 * 捉该信号,则write返回-1,errno设置为EPIPE。
 *
 * 常量PIPE_BUF规定了内核中管道缓冲区的大小。如果对管道调用write且要求写的字节
 * 数小于PIPE_BUF,则此操作不会与其他进程对同一管道的write操作穿插执行,反之则
 * 未必。
 */

#include	<unistd.h>
int pipe(int filedes[2]);

close(fd[1]);
if (fd[0] != STDIN_FILENO)
{
        dup2(fd[0], STDIN_FILENO);
        close(fd[0]);
}


#include	<stdio.h>
FILE *popen(const char *cmdstring, const char *type);
/* 若成功则返回文件指针,失败返回NULL。如果type是"r",则文件指针连接到cmd的比
 * 标准输出。如果type是"w",则文件指针连接到cmd的标准输入。 */

int pclose(FILE *fp);      /* 返回cmd的终止状态,若出错则返回-1 */
/* shell命令 ${PAGER:-more}的意思是,如果shell变量PAGER已经定义,且其值飞快噢
非空,则用其值,非则使用字符串"more"。

 * 在pclose实现中。若pclose的调用者已经为信号SIGCHLD设置了一个信号处理程序,则
 * pclose中的waitpid调用将返回一个EINTR。因为允许调用者捕捉此信号(或者任何期
 * 任何其他可能中断waitpid调用的信号),所以当waitpid被一个捕捉到的信号中断时,
 * 我们只是再次调用waitpid。
 *
 * pclose特别适用于构造简单的过滤器程序,它变换运行命令的输入或输出。
 *
 */

/*
 * 当一个程序产生某个过滤程序的输入,同时又读取该过滤程序的输出时,则改过滤程
过滤程序就成为协同进程(coprocess)。可创建两个Pipe管道来实现。
 */

#include	<sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
/*
 * 在一般情况下(没有指定O_NONBLOCK),只读open要阻塞到某个其他进程为写二打开
而打开此FIFO,只写open要阻塞到某个进程为读而打开它。

 * 如果指定了O_NONBLOCK,则只读open立即返回。但是如果没有进程已经为读而打开一
一个FIFO,那么只写open将出错返回-1,其errno是ENXIO。
 * 这样一来,如果我们要用非阻塞方式为读写而打开FIFO,则需要:
 */

unlink(fifo);
if (mkfifo(fifo, 0644) < 0)
{
        err_sys("mkfifo error");
}
if ((fdread = open(fifo, O_RDONLY | O_NONBLOCK)) < 0) /* 非阻塞打开open */
{
        err_sys("error rdopen");
}
if ((fdwrite = open(fifo, O_WRONLY)) < 0)       /* 阻塞write。如果阻塞打开open,则open write出错 */
{
        err_sys("error write");
}
clr_fl(fdread, O_NONBLOCK);                     /* 清除关闭读描述符的非阻塞 */


/*
* 类似于管道,若用write写一个尚无进程为读而打开的FIFO,则产生信号SIGPIPE。若
* 某个FIFO的最后一个写进程关闭了该FIFO,则将为该FIFO的读进程产生一个文件结束
* 文件结束标志。
*
* 常量PIPE_BUF说明了可被原子地写到FIFO的最大数据量。
*/

递送

Avatar_small
CyWee 说:
2011年8月21日 16:39

你好,我想问下这种格式的日志是什么编辑的。就是指显示一个代码的小标题

Avatar_small
CyWee 说:
2011年8月21日 16:42

打扰了额,我知道了。。起先代码高亮没激活~~

Avatar_small
星海 说:
2011年8月21日 21:50

哈哈哈,欢迎常来指导交流。。我是新手。。。。

Avatar_small
路过 说:
2011年8月23日 16:42

请教下blog的模板是怎么弄的
比较欣赏 :)

Avatar_small
星海 说:
2011年8月23日 20:04

很简单的。。进后台-》插件-》高亮。。那里启用下。

发文的时候,选“高亮代码”按钮,然后选择“折叠代码”就可以了。。。。-__-

Avatar_small
sophia 说:
2024年2月08日 04:25

Tôi rất vui khi thấy bài đăng này rất hữu ích đối với tôi vì nó chứa rất nhiều thông tin. Tôi luôn thích đọc nội dung chất lượng và điều này tôi tìm thấy trong bài đăng của bạn. Cám ơn vì đã chia sẻ. https://thabet.care/khuyen-mai-thabet/


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter