会话和进程组
进程组:进程组是多个进程的集合, 接收同一个终端的各类信号信息。进程调用setpgid(pid, pgid)可以加入一个现有的进程组或者创建一个新的进程组。
进程组长(头部进程):每个进程组都有一个进程组ID, 每个进程组都有一个组长(头部进程), 在大部分系统中, 进程组ID一般就是头部进程ID。获得一个进程所在的进程组ID用getpgid(pid)。
作业:是一个进程组,作业分为前台作业(前台进程组),后台作业(后台进程组)
会话:会话是若干进程组的集合。会话有一个前台进程组和多个后台进程组。对于一个打开的控制终端,会话 开始于用户登录,终止于用户退出。
会话的创建:
进程调用setsid函数创建一个新的Session,并成为Session Leader
a.调用这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1。
*要保证当前进程不是进程组的Leader也很容易,只要先fork再调用setsid就行了。fork创建的子进程和父进程在同一个进程组中,进程组的Leader必然是该组的第一个进程,所以子进程不可能是该组的第一个进程,在子进程中调用setsid就不会有问题了。
b.进程调用完此函数,会成为新会话的会话首进程(session leader),该进程会成为一个新进程组的头部进程,新进程组的组ID就是这个进程的ID。
c.该进程没有控制终端。如果在调用setsid之前该进程有一个控制终端,那么这种联系会被切断。
创建会话代码样例:
#define _POSIX_SOURCE
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
void main() {
pid_t pid;
int p[2];
char c='?';
if (pipe(p) != 0)
perror("pipe() error");
else
if ((pid = fork()) == 0) {
printf("child's process group id is %d\n", (int) getpgrp());
write(p[1], &c, 1);
setsid();
printf("child's process group id is now %d\n", (int) getpgrp());
exit(0);
}
else {
printf("parent's process group id is %d\n", (int) getpgrp());
read(p[0], &c, 1);
sleep(5);
}
}
运行结果:
会话与控制终端
一个会话可以有一个控制终端,控制终端通常是终端设备,比如登录后的xshell界面。
与控制终端建立连接的会话首进程被称为控制进程。
对于有控制终端的会话,同一时刻只能有一个进程组能够称为前台进程组,会话中的其他进程组都是后台进程组。
控制终端与会话的信号传递
在终端输入中断键(Delete或Ctrl+C), 会将中断信号( SIGINT)发送给前台进程组的所有进程。
在终端输入退出键(Ctrl+\), 会将退出信号( SIGQUIT)发送给前台进程组的所有进程。
如果终端已经断开连接,会将挂断信号( SIGHUP)发送给控制进程。
示意图
守护进程
守护进程的概念
守护进程是运行在操作系统后台的特殊进程,且守护进程和控制终端(比如xshell界面)是隔离的。
守护进程没有控制终端通常源于它们由系统初始化脚本启动,然而守护进程也可能从某个终端由用户在shell提示符下键入命令行启动,这样的守护进程必须亲自脱离于控制终端的关联,从而避免与作业的控制、终端会话管理、终端产生信号等发生任何不期望的交互,也可以避免在后台运行的守护进程非预期地输出到终端。
守护进程的特点:
无需控制终端
运行在后台
一般随操作系统启动和关闭
守护进程运行在后台,但是和后台进程有一定区别:
(1)守护进程和终端不挂钩,不向终端输出内容(信息),但后台进程是能向终端输出内容的(如使用printf语句,后台进程可以将内容输出到屏幕上)。
(2)守护进程在关闭终端(比如Xshe11)的时候不会受到影响,而后台进程将在终端关闭后自动退出。
守护进程的启动方式:
1.在系统启动阶段,由系统初始化脚本启动。
这些脚本通常位于/etc目录或以/etc/rc开头的某个目录中。由这些脚本启动的守护进程一开始时拥有超级用户权限。
比如inetd, sendmail, syslogd这些服务的进程都是用系统初始化脚本启动
2.当有网络请求(Telnet, FTP)到达时,由inetd超级服务器启动
3.cron命令启动
4.at命名启动
5.从用户终端(或在前台/后台)执行测试/重启服务的脚本启动
*由于守护进程没有控制终端,因此如果守护进程需要输出日志或消息,需要借助syslog/openlog 函数。
操作系统中常见的守护进程分类:
系统守护进程:syslogd、login、crond、at等。
网络相关的守护进程:sendmail、httpd、xinetd等。
独立启动的守护进程:httpd、named、xinetd等。
守护进程的创建方式:
让一个普通进程变成守护进程的操作步骤:
Step.1 调用fork( )。
Step.2 在父进程中,调用exit( )来终止父进程,留下子进程继续运行。
子进程继承了父进程的进程组ID,不过它有自己的进程ID,这就保证了子进程不是一个进程组的头部进程
Step.3 调用setsid( ),给守护进程创建一个新的进程组和会话,使当前进程成为新进程组的头部进程。
Step.4 忽略SIGHUP信号,并再次调用fork。该函数返回时,父进程实际上是上一次调用fork产生的子进程。然后这个父进程被终止,留下新的子进程继续运行。
忽略SIGHUP信号的原因:当头部进程(第一次fork产生的子进程)终止时,其会话中的所有进程(再次fork产生的子进程)都会收到SIGHUP信号。
Step.5 把工作目录改到根目录chdir( )。这是因为守护进程的工作目录可以位于文件系统的任何位置。守护进程在正常运行时,会保持某个随机目录处于打开状态,从而阻止管理员卸载包含了该目录的文件系统。这么做是为了统一路径,让文件系统可拆卸。
Step.6 关闭所有文件描述符。
Step.7 打开文件描述符stdin,stdout和stderr并将它们重定向到/dev/null。
让一个普通进程变成守护进程的常用函数: daemon_init()
代码样例:
int daemon_init(const char *pname, int facility)
{
int i;
pid_t pid;
if ((pid = Fork()) < 0)
return (-1);
else if (pid)
_exit(0); /* parent terminates */
/* child 1 continues... */
if (setsid() < 0) /* become session leader */
return (-1);
Signal(SIGHUP, SIG_IGN);
if ((pid = Fork()) < 0)
return (-1);
else if (pid)
_exit(0); /* child 1 terminates */
/* child 2 continues... */
daemon_proc = 1; /* for err_XXX() functions */
chdir("/"); /* change working directory */
/* close off file descriptors */
for (i = 0; i < MAXFD; i++)
close(i);
/* redirect stdin, stdout, and stderr to /dev/null */
open("/dev/null", O_RDONLY);
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);
openlog(pname, LOG_PID, facility); //使用sylogd处理错误
return (0); /* success */
}
简洁版:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
int main(int argc, char* argv[])
{
pid_t pid = 0;
pid_t sid = 0;
FILE *fp= NULL;
int i = 0;
pid = fork();// fork a new child process
if (pid < 0)
{
printf("fork failed!\n");
exit(1);
}
if (pid > 0)// its the parent process
{
printf("pid of child process %d \n", pid);
exit(0); //terminate the parent process succesfully
}
umask(0);//unmasking the file mode
sid = setsid();//set new session
if(sid < 0)
{
exit(1);
}
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
fp = fopen("mydaemonfile.txt", "w+");
while (i < 10)
{
sleep(1);
fprintf(fp, "%d", i);
i++;
}
fclose(fp);
return (0);
}
运行结果:
常用的Linux进程分析命令:
查看正在运行的进程:ps -ef
查看当前的所有进程:ps axj
查看占用端口的进程:lsof -i:8086
查看用户username的进程所打开的文件:lsof -u username
查询被进程ID对应的进程打开的文件:lsof -p 1000
查看进程内存情况:pmap PID
样例:
参考阅读:
《UNIX环境高级编程第3版》
《Linux C++ 通信架构实战》
《UNIX网络编程 卷1:套接字联网API 第3版》
https://www.ibm.com/docs/en/zos/