APUE学习笔记(三)
#include <stdlib.h> #include <stdio.h> #include <netdb.h> #include <arpa/inet.h> /* * === FUNCTION ====================================================================== * Name: main * Description: 注意h_aliases指向指针数组,注意netname的增减。 * ===================================================================================== */ int main ( int argc, char *argv[] ) { char **netname = NULL; struct hostent *tmp; char a[INET_ADDRSTRLEN]; while ((tmp = gethostent()) != NULL) { printf("netname: %s\n", tmp->h_name); for (netname = tmp->h_aliases; *netname != NULL; netname++) { printf("alias : %s\n", *netname); } printf("%d\n", tmp->h_addrtype); for (int i = 0; tmp->h_addr_list[i] != NULL; i++) { printf("ip %s\n", inet_ntop(AF_INET, tmp->h_addr_list[i], a, INET_ADDRSTRLEN)); } printf("\n"); } endnetent(); return EXIT_SUCCESS; } /* ---------- end of function main ---------- */
#include <stdlib.h> #include <stdio.h> #include <netdb.h> #include <arpa/inet.h> /* * === FUNCTION ====================================================================== * Name: main * Description: getnetent,此命令的实现问题在于只是读取了/etc/networks里的内 * 容,对本机地址,主机名等并不探测,目前尚不了解此命令的限定。 * ===================================================================================== */ int main ( int argc, char *argv[] ) { char *netname = NULL; struct netent *tmp; char a[INET_ADDRSTRLEN]; while ((tmp = getnetent()) != NULL) { printf("netname: %s\n", tmp->n_name); for (netname = *(tmp->n_aliases); netname != NULL; netname++) { printf("alias : %s\n", netname); } printf("%d\n", tmp->n_addrtype); int iii = tmp->n_net; printf("ip %s\n", inet_ntop(AF_INET, &iii, a, INET_ADDRSTRLEN)); } endnetent(); return EXIT_SUCCESS; } /* ---------- end of function main ---------- */
#include <stdio.h> #include <stdlib.h> #include <netdb.h> #include <arpa/inet.h> /* * === FUNCTION ====================================================================== * Name: main * Description: 只是协议名字与端口号,例如TCP IP协议等。依赖于/etc/protocols文件 * ===================================================================================== */ int main ( int argc, char *argv[] ) { char **name; struct protoent *tmp; while ((tmp = getprotoent()) != NULL) { printf("protocol name: %s\n", tmp->p_name); for (name = tmp->p_aliases; *name != NULL; name++) { printf("alias: %s \n", *name); } printf("protocol num: %d\n", tmp->p_proto); } endprotoent(); return EXIT_SUCCESS; } /* ---------- end of function main ---------- */
#include <stdio.h> #include <stdlib.h> #include <netdb.h> #include <arpa/inet.h> /* * === FUNCTION ====================================================================== * Name: main * Description: 获取服务名字,采用协议与端口号等,例如www服务,ftp服务。参照/etc/services文件获取 * ===================================================================================== */ int main ( int argc, char *argv[] ) { char **name; struct servent *tmp; while ((tmp = getservent()) != NULL) { printf("service name: %s\n", tmp->s_name); for (name = tmp->s_aliases; *name != NULL; name++) { printf("alias: %s \n", *name); } printf("port num: %d\n", ntohs(tmp->s_port)); printf("protocol name: %s\n", tmp->s_proto); printf("\n\n\n"); } endservent(); return EXIT_SUCCESS; } /* ---------- end of function main ---------- */
#include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <netdb.h> #include <arpa/inet.h> /* * === FUNCTION ====================================================================== * Name: getaddrinfo * Description: 注意错误消息要调用gai_strerror。 * hint,还有struct sockaddr到sockaddr_in的转变。且sin_port为网 * 网络字节序。 * ===================================================================================== */ int main ( int argc, char *argv[] ) { int err; char *name; struct addrinfo *ailist; struct addrinfo hint; struct sockaddr_in *sinp; char addr[INET_ADDRSTRLEN]; hint.ai_flags = AI_CANONNAME; hint.ai_family = AF_INET; hint.ai_socktype = 0; hint.ai_protocol = 0; hint.ai_canonname = NULL; hint.ai_addr = NULL; hint.ai_next = NULL; if ((err = getaddrinfo(argv[1], argv[2], &hint, &ailist)) != 0) { /* 注意返回值 */ printf("%s \n", gai_strerror(err)); exit(1); } do { printf("family: %d \n", ailist->ai_family); printf("socktype: %d \n", ailist->ai_socktype); if (ailist->ai_family == AF_INET) { sinp = (struct sockaddr_in *)ailist->ai_addr; printf("port: %d\n", ntohs(sinp->sin_port)); name = inet_ntop(AF_INET, &sinp->sin_addr, addr, INET_ADDRSTRLEN); printf("addr: %s\n", name ? name : "fuck addr"); printf("canonname %s\n", ailist->ai_canonname); } } while ((ailist = ailist->ai_next) != NULL); freeaddrinfo(ailist); return EXIT_SUCCESS; } /* ---------- end of function main ---------- */
aasdf
APUE学习笔记(二)
第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的最大数据量。 */
递送
APUE学习笔记(一)
/* 第一章: UNIX基础知识 内核的接口被称为系统调用(system call) shell是一种特殊的应用程序,它为运行其他应用程序提供了一个接口 */ #include <stdio.h> #include <dirent.h> #include <stdlib.h> int main(int argc, char *argv[]) { DIR *dp; struct dirent *dirp; if (argc != 2) { printf("fuck damn\n"); exit(1); } if ((dp = opendir(argv[1])) == NULL) { perror("fuck you"); } while ((dirp = readdir(dp)) != NULL) { printf("%s\n", dirp->d_name); } closedir(dp); return 0; } // for和跟随其后的exec两者的组合是某些操作系统所称的产生(spawn)一个新进程。
/* 1.6 程序和进程 */ #include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/wait.h> #include <stdlib.h> int main(void) { int status; char buf[30]; pid_t pid; while (fgets(buf, 29, stdin) != NULL) { if (buf[strlen(buf)-1 ] == '\n') { buf[strlen(buf) -1 ] = '\0'; } printf("virtual shell: "); if ((pid = fork()) < 0) { perror("fuck\n"); exit(126); } else if (pid == 0) { execlp(buf, buf, (char *)0); /* 子进程继承父进程的副本,获得父进程的数据空间、堆和栈的副本。exec只是用一个全新的程序替换了当前进程的正文、数据、堆和栈段。所以如果execlp成功,perror这行不会执行,但如果失败,此时仍是在副本之中,执行perror。 */ perror("couldn't exec "); exit(127); /* 退出的是子进程,不会影响整个程序 */ } if ((pid = waitpid(pid, &status, 0)) < 0) { perror("waitpid error\n"); } } return 0; } /* 在一个进程内的所有线程共享同一地址空间、文件描述符、栈以及相关的属性。需要采取同步措施以避免不一致性。 */ /* 1.10 时间值 */ // // 日历时间: // 1970/1/1以来国际标准时间(UTC)所经过的秒数累计。 time_t // // 进程时间: // 也被称为CPU时间,以时钟滴答计算。 clock_t // 用户CPU时间,是执行用户指令所使用的时间。 // 系统CPU时间,是为该进程执行内核程序所经历的时间。如:进程调用read/write,在内核执行read/write所花费的时间。 // 用户CPU时间 + 系统CPU时间 = CPU时间 // // 实际时间: // realtime ? //
/* * === FUNCTION ====================================================================== * Name: 2.5 限制 * Description: * ===================================================================================== */ /* * UNIX系统提供了以下三种限制 * (1) 编译时限制 ISO C定义的限制都是编译时限制 * (2) 不与文件或目录相关联的运行时限制(sysconf函数) * (3) * 与文件或目录相关联的限制(pathconf和fpatchconf),pathconf的返回值是基于工作目录的相对路径名的最大长度。所以,如果要确定“一些值”的话,请将工作目录改为"/"。 */ /* * UNIX系统同样提供了以下三种是否有相关选项的处理方法 * (1) 编译时选项定义在 <unistd.h>中 * (2) 与文件或目录无关联的选项确定(sysconf函数) * (3) 与文件或目录有关联的选项确定( pathconf或fpathconf) * 与文件或目录相关联的限制(pathconf和fpatchconf) */ /* 如果编译一个程序时,希望它只使用POSIX (XOPEN)的定义而不使用任何实现定义的限制,可定义 #define _POSIX_C_SOURCE 200809L 或 #define _XOPEN_SOURCE 700 * #ifdef __STDC__ .... #else 功能测试宏用于测试C编译器是否符号ISO C标准 */
/* 第3章 文件I/O */ /* * creat的不足之处在于它以只写方式打开所创建的文件。若要先写再读该文件,必须先creat, close再调用open。另外creat函数在<fcntl.h>头文件中定义 * 检测lseek返回时是否出错,应当检测其是否为 -1,因为有的系统(freebsd)lseek返回值off_t可能是负值 * 任何一个需要调用多个函数的操作都不可能是原子操作,因为在两个调用函数之间,内核有可能会临时挂起该进程。 */ /* * 原子操作: * SUS包括了XSI扩展,该扩展允许原子性地定位搜索和执行IO。 */ ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset); ssize_t pwrite(int filedes, void *buf, size_t nbytes, off_t offset); int dup(filedes); /* 总是返回当前可用文件描述符中的最小值 */ int dup2(int filedes, int filedes2); /* * 两个文件描述符指向同一个文件表项,共享同一文件状态标志以及文件偏移量。 * 但每个文件描述符都有一套文件描述符标志。新描述符的执行时关闭(close - on - exec)标志总是由dup函数清除。 * 文件描述符标志与文件状态标志有作用域的区别。前者只用于一个进程的一个文件描述符,而后者则适用于指向该给定文件表项的任何进程中的所有描述符。 */ int fsync(int filedes); /* 等待写磁盘操作结束,然后返回,确保修改过得块立即写到磁盘上 */ int fdatasync(int filedes); /* 只影响文件数据部份,但fsync同步更新文件属性 */ void sync(void); /* 只是将所有修改过的块缓冲区排入写队列,然后就返回,不等待实际写磁盘操作结束 */ /* * fcntl函数有5种功能 * 1,复制一个现有文件描述符 F_DUPFD * 2,获得/设置文件描述符标记 F_GETFD F_SETFD * 3,获得/设置文件状态标志 F_GETFL F_SETFL * 4,获得/设置异步I/O所有权 F_GETOWN F_SETOWN 接收SIGIO和SIGURG信号的进程ID(正数)或进程组ID(负数), F_GETOWN返回值为一个正的进程ID或负的进程组ID * 5,获得/设置记录锁 F_GETLK F_SETLK F_SETLKW */ // 文件状态标志里的O_RDONLY O_WRONLY O_RDWR互斥,可用O_ACCMODE获取当前读写状态
第4章: 文件和目录 #include <stat.h> int stat(char *restrict pathname, struct stat *restrict buf); int fstat(int filedes, struct stat *buf); int lstat(const char *restrict pathname, struct stat *restrict buf); /* lstat类似于stat,但是当命名的文件是一个符号链接时,lastat返回符号链接本身, * stat返回该符号链接所引用的文件信息。 */ //文件类型宏,参数均为struct stat结构中的st_mode成员 //也可用屏蔽字S_IFMT与st_stat进行逻辑与运算 // S_ISREG() /* 普通文件 */ S_ISDIR() /* 目录文件 */ S_ISCHR() /* 字符特殊文件 */ S_ISBLK() /* 块特殊文件 */ S_ISFIFO() /* 管道或FIFO */ S_ISLNK() /* 符号链接*/ S_ISSOCK() /* 套接字 */ /* * 实际用户ID和实际组ID标识我们究竟是谁。 * 有效用户ID,有效组ID以及附加组ID决定了我们文件的访问权限。 * 保存的设置用户ID和保存的设置组ID在执行一个程序时包含了有效用户ID和有效组ID的副本。 * * 通常,有效用户ID等于实际用户ID,有效组ID等于实际组ID。 * 每个文件都有一个所有者st_uid,有一个组所有者st_gid。可以在文件模式字 * (st_mode)中设置一个标志,含义是“当执行此文件时,将进程的有效用户ID设置为文 * 件的所有者ID st_uid,称为设置用户ID(set - user - ID)。st_gid类同。这两位可 * 用常量S_ISUID和S_ISGID测试。 * * 对目录的读权限,可获得在该目录中所有文件名的列表。 * 如果要在open()中指定O_TRUNC标志,必须对文件有写权限。 * 为了在一个目录中创建一个新文件或删除一个现有文件,必须对该目录具有写和执行 * 权限。删除的话,对文件本身不需要有读写权限。 * * 创建新文件时,新文件的用户ID等设置为进程的有效用户ID。 * 关于组ID,POSIX.1允许下面两种作为新文件的组ID。 * 1,新文件的组ID可以是进程的有效组ID * 2,新文件的组ID可以是它所在目录的组ID,FreeBSD和MAC总是如此。 * Linux和Solaris下,如果目录的设置组id已经设置,则将新文件的组ID设置为目录的组ID,否则将其设为进程的有效组ID。 * * access函数是按实际用户ID和实际组ID进行访问权限测试的。 * sys/stat.h 为了改变一个文件的权限位(chmod fchmod),进程的有效用户ID必须等于文件的所有者ID,或者该进程必须具有超级用户权限。 * */ S_ISUID 执行时设置用户ID S_ISGID 执行时设置组ID S_ISVTX 保存正文(粘住位) chmod fchmod无视umask值。 ls - l中,小写s,代表执行和设置用户(组)权限。大写S,代表非执行和设置用户(组)权限。 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_t owner, gid_t group); /* 更改符号链接本身 */ /* * 若_POSIX_CHOWN_RESTRICTED常量对指定的文件起作用,则: * 1,只有超级用户进程能更改该文件的用户ID。 * 2,如果进程拥有此文件(其有效用户ID等于该文件的用户ID) * 2,参数owner等于 -1或文件的用户ID,并且参数group等于进程的有效组ID或附加组ID。 * 这意味着,当_POSIX_CHOWN_RESTRICTED起作用时,你不能更改其他用户文件的用户ID。 * 如果这些函数由非超级用户进程调用,则在成功返回时,该文件的设置用户ID和设置组ID位都会被清除。 * * 在stat结构中,链接计数保存在st_nlink成员中,其基本系统数据类型是nlink_t。这种链接类型是硬件链接。 * */ /* <stdio.h>中的remove函数,对于文件,与unlink相同,对于目录与rmdir功能相同。 */ ssize_t readlink(const char *restrict pathname, char *restrict buf, size_t bufsize); /* 在buf中返回的符号链接内容不以null字符终止 */ #include <utime.h> int utime(const char *pathname, const struct utimbuf *times); /* 更改文件的访问和修改时间, * 如果times是一个空指针,则访问时间和修改时间两者都设置为当前时间。 * 执行此操作,需要进程对该文件具有写权限或者进程的有效用户ID等于文件的所有者ID。 * * 如果times是非空指针,需 * 进程的有效用户ID等于文件的所有者ID,或者进程必须是一个超级用户进程,单单对文件只具有写权限是不够的。 */ /* * 只有内核才能写目录, * 每个文件系统所在的存储设备都由其主次设备号表示。主设备号标识设备驱动程序, * 有时编码为与其通信的外设板;次设备号标识特定的子设备。在同一磁盘驱动器上的 * 各文件系统通常具有相同的主设备号,而其次设备号却不同。只有字符特殊文件和块 * 特殊文件才有st_rdev值。此值包含实际设备的设备号。 * major minor两个宏来访问主、次设备号。 */
#include <stdio.h> #include <wchar.h> int fwide(FILE *fp, int mode); /* 若mode参数值为负,fwide将试图使指定的流是字节定向的。 * 若mode参数值为正,fwide将试图使指定的流是宽定向的。 * 若mode参数值为0,fwide将不试图设置流的定向,但返回标识该流定向的值。 * 如果是宽定向,返回正值,若流是字节定向返回负值,未定向返回0值。 * fwide无出错返回。可在fwide前清除errno,从fwide返回时再检查errno值。 */ /* 术语冲洗(flush)说明标准IO缓冲区的写操作。在标准I / O库方面,flush意味着缓冲区中的内容写到磁盘上。在终端驱动程序方面,flush表示丢弃已存储在缓冲区中的数据。 */ /* setbuf, setvbuf这些函数一定要在流已被打开后调用。 */ setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size); /* _IOFBF _IOLBF _IONBF。 char *buf最好设为NULL,由标准I / O库自动为该流分配适当长度的缓冲区。*/ FILE *fopen(const char *restrict pathname, const char *restrict type); FILE *freopen(const char *restrict pathname, const char *restrict type, FILE *restrict fp); /* 在一个指定的流上打开一个指定的文件,如若该流已经打开,则先关闭该流。若该流已经定向,则freopen清除该定向。一般用于标准输入输出等重定向到文件 */ FILE *fdopen(int filedes, const char *type); /* 因为是文件描述符,此函数常用于由创建管道和网络通信通道函数返回的描述符,因为这些特殊类型的文件不能用标准I/O fopen函数打开。因为描述符已经打开,所以fdopen不能截短文件。 */ /* 当以读和写类型打开一文件时(type中有"+"号), * 如果中间没有fflush、fseek、fsetpos或rewind,则在输出的后面不能直接跟随输入。 * 如果中间没有fseek、fsetpos或rewind,或者一个输入操作没有到达尾端,则在输入操作后不能直接跟随输出。 */ /* "r+"文件必须已经存在,并且不擦除文件以前的内容,默认直接从文件开头开始覆写。 */ int getc(FILE *fp); int fgetc(FILE *fp); /* 两者的区别是,getc可被实现为宏,而fgetc不能实现为宏。这意味着,getc的参数不应当是具有副作用的表达式。可以得到fgetc的函数指针。 * 如果到达文件结尾或出错则返回EOF,为区分这种不同,必须调用ferror或feof。 */ int ferror(FILE *fp); int feof(FILE *fp); void clearerr(FILE *fp); /* 清除FILE对象中的出错标志和文件结束标志 */ int fgetpos(FILE *restrict fp, fpos_t *restrict pos); int fsetpos(FILE *fp, const fpos_t *pos); /* 注意是指针。。。fgetpos将文件位置指示器的当前值存入pos指向的对象中。在以后调用fsetpos时,可用此值重新定位至该位置。 */ char *tmpnam(char *ptr); /* 每次都产生一个不同路径名,最多调用次数是TMP_MAX,如若ptr不是NULL,则认为它指向长度至少是L_tmpnam个字符的数组。如果ptr是NULL,则每次调用,都会重写存放路径名的静态区,所以我们应当保存该路径名的副本。 */ FILE *tmpfile(void); /* 创建一个临时二进制文件,"wb+",在关闭该文件或程序结束时,将自动删除这种文件。 */ SUS XSI扩展部份 char *tempnam(const char *directory, const char *prefix); /* 如果prefix非NULL,它应当是最多包含5个字符的字符串,用其作为文件名的头几个字符。 */ int mkstemp(char *template); /* 该函数返回临时文件的打开文件描述符,可读写。mkstemp创建的临时文件不会自动被删除。 template是一个路径名,例如 /tmp/XXXXXX。如mkstemp成功返回会自动修改template字符串以反映临时文件的名字。 */
/* 第6章:系统数据文件和信息 */ //vipw命令允许管理员使用该命令编辑口令文件。 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. */ }; #include <pwd.h> struct passwd *getpwuid(uid_t uid); struct passwd *getpwnam(const char *name); struct passwd *getpwent(void); /* 返回口令文件的下一项 */ void setpwent(void); /* 反绕文件,最好在程序开始处调用一次,保护性措施。*/ void endpwent(void); /* 使用getpwent查看完口令文件后,一定要调用endpwent关闭这些文件。 */ #include <shadow.h> struct spwd { char *sp_namp; /* Login name. */ char *sp_pwdp; /* Encrypted password. */ long int sp_lstchg; /* Date of last change. */ long int sp_min; /* Minimum number of days between changes. */ long int sp_max; /* Maximum number of days between changes. */ long int sp_warn; /* Number of days to warn user to change the password. */ long int sp_inact; /* Number of days the account may be inactive. */ long int sp_expire; /* Number of days since 1970-01-01 until account expires. */ unsigned long int sp_flag; /* Reserved. */ }; struct spdw *getspnam(const char *name); struct spdw *getspent(void); void setspent(void); void endspent(void); struct group { char *gr_name; /* Group name. */ char *gr_passwd; /* Password. */ __gid_t gr_gid; /* Group ID. */ char **gr_mem; /* Member list. */ }; #include <grp.h> struct group *getgrgid(gid_t gid); struct group *getgrnam(const char *name); struct group *getgrent(void); void setgrent(void); void endgrent(void); /* 为了获取和设置附加组ID,提供了以下几个函数 */ #include <unistd.h> #include <grp.h> // getgroups将各附加组ID填写到数组grouplist中,元素最多为gidsetsize个。实际填写到数组中的附加组id数目由函数返回,如果出错返回-1。 // 作为一个特例,若gidsetseize为0,函数只返回附加组id数目,对数组grouplist不作修改。这样可以确定数组grouplist的长度。 int getgroups(int gidsetsize, gid_t grouplist[]); int setgroups(int ngroups, const gid_t grouplist[]); /* 特权操作,ngroups的值不能大于NGROUP_MAX */ int initgroups(const char *username, gid_t basegid); /* 特权操作,login用到,这两个函数还需要进一步了解。 */ // freebsd中,阴影文件是/etc/master.passwd。 utmp文件记录当前登录进系统的各个用户;wtmp文件,跟踪各个登录和注销事件。last命令读wtmp文件并打印记录。 #include <time.h> time_t time(time_t *calptr); int gettimeofday(struct timeval *restrict tp, void *restrict tzp); /* 总是返回0值,SUS中,tzp唯一合法值为NULL。 */ struct timeval { time_t tv_sec; long tv_usec; /* 微秒 */ } struct tm { int tm_sec; /* Seconds. [0-60] (1 leap second) */ int tm_min; /* Minutes. [0-59] */ int tm_hour; /* Hours. [0-23] */ int tm_mday; /* Day. [1-31] */ int tm_mon; /* Month. [0-11] */ int tm_year; /* Year - 1900. */ int tm_wday; /* Day of week. [0-6] */ int tm_yday; /* Days in year.[0-365] */ int tm_isdst; /* DST. [-1/0/1]*/ #ifdef __USE_BSD long int tm_gmtoff; /* Seconds east of UTC. */ __const char *tm_zone; /* Timezone abbreviation. */ #else long int __tm_gmtoff; /* Seconds east of UTC. */ __const char *__tm_zone; /* Timezone abbreviation. */ #endif }; struct tm *gmtime(const time_t *calptr); /* TZ,localtime */ struct tm *localtime(const time_t *calptr); /* UTC time */ time_t mktime(struct tm *tmptr); /* TZ,local time */ char *asctime(const struct tm *tmptr); char *ctime(const time_t *calptr); /* TZ,localtime */ size_t strftime(char *restrict buf, size_t maxsize, const char *restrict format, const struct tm *restrict tmptr); /* TZ,localtime */
/* 第7章:进程环境 */ /* 有8种方式使进程终止(termination),其中5种为正常终止,他们是 * 1,从main返回 return * 2,调用exit * 3, 调用_exit或_Exit ISO C定义_Exit,POSIX.1说明_exit,UNIX系统中两者同义 * 4, 最后一个线程从其启动例程返回 进程终止状态为0,与线程返回值无关。 * 5,最后一个线程调用pthread_exit 这样进程终止状态总是0。 * 异常终止有3种方式,他们是 * 1,调用abort SIGABRT信号,下一种异常终止的特例 * 2,接到一个信号并终止 * 3,最后一个线程对取消请求做出相应。 * * 内核使程序执行的唯一方法是调用一个exec函数。 * 进程自!!愿!!!终止的唯一方法是显式或隐式的(通过exit)调用_exit或_Exit。 * 不管进程如何终止,最后都会执行内核中同一段代码。为相应进程关闭所有打开文件 关闭所有打开文件描述符,释放它所使用的存储器等。 */ #include <stdlib.h> int atexit(void (*func)(void)); extern char **environ; int putenv(char *str); int setenv(const char *name, const char *value, int rewrite); /* 若rewrite非0,则首先删除其现有定义。若rewrite为0,则不删除其现有定义。 */ /* putenv和setenv差别: * Linux和Solaris中,putenv实现将传送给它的字符串地址作为参数直接放入环境表中。 * 如果字符串存放在栈中,且作为参数,函数返回时,栈销毁,可能造成错误。 */ #include <setjmp.h> int setjmp(jmp_buf env); /* 若直接调用则返回0,若从longjmp调用返回则返回longjmp参数里的val值 */ void longjmp(jmp_buf env, int val); /* 声明为全局或静态变量的值在执行longjmp时保持不变。volatile也可。 */ /* * 全局、静态和易失变量不受编译器优化的影响。 * getlimit、setrlimit具体参数请参考manpage。 * * 更改资源限制时,遵循下列三条原则: * 1,任何一个进程都可将一个软限制值改为小于或等于其硬限制值。 * 2,任何一个进程都可以降低其硬限制值,但它必须大于或等于其软限制值。对于普通用户而言这是不可逆的。 * 3,只有超级用户进程可以提高其硬限制值。 * */
/* 第8章:进程控制 */ /* 父进程设置的文件锁不会被子进程继承 * 子进程的未处理的闹钟(alarm)被清除 * 子进程的未处理信号集设置为空集 */ /* * vfork创建子进程的目的是exec一个新程序,并不将父进程的地址空间完全复制到子进程中。 * 子进程调用exec或exit之前,它在父进程的地址空间中运行。 * vfork区别于fork:vfork确保子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。 * 如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。 */ /* _exit并不执行标准I/O缓冲的冲洗操作。 * * vfork中,如果子进程调用exit,则该程序的输出是不确定的,它依赖于标准I/O库的实现。 * 情况1:exit实现冲洗所有标准I/O流,如果这是函数唯一的动作,那么输出与 * 子进程调用_exit完全相同。 * 情况2:如果该实现也关闭标准I/O流,那么表示标准输出FILE对象的相关存储区会被清0。 * 因为子进程借用了父进程的地址空间,所以父进程恢复运行并调用printf时,也就不会产生任何输出。 * * * * * 大多数exit的现代实现不再关闭流,因为进程即将终止,那时内核将关闭在进程中哦 进程中已打开的所有文件描述符,在库中关闭他们。比如Linux 课后题8.1我不明白,为什么fclose(stdout)可行,而close(STDOUT_FILENO)不可行。 */ /* 对于父进程已经终止的所有进程,他们的父进程都改变为init进程。我们称这些进程 * 为孤儿进程,由init进程领养。 * 一个已经终止,但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息, * 释放它仍占用的资源)的进程被称为僵死进程。 */ #include <sys/wait.h pid_t wait(int *statloc); pid_t waitpid(pid_t pid, int *statloc, int options); /* 如果成功返回则返回进程ID,0。如果出错则返回-1。 * pid = -1,等待任一子进程。 * pid > 0,等待其进程ID与pid相等的子进程。 * pid = 0,等待其组ID等于调用进程组ID的任一子进程。 * pid = -1,等待其组ID等于pid绝对值的任一子进程。 * * options常量: * 1,WCONTINUED 若实现作业控制,如果PID指定的任意子进程在暂停后已经继 已经继续,但其状态尚未报告,则返回其状态。 * 2,WNOHANG 若由PID指定的子进程并不是立即可用的,则waitpid不阻塞, * 其返回值为0。 * 3,WUNTRACED 若某实现支持作业控制,而由pid指定的任一子进程已处于暂 *暂停状态,并且自暂停状态以来还未报告过,则返回其状态。 * * */ WIFEXITED(status) /* 若为正常终止子进程返回的状态则为真, 可执行WEXITSTATUS(status)来获取子进程传给exit族参数的低8位 */ WIFSIGNALED(status) /* 异常终止状态宏测试。WTERMSIG(status) WCOREDUMP(status) */ WIFSTOPPED(status) /* 暂停子进程返回的状态,WSTOPSIG(status)*/ WIFCONTINUED(status) /* 作业控制暂停后已经继续的子进程反回状态。 */ #include <sys/types.h> #include <sys/time.h> #include <sys/resource.h> #include <sys/wait.h> pid_t wait3(int *status, int options, struct rusage *rusage); pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage); /* 如果成功则返回进程ID,如果出错则返回-1。 * rusage为资源统计信息,包括用户CPU时间总量、系统CPU时间总量、页面出错次数, * 接受到的信号次数等。有关细节参阅 getrusage(2)手册页。 */ /* race condition(竞争条件),polling(轮询), */ #include <unistd.h> extern char **environ; int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ..., char * const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execve(const char *path, char *const argv[], char * const envp[]); /* 只有execve是内核系统调用,其它都是库函数 */ /* 若函数执行出错返回-1,否则不返回值。 * * execl、execlp、execle着三个函数表示命令行参数的一般方法是: * char *arg0, char *arg1, ..., (char *) 0; * 如果用常数0表示一个空指针,必须将它强制转换为一个字符指针,否则将它解释为 *整形参数。 * * 字母p表示该函数取filename为参数,并且用PATH环境变量寻找可执行文件。 * 字母l表示该函数取一个参数表。 * 字母v表示该函数取一个argv[]矢量。 * 字母e表示该函数取envp[]数组,而不是使用当前环境。 */ /* * * 执行新程序的进程还保持了进程的下列特征: * 进程ID和父进程ID,实际用户ID和实际组ID,闹钟尚余留时间,当前工作目录,文件 文件创建屏蔽字,文件锁,进程信号屏蔽,未处理信号,资源限制,tms_utime,tms_stime,tms_cutime,tms_cstime值等。 * 除非对设置了 打开文件描述符的close-on-exec标志,否则系统默认在执行exec后仍保持这种描述符打开。 * 有效ID是否改变则取决于所执行程序文件的设置用户ID位和设置组ID位是否设置。 * * All process attributes are preserved during an execve(), except the following: * The dispositions of any signals that are being caught are reset to the default (signal(7)). * Any alternate signal stack is not preserved (sigaltstack(2)). * Memory mappings are not preserved (mmap(2)). * Attached System V shared memory segments are detached (shmat(2)). * POSIX shared memory regions are unmapped (shm_open(3)). * Open POSIX message queue descriptors are closed (mq_overview(7)). * Any open POSIX named semaphores are closed (sem_overview(7)). * POSIX timers are not preserved (timer_create(2)). * Any open directory streams are closed (opendir(3)). * Memory locks are not preserved (mlock(2), mlockall(2)). * Exit handlers are not preserved (atexit(3), on_exit(3)). * The floating-point environment is reset to the default (see fenv(3)). The process attributes in the preceding list are all specified in POSIX.1-2001. The following Linux-specific process attributes are also not preserved during an execve(): * The prctl(2) PR_SET_DUMPABLE flag is set, unless a set-user-ID or set-group ID program is being executed, in which case it is cleared. * The prctl(2) PR_SET_KEEPCAPS flag is cleared. * The process name, as set by prctl(2) PR_SET_NAME (and displayed by ps -o comm), is reset to the name of the new executable file. * The termination signal is reset to SIGCHLD (see clone(2)). */ #include <unistd.h> int setuid(uid_t uid); int setgid(uid_t uid); int seteuid(uid_t uid); int setegid(uid_t uid); /* 改变用户ID的规则: * 若用户具有超级用户特权,则setuid函数将实际用户ID,有效用户ID以及保存的设置用户ID均设置为uid。 * 若进程没有超级用户特权,但是uid等于实际用户ID或保存的设置用户id,则setuid将有效用户ID设置为uid,不改变实际用户ID和保存的设置用户ID。 * 如果以上两个条件都不满足,则将errno设置为EPERM,并返回-1。 */ /* 关于内核维护的三个用户ID,还要注意以下几点 * 只有超级用户进程可以改变实际ID。 * 仅当对程序文件设置了设置用户ID位时,exec函数才会设置有效用户ID。结合以上, * 任何时候都可以调用setuid,将有效用户ID设置为实际用户ID或设置用户ID。不能将 * 有效用户ID设置为任意随机值。*/ int setreuid(uid_t ruid, uid_t euid); int setregid(gid_t rgid, gid_t egid); /* 如果其中任一参数的值为-1,则表示相应的ID应当保持不变。 * 一个非特权用户总能交换实际用户ID和有效用户ID。 */ /* 当被执行文件是解释器文件时,argv[0]是该解释器的pathname,argv[1]是解释器中 * 的可选参数,其余参数是exec后的pathname,参数等*/ system()在实现中调用了fork, exec, waitpid,因此有三种返回值。 /* 1,如果fork失败或者waitpid返回除EINTR之外的出错,则返回 -1,而且errno中设置错误类型值。 * 2,如果exec失败,则其返回值如同shell执行了exit(127)一样。 * 3,所有三个函数都执行成功,并且system的返回值是shell的终止状态,其格式已在waitpid中说明。 * * shell的 -c选项告诉shell程序取下一个命令行参数作为标准输入,shell将对字符串 * 进行语法分析,将它们分成命令行参数。 * 调用_exit是为了防止任一标准IO缓冲区(由父进程复制到子进程)在子进程中被冲洗。 */ /* apue英文版P248,中文版P202中介绍说在设置用户ID程序中调用system,执行其他程 * 序,存在安全性漏洞,导致超级用户权限保存下来的问题注释中说,bash v2中不能运 * 行此实例,经ubuntu 11.04 bash4.28实测,否定,不知原因???*/ /* 如果一个进程正以特殊的权限运行,它又想生成另一个进程执行另一个程序,则它应 *应当直接fork和exec,且在fork后、exec之前改回普通用户权限。设置用户ID和设置组 ID程序绝不应当调用system函数*/ /* 进程时间: * * 任一进程都可调用times函数以获得它自己及终止子进程的墙上时钟时间、用户CPU时 *时间、系统CPU时间*/ #include <sys/times.h> struct tms { clock_t tms_utime; /* user CPU time */ clock_t tms_stime; /* system CPU time */ clock_t tms_cutime; /* user CPU time, terminated children */ clock_t tms_cstime; /* system CPU time,terminated children */ }; clock_t times(struct tms *buf);
/* 第9章:进程关系 */ /* 每个进程都有一个父进程(初始的内核进程并无父进程,也可以说其父进程就是自己 * BSD终端登录,init进程读取/etc/ttys * Linux终端登录,init进程读取/etc/inittab,但upstart(ubuntu) init进程读取 * /etc/init/tty*文件 * * 传统的UNIX系统用户身份验证过程如下: * 系统自举时,内核创建进程为1的init进程,读取终端配置文件,对每一个允许登录的 * 终端设备,init以空环境调用一次fork,它所生成的子进程执行(exec)getty程序。 * * getty为终端设备调用open函数,以读写方式打开终端。输出login: 之类的信息。 * 然后它以类似于下面的方式调用login程序。 * execle("/bin/login", "login", "-p", username, (char *)0, envp); * getty以终端名和在gettytab中说明的字符串为login创建一个环境(envp)。 * -p通知login保留传送给它的环境。 * * login调用getpwnam取得相应用户的口令文件登录项,然后getpass,getpass再调用 * crypt将用户键入的口令加密与该用户在阴影文件口令中登录项的pw_passwd字段相比相比较。 * * 现代UNIX都支持PAM(可插入式身份验证模块)。 * */ /* 如果用户登录正确,login将执行如下工作: * 当前工作目录更改为用户的起始目录(chdir) * 调用chown改变该终端的所有权,使登录用户称为它的所有者 * 将对该终端设备的访问权改变成用户读和写 * 调用setgid及initgroups设置进程的组ID * 用login所得到的所有信息初始化环境 * login进程改变为登录用户的用户ID(setuid)并调用该用户的登录shell,如下:*/ execl("bin/sh", "-sh", (char *)0); /* argv[0]的第一个字符'-'是一个标志,表示该shell被调用为登录shell。 */ #include <unistd.h> pid_t getpgrp(void); pid_t getpgid(pid_t pid); /* 若pid为0,则返回调用进程的进程组ID。 */ int setpgid(pid_t pid, pid_t pgid); /* setpgid函数将pid进程的进程组ID改为pgid。如果两个参数相等,则pid变成进程组组长。 * 如果pid是0,则使用调用者的进程ID。如果pgid是0,则由pid指定的进程ID将用作进程组ID。 * 一个进程只能为它自己或它的子进程设置进程组ID。*/ pid_t setsid(void); /* 如果调用此函数的进程不是一个进程组的组长,则此就创建一个进程 * 结果将发生以下三件事: * 该进程变成会话首进程 * 该进程成为一个进程组的组长进程。 * 该进程没有控制终端。如果在setsid之前该进程有一个控制终端,那么这种联系会切 那么这种联系会切断。 */ pid_t getsid(pid_t pid); /* 如果pid是0,getsid返回调用进程的会话首进程的进程组ID。 * Linux之外有些系统如果pid不属于调用者所在的会话,则产生EPRM错误。*/ /* 建立与控制终端连接的会话首进程被称为控制进程(controlling process)。 * 一个会话中的几个进程组可分成一个前台进程组 (foreground process group),以及 * 一个或几个后台进程组 (background process group)。 * 无论何时键入终端的中断键 (Ctrl+C) 或终端的退出键 (Ctrl+\),就会将信号发送哦 发送给前台进程组的所有进程。 */ pid_t tcgetpgrp(int filedes); int tcsetpgrp(int filedes, pid_t pgrpid); /* pgrpid的值应当是在同一会话中的一个进程组的ID * filedes必须引用该会话的控制终端。*/ pid_t tcgetsid(int filedes);
/* 第10章:信号 */ /* 信号是一种软件中断,它提供了一种处理异步事件的方法。 * kill函数对信号编号0有特殊的应用,POSIX.1将此种信号编号值称为空信号。 * 如果signo参数是0,则kill仍执行正常的错误检查,但不发送信号,常被用来确定一 确定一个特定进程是否仍旧存在。如果并不存在,则kill返回-1,并将errno设为 ESRCH。 * kill(2)函数可将信号发送给另一个进程或进程组。但!接收信号进程和发送信号进程 * 的所有者必须相同,或者发送信号进程的所有者为超级用户。SIGKILL和SIGSTOP信号 * 不能被忽略或者被捕捉。 * signal函数由ISO C定义,因为ISO C不涉及多线程、进程组以及终端I/O,所以它对信 * 号的定义不明确,最好使用sigaction函数。 */ #include <signal.h> void (*signal(int signum, void (*func)(int)))(int); /* 若成功返回信号以前的处理配置(之前的信号处理程序的指针),若出错则返回SIG_ERR。 * * 子进程继承父进程的信号处理方式,exec函数将原先设置要捕捉的函数都更改为它们的默认动作。 */ if (signal(SIGINT, SIG_IGN) != SIG_IGN) { signal(SIGINT, sig_int); /* 仅当信号当前未被忽略时,进程才会捕捉它们。 */ } /* 早期UNIX的一个特性是:如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信 * 信号,则该系统调用就被中断不再继续执行。该系统调用返回出错,其errno被设置为EINTR。 * * 早期UNIX版本中的一个问题是:在进程每次接收到信号并对其进行处理时,随即将信 *信号动作复位为默认值。 * * 必须区分系统调用和函数!当捕捉到某个信号时,被中断的是内核中的系统调用。 * * 低速系统调用包括: * 1,在读某些类型的文件时(管道,终端设备,网络设备)时,如果数据并不存在则可 * 可能会使调用者永远阻塞。 * 2,在写这些类型的文件时,如果不能立即接受这些数据,也可能会使调用者永远阻塞。 * 3,打开某些类型的文件,在某中条件发生之前(如打开终端设备,它要等待直到调制 调制解调器应答了电话)。 * 4,pause和wait函数 * 5,某些ioctl操作和某些进程间通信函数。 * * FreeBSD,Linux,Mac OS的默认方式是重启动由信号中断的系统调用,Solaris 9默认EINTR。 * */ /* * 不可重入函数原因: * 1,已知它们使用静态数据结构。 * 2,它们调用malloc 或 free * 3,他们是标准I / O函数。 * 各种wait函数都能改变errno值。 */ /* 如果特地设置信号SIGCLD SIGCHLD的捕捉配置为SIG_IGN,则调用进程的子进程不产生 * 僵死进程。(FreeBSD除外) * * 在产生了信号时,内核通常在进程表中设置一个某种形式的标志——递送。 * 在信号产生 (generation) 和递送 (delivery)之间的时间间隔内,我们称信号是味觉 未决的 (pending) 。 * * 内核在递送一个原来被阻塞的信号给进程时(而不是在产生该信号时),才决定对他 对它的处理方式。 * POSIX.1的Rationale建议:在其他信号之前递送与进程当前状态有关的信号,如SIGSEGV。 * */ #include <signal.h> int kill(pid_t pid, int signo); int raise(int signo); /* * kill的pid参数: * pid > 0 将该信号发送给进程ID为pid的进程 * pid = 0 将信号发送给与发送进程属于同一进程组的所有进程。但不包括实现 * 定义的系统进程集。(包括内核进程和 init(pid 1) ) * pid == -1 将该信号发送给发送进程有权限向他们发送信号的系统上的所有进程。 * pid < 0 发送给其进程组ID等于pid的绝对值。 * * SIGCONT例外,进程可将它发送给属于同一会话的任何进程。 * */ #include <unistd.h> unsigned int alarm(unsigned int seconds); /* 返回值为0或以前设置的闹钟时间的余留秒数。 * 每个进程只能有一个闹钟时间。 */ int pause(void); /* 只有执行了一个信号处理程序并返回时才返回,值为-1,并将errno设置为EINTR。*/ /* 信号处理程序中,如果该信号中断了其他信号处理程序,且使用longjump避免信号与 * pause等的竞争条件,则有可能会提早终止其他信号处理程序。 * * 如果要对I/O操作设置时间限制,则可使用longjmp或者使用select和poll函数。 */ sigset_t set; *set |= 1 << (signo - 1); /* sigaddset的一种实现 */ *set &= ~(1 << (signo - 1)); /* sigdelset的一种实现 */ int sigprocmask(int how, sigset_t *restrict set, sigset_t *restrict oset); /* SIG_BLOCK ,当前信号屏蔽字与set指向信号集的屏蔽字的并集。 * SIG_UNBLOCK,当前信号屏蔽字与 set指向信号补集 的并集。 * 而SIG_SETMASK,进程的屏蔽字将被set所指向的信号集的值代替。 * sigprocmask仅为单线程的进程定义。 */
#include <signal.h> int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict buf); struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); }; /* sa_mask字段说明了一个信号集,在调用信号捕捉函数之前,这一信号集要加到进程的 进程的信号屏蔽字中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字复原。 * SA_NOCLDSTOP If signum is SIGCHLD, do not receive notification when child processes stop (i.e., when they receive one of SIGSTOP, SIGTSTP, SIGTTIN or SIGTTOU) or resume (i.e., they receive SIGCONT) (see wait(2)). This flag is only meaningful when establishing a handler for SIGCHLD. 进程停止时不产生SIGCHLD信号,进程终止时仍然产生SIGCHLD信号。 SA_NOCLDWAIT (since Linux 2.6) If signum is SIGCHLD, do not transform children into zombies when they terminate. See also waitpid(2). This flag is only meaningful when establishing a handler for SIGCHLD, or when setting that signal's disposition to SIG_DFL. If the SA_NOCLDWAIT flag is set when establishing a handler for SIGCHLD, POSIX.1 leaves it unspecified whether a SIGCHLD signal is generated when a child process terminates. On Linux, a SIGCHLD signal is generated in this case; on some other implementa- tions, it is not. SA_NODEFER Do not prevent(阻塞) the signal from being received from within its own signal handler. This flag is only meaningful when establish- ing a signal handler. SA_NOMASK is an obsolete, nonstandard synonym for this flag. 对应于早期的不可靠信号。 SA_ONSTACK Call the signal handler on an alternate signal stack provided by sigaltstack(2). If an alternate stack is not available, the default stack will be used. This flag is only meaningful when establishing a signal handler. SA_RESETHAND Restore the signal action to the default state once the signal handler has been called. This flag is only meaningful when establishing a signal handler. SA_ONESHOT is an obsolete, nonstandard synonym for this flag. 对应于早期不可靠信号。 SA_RESTART Provide behavior compatible with BSD signal semantics by making certain system calls restartable across signals. This flag is only meaningful when establishing a signal handler. See signal(7) for a discussion of system call restarting. XSI扩展规定,除非说明了SG_RESTART标志,否则sigaction函数不再重启动被中断的系统调用。 SA_SIGINFO (since Linux 2.2) The signal handler takes 3 arguments, not one. In this case, sa_sigaction should be set instead of sa_handler. This flag is only meaningful when establishing a signal handler. 系统支持实时扩展时,由SA_SIGINFO设置的sa_sigaction信号处理程序将导致信号可靠的排队。 */ /* Freebsd和Mac OS中,setjmp和longjmp函数保存和恢复信号屏蔽字,但其中的_setjmp * 和_longjmp与Linux和Solaris 9中的setjmp,longjmp不执行此操作。 */ /* 在信号处理程序中应当使用sigsetjmp与siglongjmp */ #include <setjmp.h> int sigsetjmp(sigjmp_buf env, int savemask); /* 直接调用返回0,从siglongjmp返回则返回非0值,如果savemask非0,则sigsetjmp * 在env中保存进程的当前信号屏蔽字。 * 调用siglongjmp时,如果sigsetjmp已经保存了env,则siglongjmp从其中恢复保存的信 保存的信号屏蔽字。 */ int siglongjmp(sigjmp_buf env, int val); /* 程序清单10-14 * 在信号处理程序中调用siglongjmp,应当使用如下技术: * 仅在调用sigsetjmp后才将变量canjump设置为非0值。 * 在信号处理程序中检测次变量,仅当它为非0值时才调用siglongjmp。 * 这提供了一种保护机制,使得在jmbuf(跳转缓冲)尚未由sigsetjmp初始化时,防止 * 调用信号处理程序。 * * 数据类型 sig_atomic_t,由ISO C标准定义,在写这种类型的变量时不会被中断。 * 这意味着在具有虚拟存储器的系统上这种变量不会跨越页边界,可用一条机器指令。 * 这种类型的变量总是包括ISO类型修饰符volatile,其原因是:该变量将由两个不同哦 两个不同的控制线程——main函数和异步执行的信号处理程序访问。 */ int sigsuspend(const sigset_t *sigmask); /* 始终返回-1,errno设置为EINTR */ #include <stdlib.h> void abort(void); /* POSIX.1要求如果abort调用终止进程,则它对所有打开的标准I/O流的效果应当与进程终止前对每个流调用fclose相同。 POSIX.1也说明abort并不理会进程对此信号的阻塞和忽略。*/ /* POSIX.1说明:若父进程捕捉SIGCHLD信号,那么正在执行system函数时,应当阻塞对 * 父进程递送SIGCHLD信号。否则,当system函数创建的子进程结束时,system的调用者 * 可能错误认为,它自己的一个子进程已经结束了。 */ /* system函数的实现需要注意:sigation忽略掉父进程的SIGINT,SIGQUIT信号, * sigprocmask block掉SIGCHLD,子进程在exec前恢复SIGINT,SIGQUIT默认处理程序, * 恢复原来的mask值。然后父进程调用wait系列函数获取状态,恢复SIGINT,SIGQUIT, * mask值,并返回子进程退出状态。 * * system返回值是shell终止状态,但shell的终止状态并不总是执行命令字符串进程的 * 终止状态。bash中有意个特性,如果用退出键或其他信号杀死执行命令进程后,其中 *终止状态是128+信号编号。该信号终止了正在执行的命令。 * 例如 sh -c "sleep 30",后,用ctrl+c中断掉,返回状态为130。 * * 当shell本身异常终止时,system的返回值才报告一个异常终止。 */ /* POSIX.1认为有6个信号与作业控制有关。 * * SIGCHLD 子进程已停止或终止。 * SIGCONT 如果进程已停止,则使其继续运行。 * SIGSTOP 停止信号(不能被捕捉或忽略)。 * SIGTSTP 交互式停止信号。 * SIGTTIN 后台进程组成员读控制终端。当输入Ctrl+z时(挂起),SIGTSTP背诵 被送至前台进程组的所有进程。 * SIGTTOU 后台进程组成员写到控制终端 * * 除SIGCHLD以外,大多数应用程序并不处理这些信号,交互式SHELL通常做所有工作。 * 作业控制信号有某种交互作用。当对一个劲成产生四种停止信号(SIGTSTP, SIGSTOP, * SIGTTIN或SIGTTOU)中的任意一种时,对同一进程的任一未决SIGCONT信号将被丢弃。 * 与此类似,对一个进程产生SIGCONT信号时,对同一进程的任一未决停止信号将被丢弃 */ /* 某些系统提供数组extern char *sys_siglist[]; * 数组下表是信号编号,给出一个指向信号字符串名字的指针。 */ #include <signal.h> void psignal(int signo, const char *msg); /* 类似于perror */ #include <string.h> char *strsignal(int signo); /* 返回该信号说明的字符串 */
APUE中,error.c几个错误处理函数的不同
#include "apue.h" #include <errno.h> /* for definition of errno */ #include <stdarg.h> /* ISO C variable aruments */ static void err_doit(int, int, const char *, va_list); /* err_ret非致命错误输出 与err_sys相比,没有exit(),并不退出函数。 相同的是都调用stderror(errno)打印详细出错信息。 可变参数列表中多为vsnprintf等打印参数的参数列表。 */ void err_ret(const char *fmt, ...) { va_list ap; va_start(ap, fmt); err_doit(1, errno, fmt, ap); va_end(ap); } /* * Fatal error related to a system call. * Print a message and terminate. */ void err_sys(const char *fmt, ...) { va_list ap; va_start(ap, fmt); err_doit(1, errno, fmt, ap); va_end(ap); exit(1); /* 退出 */ } /* * Fatal error unrelated to a system call. * Error code passed as explict parameter. * Print a message and terminate. * 不调用系统打印出错信息。 */ void err_exit(int error, const char *fmt, ...) { va_list ap; va_start(ap, fmt); err_doit(1, error, fmt, ap); va_end(ap); exit(1); } /* * 不调用系统出错信息,但退出程序 * Fatal error related to a system call. * Print a message, dump core, and terminate. */ void err_dump(const char *fmt, ...) { va_list ap; va_start(ap, fmt); err_doit(1, errno, fmt, ap); va_end(ap); abort(); /* dump core and terminate */ exit(1); /* shouldn't get here */ } /* * Nonfatal error unrelated to a system call. * Print a message and return. */ void err_msg(const char *fmt, ...) { va_list ap; va_start(ap, fmt); err_doit(0, 0, fmt, ap); va_end(ap); } /* * 不调用系统出错信息,但退出程序 * Print a message and terminate. */ void err_quit(const char *fmt, ...) { va_list ap; va_start(ap, fmt); err_doit(0, 0, fmt, ap); va_end(ap); exit(1); } /* * Print a message and return to caller. * Caller specifies "errnoflag". */ static void err_doit(int errnoflag, int error, const char *fmt, va_list ap) { char buf[MAXLINE]; vsnprintf(buf, MAXLINE, fmt, ap); /* vsnprintf与snprintf区别在于vsnprintf调用可变参数列表 */ if (errnoflag) /* 在buf后追加系统出错信息 */ snprintf(buf+strlen(buf), MAXLINE-strlen(buf), ": %s", strerror(error)); strcat(buf, "\n"); fflush(stdout); /* in case stdout and stderr are the same */ fputs(buf, stderr); fflush(NULL); /* flushes all stdio output streams */ }