终端:

在UNIX系统中,用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端(Controlling Terminal)

进程中,控制终端是保存在PCB中的信息,而fork会复制PCB中的信息,因此由Shell进程启动的其它进程的控制终端也是这个终端

默认情况下(没有重定向),每个进程的标准输入、标准输出和标准错误输出都指向控制终端,进程从标准输入读也就是读用户的键盘输入,

进程往标准输出或标准错误输出写也就是输出到显示器上。信号中还讲过,在控制终端输入一些特殊的控制键可以给前台进程发信号,例如Ctrl-C表示SIGINT,Ctrl-\表示SIGQUIT。

         Alt + Ctrl + F1、F2、F3、F4、F5、F6    字符终端   pts (pseudo terminal slave) 指伪终端。

         Alt + F7              图形终端

         SSH、Telnet...          网络终端

终端的启动流程:

文件与I/O中讲过,每个进程都可以通过一个特殊的设备文件/dev/tty访问它的控制终端。事实上每个终端设备都对应一个不同的设备文件

/dev/tty提供了一个通用的接口,一个进程要访问它的控制终端既可以通过/dev/tty也可以通过该终端设备所对应的设备文件来访问。

ttyname函数可以由文件描述符查出对应的文件名,该文件描述符必须指向一个终端设备而不能是任意文件

简单来说,一个Linux系统启动,大致经历如下的步骤:

init --> fork --> exec --> getty --> 用户输入帐号 --> login --> 输入密码 --> exec --> bash

硬件驱动程序负责读写实际的硬件设备,比如从键盘读入字符和把字符输出到显示器,线路规程像一个过滤器,对于某些特殊字符并不是让它直接通过,

而是做特殊处理,比如在键盘上按下Ctrl-z,对应的字符并不会被用户程序的read读到,而是被线路规程截获,解释成SIGTSTP信号发给前台进程,通常会使该进程停止。

线路规程应该过滤哪些字符和做哪些特殊处理是可以配置的。

终端设备模块

line disciline: 线路规程,用来过滤键盘输入的内容。比如按键CTRL + C 会退出进程,而按键

C 就显示字母C,就是因为line discipline 的过滤

ttyname函数

由文件描述符查出对应的文件名

char *ttyname(int fd);     成功:终端名;失败:NULL,设置errno

下面我们借助ttyname函数,通过实验看一下各种不同的终端所对应的设备文件名。

#include <unistd.h>
#include <stdio.h>
int main(void)
{
printf("fd 0: %s\n", ttyname());
printf("fd 1: %s\n", ttyname());
printf("fd 2: %s\n", ttyname());
return ;
}

输出:

fd : /dev/pts/
fd : /dev/pts/
fd : /dev/pts/

网络终端:

虚拟终端或串口终端的数目是有限的,虚拟终端(字符控制终端)一般就是/dev/tty1∼/dev/tty6六个串口终端的数目也不超过串口的数目

然而网络终端或图形终端窗口的数目却是不受限制的,这是通过伪终端(Pseudo TTY)实现的。

一套伪终端由一个主设备(PTY Master)和一个从设备(PTY Slave)组成。主设备在概念上相当于键盘和显示器,只不过它不是真正的硬件而是一个内核模块,

操作它的也不是用户而是另外一个进程。从设备和上面介绍的/dev/tty1这样的终端设备模块类似,只不过它的底层驱动程序不是访问硬件而是访问主设备。

网络终端或图形终端窗口的Shell进程以及它启动的其它进程都会认为自己的控制终端是伪终端从设备,例如/dev/pts/0、/dev/pts/1等。

下面以telnet为例说明网络登录和使用伪终端的过程。

网络终端

TCP/IP协议栈:在数据包上添加报头。

如果telnet客户端和服务器之间的网络延迟较大,我们会观察到按下一个键之后要过几秒钟才能回显到屏幕上。

这说明我们每按一个键telnet客户端都会立刻把该字符发送给服务器,然后这个字符经过伪终端主设备和从设备之后被Shell进程读取,同时回显到伪终端从设备,

回显的字符再经过伪终端主设备、telnetd服务器和网络发回给telnet客户端,显示给用户看。也许你会觉得吃惊,但真的是这样:每按一个键都要在网络上走个来回!

进程组

概念和特性

进程组,也称之为作业。BSD于1980年前后向Unix中增加的一个新特性。代表一个或多个进程的集合。每个进程都属于一个进程组

在waitpid函数和kill函数的参数中都曾使用到。操作系统设计的进程组的概念,是为了简化对多个进程的管理。

