
目录一. 会话session1.1 进程组1.1.1 进程组1.1.2 getpgid()1.2 会话1.2.1 概念1.2.2 getsid()1.2.3 创建会话1、注意事项2、setsid()函数二. 守护进程daemon2.1 概念2.2 守护进程的创建2.3 用到的函数2.3.1 chdir2.3.2 umask2.4 示例代码12.5 示例代码2三. 系统日志3.1 系统日志3.2 三个函数3.2.1 openlog3.2.2 syslog3.2.3 closelog3.3 修改后的示例代码2一. 会话session1.1 进程组1.1.1 进程组1、进程组也称之为作业。BSD于1980年前后向Unix中增加的一个新特性。代表一个或多个进程的集合。每个进程都属于一个进程组。eg当父进程fork出子进程时这个子进程就和父进程属于同一进程组他们的进程组ID(PGID)相同都是父进程的PID父进程就是组长进程。当父进程创建子进程的时候默认子进程与父进程属于同一进程组。进程组ID第一个进程ID(组长进程)。所以组长进程标识其进程组ID其进程ID示例代码int main() { pid_t pid; pid fork(); if(pid 0){ wait(NULL); } else if(pid 0){ while(1){ printf(child\n); sleep(1); } } exit(0); }结果可以看到子进程与父进程同属于一个进程组2、在waitpid函数和kill函数的参数中都曾使用到。操作系统设计的进程组的概念是为了简化对多个进程的管理。waitpid函数中第一个参数传0代表等待与调用者同属于同一进程组的任何进程。kill函数中第一个参数传0代表发生信号给与调用者同属于同一进程组的所有进程同时可以使用kill -SIGKILL -进程组ID(负的)来将整个进程组内的进程全部杀死。3、组长进程可以创建一个进程组创建该进程组中的进程然后终止。只要进程组中有一个进程存在进程组就存在与组长进程是否终止无关。4、进程组生存期进程组创建到最后一个进程离开(终止或转移到另一个进程组)。一个进程可以为自己或子进程设置进程组ID1.1.2 getpgid()作用获取指定进程的进程组ID即PGID参数pid指定进程若传入0则返回调用者的进程组ID返回值成功返回进程组ID失败返回-1并且设置errno1.2 会话1.2.1 概念会话多个进程组的集合1.2.2 getsid()作用返回指定进程的会话ID参数pid指定进程 如果pid传入0则返回调用者的会话ID(SID)返回值成功返回会话ID 失败返回-1且设置errno1.2.3 创建会话1、注意事项创建一个会话需要注意以下6点注意事项调用进程不能是进程组组长该进程变成新会话首进程(会长)(session header)该进程成为一个新进程组的组长进程。需有root权限 (ubuntu不需要)新会话丢弃原有的控制终端该会话没有控制终端不可与用户交互后台执行。该调用进程是组长进程则出错返回建立新会话时先调用fork, 父进程终止子进程调用setsid组长进程不能成为新会话首进程新会话首进程必定会成为组长进程。2、setsid()函数作用setsid() 函数会在调用进程不是进程组组长的情况下创建一个新的会话。调用进程将成为新会话的组长会话ID进程ID。该调用进程还会成为该会话中新进程组的组长进程组ID进程ID参数无返回值成功将返回调用进程的新会话ID 失败返回-1且设置errno示例代码void sys_err(const char *str) { perror(str); exit(1); } int main() { pid_t pid; pid fork(); if(pid 0){ sys_err(fork()); } else if(pid 0){ /子进程创建会话前打印进程id组id会话id printf(child pid is %d\n,getpid()); printf(child gpid is %d\n,getpgid(0)); printf(child sid is %d\n,getsid(0)); sleep(2); pid_t sid setsid();/子进程非组长调用setsid成功创建会话并且 /创建之后子进程成为进程组组长会话的会长pidpgidsid if(sid 0){ sys_err(setsid()); } printf(sid is %d\n,sid); printf(child pid is %d\n,getpid()); printf(child gpid is %d\n,getpgid(0)); printf(child sid is %d\n,getsid(0)); sleep(20);/这段时间可查看进程信息 } exit(0); }结果查看进程信息二. 守护进程daemon2.1 概念1、Daemon(精灵)进程是Linux中的后台服务进程通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以d结尾的名字(httpd,sshd,vsftpd,nfsd)。tty文字终端pts虚拟终端2、Linux后台的一些系统服务进程没有控制终端不能直接和用户交互。不受用户登录、注销的影响一直在运行着他们都是守护进程。如预读入缓输出机制的实现ftp服务器nfs服务器等。3、创建守护进程最关键的一步是调用setsid函数创建一个新的Session并成为Session Leader。4、使用kill命令可以结束它或者使用脚本文件去管理它2.2 守护进程的创建1、创建子进程父进程退出所有工作在子进程中进行形式上脱离了控制终端2、在子进程中创建新会话setsid()函数使子进程完全独立出来脱离控制3、改变当前目录为根目录或者家目录也可以chdir()函数防止占用可卸载的文件系统也可以换成其它路径使用命令pwdxpid查看进程运行在哪个路径4、重设文件权限掩码umask()函数防止继承的文件创建屏蔽字拒绝某些权限增加守护进程灵活性守护进程有时候需要创建系统日志文件等5、关闭文件描述符继承的打开文件不会用到浪费系统资源无法卸载一般将012号文件描述符重定向到/dev/null这个空洞文件中6、开始执行守护进程核心工作守护进程退出处理程序模型注意由于守护进程不受用户的登录注销影响最后要结束守护进程只能采用kill命令将其结束2.3 用到的函数2.3.1 chdir作用改变调用进程的工作路径参数指定的工作路径返回值成功返回0 失败返回-1且设置errno2.3.2 umask作用改变当前进程的掩码参数mask要修改的值一个八进制数返回值此函数总是成功注意系统默认创建文件最大默认权限0666创建目录最大默认权限07772.4 示例代码1int main() { pid_t pid; int ret; pid fork(); if(pid 0){ sys_err(fork()); } else if(pid 0){/父进程退出 exit(0); } else{ pid_t sid setsid();/子进程创建会话脱离终端 if(sid 0){ sys_err(setsid()); } ret chdir(/);/改变工作目录防止占用可卸载的文件系统 if(ret 0){ sys_err(chdir()); } umask(0022);/防止继承父进程的掩码创建的文件权限过紧或者过松 /守护进程一般要创建日志文件 int fd open(/dev/null, O_RDWR);/空洞文件 if(fd 0){ sys_err(open()); } dup2(fd, 0); dup2(fd, 1); dup2(fd, 2);/将文件描述符012重定向守护进程脱离终端 if(fd 2){/防止fd原本就是012中的其中一个 close(fd); } while(1);/守护进程逻辑 } exit(0); }结果2.5 示例代码2static int my_daemon() { pid_t pid; pid fork(); if(pid 0){ perror(fork()); return -1; } else if(pid 0){ exit(0); } else{ pid_t sid setsid(); if(sid 0){ perror(setsid()); return -1; } chdir(/); umask(0022); int fd open(/dev/null, O_RDWR); if(fd 0){ perror(open()); return -1; } dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); if(fd 2){ close(fd); } return 0; } } int main() { int ret my_daemon(); if(ret -1){ exit(1); } FILE *fp fopen(/tmp/out, w); if(fp NULL){ perror(fopen()); exit(1); } for(int i 0;;i){ fprintf(fp,%d\n,i); fflush(fp);/文件为全缓冲需要强制刷新 sleep(1); } exit(0); }结果三. 系统日志3.1 系统日志1、在示例代码2中创建了守护进程之后往/tmp/out文件写东西来模拟守护进程的逻辑但是也可能出现打开这个文件失败的情况打开失败后使用perror来在终端上进行报错打印但是终端已经被重定向这时就需要向系统日志中来写报错信息。程序 / 服务器脱离终端必须靠日志输出信息2、注意只有syslogd服务才有权限写系统日志(为了权限分割不可能每个程序都有权限写系统日志)3、系统日志在/var/log下全部是系统日志主日志文件messagesubuntu是在syslog中可以使用命令tail /var/log/syslog来查看日志文件3.2 三个函数3.2.1 openlog作用用于为程序打开与系统日志记录器的连接参数ident--字段程序名NULL由 ident 指向的字符串会被添加到每条消息的开头并且通常会被设置为程序名。如果 ident 为 NULL则使用程序名option--选项LOG_PID日志中带上pidfacility--来源LOG_USERLOG_DAEMON用户进程守护进程返回值无3.2.2 syslog作用syslog() 会生成一条日志消息该消息将由 syslogd 进行分发参数priority--级别LOG_EMERGLOG_ALERTLOG_CRITLOG_ERRLOG_WARNINGLOG_NOTICELOG_INFOLOG_DEBUG崩溃需立即处理严重错误错误警告注意普通信息调试信息format-format内容类似printf注意不用加\n等来控制格式格式是由syslogd服务来控制的只传入要提交的字符即可返回值无3.2.3 closelog作用关闭用于向系统日志写入数据的文件描述符。参数无返回值无3.3 修改后的示例代码2#define FNAME /tmp/out static int my_daemon() { pid_t pid; pid fork(); if(pid 0){ return -1; } else if(pid 0){ exit(0); } else{ pid_t sid setsid(); if(sid 0){ return -1; } chdir(/); umask(0022); int fd open(/dev/null, O_RDWR); if(fd 0){ return -1; } dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); if(fd 2){ close(fd); } return 0; } } int main() { openlog(my_daemon, LOG_PID, LOG_DAEMON); int ret my_daemon(); if(ret -1){ syslog(LOG_ERR,my_daemon() failed!);/报错写在系统日志中 exit(1); } syslog(LOG_INFO, my_daemon() successded!); FILE *fp fopen(FNAME, w); if(fp NULL){ syslog(LOG_ERR, fopen:%s,strerror(errno)); exit(1); } syslog(LOG_INFO, %s was opened,FNAME); for(int i 0;;i){ fprintf(fp,%d\n,i); fflush(fp); sleep(1); } fclose(fp); closelog(); exit(0); }结果使用命令tail /var/log/syslog查看日志文件