当父进程,创建子进程的时候,默认子进程与父进程属于同一进程组。进程组ID==第一个进程ID(组长进程)所以,组长进程标识:其进程组ID==其进程ID

可以使用kill -SIGKILL -进程组ID(负的)来将整个进程组内的进程全部杀死。【kill_multprocess.c】

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h> void sys_err(const char *str)
{
perror(str);
exit(-);
} int main(int argc, char *argv[])
{
pid_t pid;
int i, n; if (argc < ) {
printf("./a.out numchild\n");
exit();
}
n = atoi(argv[]); for (i = ; i < n; i++) {
if ((pid = fork()) < )
sys_err("fork");
else if (pid == )
break;
} if (pid == ) { /* in child */
while () {
printf("I'm child pid = %d\n", getpid());
sleep();
}
} if (pid > ) { /* in parent */ pid_t wpid;
while ((wpid = wait(NULL)) > )
printf("child %d is over\n", wpid);
} return ;
}

组长进程可以创建一个进程组,创建该进程组中的进程,然后终止。只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关。

进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组)。

一个进程可以为自己或子进程设置进程组ID

进程组操作函数

getpgrp函数

获取当前进程的进程组ID

pid_t getpgrp(void); 总是返回调用者的进程组ID

getpgid函数

获取指定进程的进程组ID

pid_t getpgid(pid_t pid); 成功:0;失败:-1,设置errno

   如果pid = 0,那么该函数作用和getpgrp一样。

练习:查看进程对应的进程组ID   【getpgid.c】

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h> void sys_err(const char *str)
{
perror(str);
exit(-);
} int main(int argc, char *argv[])
{
pid_t pid;
int i, n; if (argc < ) {
printf("./a.out numchild\n");
exit();
}
n = atoi(argv[]); for (i = ; i < n; i++) {
if ((pid = fork()) < )
sys_err("fork");
else if (pid == )
break;
} if (pid == ) { /* in child */
while () {
printf("I'm child pid = %d\n", getpid());
sleep();
}
} if (pid > ) { /* in parent */ pid_t wpid;
while ((wpid = wait(NULL)) > )
printf("child %d is over\n", wpid);
} return ;
}

setpgid函数

改变进程默认所属的进程组。通常可用来加入一个现有的进程组或创建一个新进程组。

int setpgid(pid_t pid, pid_t pgid); 成功:0;失败:-1,设置errno

将参1对应的进程,加入参2对应的进程组中。

  注意:

1. 如改变子进程为新的组,应fork后,exec前。

2. 权级问题。非root进程只能改变自己创建的子进程,或有权限操作的进程

练习:修改子进程的进程组ID 【setpgid.c】

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> int main(void)
{
pid_t pid; if ((pid = fork()) < ) {
perror("fork");
exit();
} else if (pid == ) { printf("child PID == %d\n",getpid());
printf("child Group ID == %d\n",getpgid()); // 返回组id
sleep();
printf("----Group ID of child is changed to %d\n",getpgid());
exit(); } else if (pid > ) {
sleep();
setpgid(pid,pid); //让子进程自立门户,成为进程组组长,以它的pid为进程组id sleep();
printf("\n");
printf("parent PID == %d\n", getpid());
printf("parent's parent process PID == %d\n", getppid());
printf("parent Group ID == %d\n", getpgid()); sleep();
setpgid(getpid(),getppid()); // 改变父进程的组id为父进程的父进程
printf("\n----Group ID of parent is changed to %d\n",getpgid());
while();
} return ;
}
child PID ==
child Group ID ==
----Group ID of child is changed to parent PID ==
parent's parent process PID == 68319
parent Group ID == ----Group ID of parent is changed to

会话

创建会话

多个进程组成一个进程组,多个进程组组成一个会话

创建一个会话需要注意以下6点注意事项:

1.       调用进程不能是进程组组长,该进程变成新会话首进程(session header)

2.       该进程成为一个新进程组的组长进程。

3.       需有root权限(ubuntu不需要)

4.       新会话丢弃原有的控制终端,该会话没有控制终端

5.       该调用进程是组长进程,则出错返回

6.       建立新会话时,先调用fork, 父进程终止,子进程调用setsid

getsid函数

获取进程所属的会话ID

pid_t getsid(pid_t pid); 成功:返回调用进程的会话ID;失败:-1,设置errno

pid为0表示察看当前进程session ID

ps ajx命令查看系统中的进程。参数a表示不仅列当前用户的进程,也列出所有其他用户的进程,参数x表示不仅列有控制终端的进程,也列出所有无控制终端的进程,参数j表示列出与作业控制相关的信息。

组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。

setsid函数

创建一个会话,并以自己的ID设置进程组ID,同时也是新会话的ID

pid_t setsid(void);  成功:返回调用进程的会话ID;失败:-1,设置errno

调用了setsid函数的进程,既是新的会长,也是新的组长。 

练习:fork一个子进程,并使其创建一个新会话。查看进程组ID、会话ID前后变化                           【session.c】

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> int main(void)
{
pid_t pid; if ((pid = fork())<) {
perror("fork");
exit(); } else if (pid == ) { printf("child process PID is %d\n", getpid());
printf("Group ID of child is %d\n", getpgid());
printf("Session ID of child is %d\n", getsid()); sleep();
setsid(); //子进程非组长进程,故其成为新会话首进程,且成为组长进程。该进程组id即为会话进程 printf("Changed:\n");
printf("child process PID is %d\n", getpid());
printf("Group ID of child is %d\n", getpgid());
printf("Session ID of child is %d\n", getsid());
sleep(); exit();
} return ;
}
child process PID is
Group ID of child is
Session ID of child is
Changed:
child process PID is
Group ID of child is
Session ID of child is

守护进程

Daemon(精灵)进程,是Linux中的后台服务进程通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以d结尾的名字。

Linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互。不受用户登录、注销的影响,一直在运行着,他们都是守护进程。如:预读入缓输出机制的实现;ftp服务器;nfs服务器等。

创建守护进程,最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader。

创建守护进程模型

1.       创建子进程,父进程退出

所有工作在子进程中进行形式上脱离了控制终端

2.       在子进程中创建新会话

           setsid()函数

           使子进程完全独立出来,脱离控制

3.       改变当前目录为根目录

           chdir()函数

           防止占用可卸载的文件系统

           也可以换成其它路径

4.       重设文件权限掩码

           umask()函数

           防止继承的文件创建屏蔽字拒绝某些权限

           增加守护进程灵活性

5.       关闭文件描述符

           继承的打开文件不会用到,浪费系统资源,无法卸载

6.       开始执行守护进程核心工作

7.       守护进程退出处理程序模型                                                                                                                                                                                                                                 【mydaemond.c】

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h> void daemonize(void)
{
pid_t pid;
/*
* 成为一个新会话的首进程,失去控制终端
*/
if ((pid = fork()) < ) {
perror("fork");
exit();
} else if (pid != ) /* parent */
exit();
setsid();
/*
* 改变当前工作目录到/目录下.
*/
if (chdir("/") < ) {
perror("chdir");
exit();
}
/* 设置umask为0 */
umask();
/*
* 重定向0,1,2文件描述符到 /dev/null,因为已经失去控制终端,再操作0,1,2没有意义.
*/
close();
open("/dev/null", O_RDWR);
dup2(, );
dup2(, );
} int main(void)
{
daemonize();
while(); /* 在此循环中可以实现守护进程的核心工作 */
}
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h> void daemonize(void)
{
pid_t pid;
/*
* 成为一个新会话的首进程,失去控制终端
*/
if ((pid = fork()) < ) {
perror("fork");
exit();
} else if (pid != ) /* parent */
exit();
setsid();
/*
* 改变当前工作目录到/目录下.
*/
if (chdir("/") < ) {
perror("chdir");
exit();
}
/* 设置umask为0 */
umask();
/*
* 重定向0,1,2文件描述符到 /dev/null,因为已经失去控制终端,再操作0,1,2没有意义.
*/
close();
open("/dev/null", O_RDWR);
dup2(, );
dup2(, );
} int main(void)
{
daemonize();
int fd = open("./mydaemon.log", O_RDWR|O_CREAT, );
if (fd < ) {
perror("open log error\n"); // 失去了终端,没有打印
}
char buf[] = "hello world";
while(){ /* 在此循环中可以实现守护进程的核心工作 */
sleep();
write(fd,buf,sizeof buf);
}
return ;
}

open /mydaemon.log文件没有权限,而切换到root权限后执行成功。

因为我创建的mydaemon程序的工作目录已经切换到了根目录,所以普通用户没有在根目录下创建文件的权限。如果这里将文件创建在当前目录的话就不用切换到root权限。

cat /mydaemon.log
hello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello

linux系统编程--守护进程,会话,进程组,终端的更多相关文章

  1. Linux系统编程---守护进程

    守护进程是什么?就是在后台运行的进程. 那么如何创建守护进程呢? 1. 创建孤儿进程 2. setsid() 创建进程会话 3. 重定向标准输入, 标准输出 4. chdir, 改当当前进程的工作目录 ...

  2. Linux系统编程(8)—— 进程之进程控制函数fork

    fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事. 一个进程调用fork()函数后,系统先 ...

  3. Linux系统编程(7)—— 进程之进程概述

    我们知道,每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体.现在我们全面了解一下其中都有哪些信息. 进程id.系统中每个进程有 ...

  4. Linux系统编程(9)—— 进程之进程控制函数exec系列函数

    在Linux中,并不存在exec()函数,exec指的是一组函数,一共有6个,分别是: #include <unistd.h> extern char **environ; int exe ...

  5. Linux系统编程@进程管理(二)

    1.创建守护进程(Deamon) 守护进程的概念与作用 后台服务程序 – 系统服务,进程名字往往以’d’结尾,生存周期比较长(系统装入时启动,关闭时候终止.系统装入两种启动方式:1从启动脚本.etc/ ...

  6. Linux系统编程@进程通信(一)

    进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...

  7. linux系统编程之进程(一)

    今天起,开始学习linux系统编程中的另一个新的知识点----进程,在学习进程之前,有很多关于进程的概念需要了解,但是,概念是很枯燥的,也是让人很容易迷糊的,所以,先抛开这些抽象的概念,以实际编码来熟 ...

  8. 构建一个简单的Linux系统 MenuOs —— start_kernel到init进程(20135304刘世鹏)

    构建一个简单的Linux系统 MenuOs —— start_kernel到init进程 作者:刘世鹏20135304 <Linux内核分析>MOOC课程http://mooc.study ...

  9. Linux系统编程【转】

    转自:https://blog.csdn.net/majiakun1/article/details/8558308 一.Linux系统编程概论 1.1 系统编程基石 syscall: libc:标准 ...

随机推荐

  1. C++编写DLL文件

    动态链接库DLL文件与EXE文件一样也是可执行文件,但是DLL也被称为库,因为里面封装了各种类.函数之类的东西,就像一个库一样,存着很多东西,主要是用来调用的.调用方式主要分为两种:隐式(通过lib文 ...

  2. 手动部署k8s-prometheus

    简介 Prometheus 最初是 SoundCloud 构建的开源系统监控和报警工具,是一个独立的开源项目,于2016年加入了 CNCF 基金会,作为继 Kubernetes 之后的第二个托管项目. ...

  3. MySQL 子查询(一)

    源自MySQL 5.7 官方手册 13.2.10 Subquery Syntax 〇.MySQL子查询介绍 子查询指的是嵌套在某个语句中的SELECT语句. MySQL支持标准SQL所要求的所有子查询 ...

  4. Linux上定时shell脚本

    原文链接:http://www.92coder.com/9-Linux%E5%AE%9A%E6%97%B6shell%E8%84%9A%E6%9C%AC/#more 本文主要介绍在Linux系统上部署 ...

  5. Viola–Jones object detection framework--Rapid Object Detection using a Boosted Cascade of Simple Features中文翻译 及 matlab实现(见文末链接)

    ACCEPTED CONFERENCE ON COMPUTER VISION AND PATTERN RECOGNITION 2001 Rapid Object Detection using a B ...

  6. ECMAScript中的原型继承

    //ECMAScript中的原型继承//ECMAScript中的继承主要是依靠原型链实现的.(关于原型链的介绍,详见<高三>6.3.1章节 P162) //本文示例主要为了说明SubTyp ...

  7. js重点——作用域——内部原理(二)

    本篇是深入分析和理解作用域的第一篇——内部原理和工作模型. 我们知道作用域是变量,对象,函数可访问的一个范围.这说明了我们需要一套良好的规则来存储变量,之后方便查找.所以我们首先要理解的是在哪里而且怎 ...

  8. requests模块发送数据

    通过json dumps发送 import requests import json def agent(): """ 执行命令采集硬件信息 将执行的信息发送给API : ...

  9. 微信小程序配置动态title

    wx.setNavigationBarTitle({ title: this.dynTitle }) 通过页面路由跳转传参 onload(opt)中的opt接受传过来的title 赋值即可

  10. ZPL语言完成条形码的打印

    近期因为项目的需求,需要使用到打印机来打印业务相关的条形码和其他信息,由于之前有操作其它打印机的经验,Leader就安排我来做这个了(凑哦,这能说我是懵逼的么).于是就开始了我的探索之旅啦,不对,是踩 ...