一、进程标识符pid
1、pid的类型:pid_t
pid 是有符号的16位整型数,也就是说可以同时进行三万多进程。
2、命令:ps
ps命令用于报告当前进程的信息:
ps命令有着不同的组合,可以显示进程不同的内容:
(1)ps -axf :是描述当前进程
(2)ps -axm :描述进程详细信息,m表示more
(3)ps ax -L :以linux特有的方式进行查看
3、进程号是顺次向下使用
注意: 之前讲的文件描述符,是优先使用当前最小的文件描述符,但是进程标识符不一样,进程号是顺次向下使用, 即使前面有释放的,进程号也会一直变大。
4、如何获得当前进程的进程号?
(1)getpid( )函数:获得当前进程的进程号
(2)getppid()函数:获得当前进程的父进程进程号
二、进程的产生
1、fork( )函数
- (1)作用:创建一个新的进程,通过复制当前进程
注意理解关键字: duplicating, 意味着拷贝,克隆,一模一样
重点1:fork创建新的进程,是通过复制当前进程
复制标志着: 副本和原本是一样的, 是通过复制父进程的方式来创建进程的,连执行到的位置都一样。
-
(2)fork后父子进程的区别
1)fork的返回值不一样
2)pid不同
3)ppid不同, 父进程ID
4)未决信号(悬而未决)和文件锁不继承
5)资源利用量归零 -
(3)init进程: 1号进程,是所有进程的祖先进程
-
(4)fork( )函数返回值
fork( )函数运行成功后,会有两个返回值。当调用成功的时候,给父进程返回子进程的pid号, 返回给子进程0;如果创建失败,则返回父进程-1
-
(5)例子:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("[%d] begin!\n", getpid());
pid_t pid = fork();
if(pid < 0){
perror("fork()");
exit(1);
}
if(pid == 0){ // child
printf("[%d]: Child is working~\n", getpid());
}else{ // parent
printf("[%d]: Parent is working~\n", getpid());
}
printf("[%d] End!\n", getpid());
exit(0);
}
运行结果:
注意:永远不要去猜父进程和子进程哪一个被调度。调度器的调度策略来决定哪个进程先运行。这里也可能会出现子进程先调度。
重点2:永远不要去猜父进程和子进程哪一个被调度
如果想要子进程先运行,可以有一定的措施,但一定不要猜测(比如这里加了一个sleep函数-但是不推荐用这个函数,后面会解释)
运行结果:
在源代码中加入一句 getchar( )代码,让程序一直运行,不结束:
另外开一个终端,利用 ps -axf 命令,来查看当前进程的信息:
像这种阶梯状的都是父子进程的关系,这种顶格写的他们的父进程都是1号init
重点3:面试题->fflush的重要性
(begin是打印了一个,end一定是打印了两个,说明两个进程执行到的节点都是一样的)但是,如果将输出重定向到一个文件中,就会出现问题:
可以看到,这里出现了两个 Begin
为什么呢? 是因为在创建子进程的时候没有刷新缓冲区,导致,缓冲区中的数据没有更新。因此应该这样做:在fork()之前刷新所有该刷新的流
(标准输出是行缓冲,文件是全缓冲,所以会出现打印终端上没有问题,但是输出到文件中时就有两个Begin) (全缓冲模式中 \n 只表示换行,没有刷新缓冲区的作用)
运行结果:
2、例子:数质数的个数
(1)原始例子
题意:计算给定区间中质数的个数
代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define LEFT 30000000
#define RIGHT 30000200
int main() {
int i, j, mark;
for (i = LEFT; i <= RIGHT; i++) {
mark = 1;
for (j = 2; j < i / 2; j++) {
if (i % j == 0) {
// 非质数
mark = 0;
break;
}
}
if (mark)
// 是质数
printf("%d is a primer\n", i);
}
exit(0);
}
运行结果:
如果想要知道该程序执行了多少的时间,可以采用:time
命名
如果终端上只想显示运行了多少时间,但是代码却又输出了内容,可以将这些内容输出重定向到一个空设备上去:/dev/null
(注意这里 time 不属于标准输出,不能重定向到空设备上去)
(2)改进例子
- 如果把这个任务让多个子任务去操作, 创建200 个子进程去计算
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define LEFT 30000000
#define RIGHT 30000200
int main() {
int i, j, mark;
pid_t pid;
for (i = LEFT; i <= RIGHT; i++) {
pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid == 0) {
// i子进程,干活
mark = 1;
for (j = 2; j < i / 2; j++) {
if (i % j == 0) {
// 非质数
mark = 0;
break;
}
}
if (mark)
// 是质数
printf("%d is a primer\n", i);
exit(0); // 子进程结束(一定要有)
}
}
exit(0);
}
运行结果:
(可以看到这是乱序的状况,这是因为进程调度的问题)
(3)对比
-
可以看到前后的时间差距很大,你可能会想,程序0相当于一个人在干201个活, 程序1相当于一个人干一个活,共201个人干, 所以它的时间短,错了!
因为当前系统是4核的,所以4个进程是可以平行运行的。如果是单核机器的话, 无论是多少个 进程,都要一个一个调度,所以单核机器使用的时间大小和有多少个进程没有关系。 -
注意:但是四核的机器运行的时间并不是单核机器运行时间的四分之一,所以运行时间还是和调度有很大关系。
-
谁打开谁关闭, 谁申请,谁释放,父进程创建了子进程,那么父进程就是要给子进程“收尸”
-
如果程序当中出现僵尸态,僵尸态应该是一闪即逝的,因为表示及时收尸了; 或者如果你查看的时候有几个僵尸态进程,过了三五秒钟再看,还是有几个僵尸态,但是换了一批,这种情况表示要么就是当前操作系统比较忙,要么就是父进程比较忙,需要过一段时间进行批量的收尸。
3、vfork( )函数:将被废弃了
-
fork( )函数:是通过复制父进程的方式产生子进程,它和父进程一样,只有那五点不一样。
-
例如,我的父进程中从数据库中导入30万条记录,创建子进程,然后让子进程打印hello world, 然后退出,这样的fork成本比较高,但是注意,这说的只是fork的原始实现。
-
如果使用vfork的话,当使用vfork产生一个子进程的话,子进程和父进程共用一个数据块,但是,如果子进程中修改了的,父进程可以看得到吗??man手册中说:使用vfork调用的进程,只能保证成功的调用
_exit(2)
或者exec(3)
, 其他的都属于未定义范围。
-
其实现在fork已经不是前面说的那样了,fork添加了写时拷贝技术,在进行fork的时候,子进程与父进程确实是共用数据块(只读不写), 如果一个进程企图使用一个指针去写,首先会把数据拷贝一份,然后修改自己的那一份,不会影响其他的进程,谁改谁拷贝。
三、进程的消亡及释放资源(收尸)
-
收尸:等进程状态发生变化,然后对进程收尸,资源回收。
前面执行primer1程序的时候,命令行会先出现,然后后面的打印后出现,这个现象的原因是???
1、wait( )函数(没有指向,只会死等)
(1)功能
父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:
1) 阻塞等待子进程退出
2) 回收子进程残留资源
3) 获取子进程结束状态(退出原因)
(2)查看进程退出状态
pid_t wait(int *status)
将当前子进程收尸收回来的状态放入到一个整形变量status
中,也就是说给他一个整形变量的地址值。函数提供了一系列的宏,用于查看当前进程退出的状态是什么
-
WIFEXITED(status) :判断当前进程是否正常终止,背五条正常终止,三条异常终止
-
WEXITSTATUE(status) :在进程必须正常结束情况下,返回子进程退出时的状态
-
WIFSIGNALED(status) :判断进程是否是由信号使其异常终止的
-
WTERMSIG(status):如果是由信号导致的进程异常终止,返回使进程终止的那个信号的编号
2、waitpid( )函数(不用死等)
(1)作用
作用同于wait,但可指定pid进程清理,可以不阻塞。
该函数好用的点在于可以使用 options 使得操作不阻塞(options是一个位图,其中一个NOHANG
. 不用死等)
(2)参数介绍
-
参数 pid:
pid > 0 回收指定ID的子进程pid = -1 回收任意子进程(相当于wait)
pid = 0 回收和当前调用waitpid一个组的所有子进程
pid < -1 回收指定进程组内的任意子进程
注意:
(1)用户分组,进程组什么的,不管什么分组,唯一的好处就是好操作。一个进程创建出来的进程是跟它的父进程是同组进程(当然可以人为设置使其不是同组)。
(2)一次wait或waitpid调用只能清理一个子进程,清理多个子进程需要用到循环 -
参数 status
同wait函数一样,这里不做介绍了 -
参数 options
该参数是一个位图,可以由多个进行 与 操作,options的默认值是0,表示会阻塞。可以通过将 options 设置为常量WNOHANG
、WUNTRACED
和WCONTINUED
的各种组合来修改默认行为:(1)
WNOHANG
: 如果等待集合中的任何子进程都还没有终止,那么就立即返回(返回值为0)。 默认的行为是挂起调用进程,直到有子进程终止 。在等待子进程终止的同时,如果还想做些有用的工作,这个选项会有用。(这个就可以使回收操作变成非阻塞)(2)
WUNTRACED
: 挂起调用进程的执行,直到等待集合中的一个进程变成已终止或者被停止 。返回的PID 为导致返回的已终止或被停止子进程的 PID。默认的行为是只返回已终止的子进程。当你想要检査已终止和被停止的子进程时,这个选项会有用。(3)
WCONTINUED
: 挂起调用进程的执行,直到等待集合中一个正在运行的进程终止或等待集合中一 个被停止的进程收到 SIGCONT 信号重新开始执行。
3、例子:利用wait( )函数进行收尸
代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define LEFT 30000000
#define RIGHT 30000200
int main() {
int i, j, mark;
pid_t pid;
for (i = LEFT; i <= RIGHT; i++) {
pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid == 0) {
// i子进程,干活
mark = 1;
for (j = 2; j < i / 2; j++) {
if (i % j == 0) {
// 非质数
mark = 0;
break;
}
}
if (mark)
// 是质数
printf("%d is a primer\n", i);
exit(0); // 子进程结束
}
}
// int st;
for (i = LEFT; i <= RIGHT; i++) {
// wait(&st);
wait(NULL); // 不保存状态)
}
exit(0);
}
运行结果:
四、进程分配
问题:如果当前的任务有201个,然后用于处理这个任务的进程有N(3)个,如何分配?
1、分块法
-
三个进程,第一个进程处理一部分,第二第三个进程处理一部分。
-
缺点是:任务的轻重,分配并不平均
2、交叉分配法
-
第一个数给进程1, 第二个数给进程2, 第三个数给3, 第四个数给进程1, 第五个数给进程2…
-
如果在一个任务中,如果可以使用分块法也可以使用交叉分配法的时候,我们使用交叉分配法。
例子:利用交叉分配法对任务进行分配
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define LEFT 30000000
#define RIGHT 30000200
#define N 3
int main() {
int i, j, n, mark;
pid_t pid;
for (n = 0; n < N; n++) {
pid = fork();
if (pid < 0) {
perror("fork()");
// 这里注意,如果出错了,需要写一个循环,把曾经fork出去的内容全部收尸
exit(1);
}
if (pid == 0) {
for (i = LEFT + n; i <= RIGHT; i += N) {
mark = 1;
for (j = 2; j < i / 2; j++) {
if (i % j == 0) {
// 非质数
mark = 0;
break;
}
}
if (mark)
printf("进程[%d] %d is a primer\n",n , i);
}
exit(0);
}
}
for (n = 0; n < N; n++) {
wait(NULL);
}
exit(0);
}
运行结果:
3、池类算法
-
上游一些进程,下游一些进程,中间一个模块(池子),上游的进程将任务往中间模块扔,下游的进程抢任务。
-
这样的任务分配和抢到的任务都具有随机性。
五、exec函数族
-
在当前阶段,需要记住一个单词
"few"
, 这个单词的三个字母:f: fork
,e: exec
,w: wait
, 这三个函数搭建起了linux的框架。 -
疑问?? 为什么shell创建的子进程不是shell, 而是primerN这样的进程??
-
exec函数族的函数有(执行一个文件):execl( )、execp( )、execle( )、execv( )、execvp( ) 。
-
exec函数族用一个新的进程映像,替换当前的进程映像。
-
比如说: 进程空间搭建起来的话,在exec这个阶段就已经有代码段,已初始化数据段,未初始化数据段,栈和堆是后来才搭建起来,所以在c程序虚拟空间完成的时候,是在各个不同的阶段做的不同的实现,搭建起来的不同的数据内容。
-
注意: 上面的environ环境变量, 它的存储和argv的存储非常像。后面两个函数看起来是定参结构,前面两个是变参实现,但是实际上有多少个存储结构是和argv有关的,argv才是真正意义上的变参实现,所以前面两个函数是定参,后面两个是变参。
1、execl( ):定参
-
格式:
int execl(const char *pathname, const char *arg, ... /* (char *) NULL */);
-
传入一个可执行文件的路径,char * arg …的意思是:要给这个命令传参的化,参数是哪些,可以传多个参数,最后补一个NULL作为当前传参的结束。
2、execlp( ):定参
-
格式:
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
-
传入一个可执行文件的名字,char * arg …的意思是:要给这个命令传参的化,参数是哪些,可以传多个参数,最后补一个NULL作为当前传参的结束。
-
为什么只传入一个可执行文件的名字不需要路径就可以呢??因为他有环境变量。环境变量是程序员与管理员之间的一种约定。
3、execle( ):定参
-
格式:
int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */);
-
这个函数和execlp差不多,最后可以将一个环境变量导入进来。
4、execv( ):变参
例子:查看时间戳
-
(1)执行命令行打印时间戳
-
(2)利用
which
命令查看一下 date 命令在哪里
-
(3)利用代码打印时间戳
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/*
date + %s
打印时间戳
*/
int main() {
puts("Begin");
// 用一个新的进程映像来替换现在的
// 如果下面的execl()函数执行成功,则不会打印End
execl("/bin/date", "date", "+%s", NULL);
// 这里都不用判断execl返回值,因为下面的内容只有在
// execl执行失败之后才会执行到
perror("execl()");
exit(1);
puts("End");
exit(0);
}
执行结果:
-
(4)我们将这个函数的输出重定向到 /tmp/out , 然后在显示,会发现Begin没有了
所以需要注意:在exec这个函数族的使用的时候,一定也要注意fflush的使用。当缓冲区还没有向外输出呢,exec这个函数就用来替换当前的旧的进程映像。所以在使用exec之前一定要将所有的流刷新一下。
-
(5)你还是你,但是你已经不是你了,它的壳子没有变(PID), 但是它的内容(进程映像)已经变了。Unix 世界是怎么做的, shell
代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
/*
使用fork, wait, exec
*/
int main() {
pid_t pid;
puts("Begin");
fflush(NULL);
pid = fork();
if (pid == 0) {
// 子进程
execl("/bin/date", "date", "+%s", NULL);
perror("execl()");
exit(1);
}
// 父进程等着收尸
wait(NULL);
puts("End");
exit(0);
}
运行结果:
所以所有的shell都是这样做的,当你执行一个命令的时候,shell会根据fork创建一个子进程,然后在子进程里面进行exec操作, 替换子进程,shell这个父进程在wait, 等待收尸。所以当我们执行一个命令的时候,都是命令的结果先出来,也就是子进程结果出来,然后命令行再弹出来,因为shell进程在wait,当子进程结束的时候,shell帮忙给收尸。
为什么是父子进程可以打印在同一终端上
- 每个进程中有一个文件描述符表,0、1、2关联stderr, stdin, stdout, 一旦创建子进程,子进程是通过复制父进程来的,所以它的文件描述符表是和父进程一样的,所以终端(父进程)执行 一个命令(子进程)的时候,子进程是打印输出到终端的。
例子:执行sleep
代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
/*
使用fork, wait, exec
*/
int main() {
pid_t pid;
puts("Begin");
fflush(NULL);
pid = fork();
if (pid == 0) {
// 子进程
execl("/bin/sleep", "sleep", "100", NULL);
perror("execl()");
exit(1);
}
// 父进程等着收尸
wait(NULL);
puts("End");
exit(0);
}
查看当前父子进程关系:
- argv[0]很多时候被人忽略,但是如果换成其他的名字,比如,我们换为"httpd", 显示的就是httpd, 这就是木马的产生的低级办法。比较高级的隐藏在内核模块当中,当然没有隐藏在用户态危害更大。
父子进程关系
六、用户权限及组权限
-
其实用户权限和组权限是没有我们想的那么简单,它们是分作好几种方式来进行存放的,为什么这么做?
-
当我们查看
/etc/shadow
的时候,用户权限是不够的;但是我们操作/etc/passwd
权限是够的。
-
一开始的时候,普通用户什么权限也没有,连修改自己口令的权限都没有,权限全部集中在root用户,慢慢的,root用户的权限开始下放,普通用户才慢慢的有了权限。普通用户可以通过passwd文件修改自己的口令。
1、关于:u + s 和 g + s
-
其实在我们执行某一个命令的时候,是带着一个身份来执行的,身份从哪来的?
-
uid 和 gid, 拿出一个来讲: uid
user id(uid)其实存的不只有一份,它有三份。一个叫real uid
,effecitive uid
,save uid
。可以没有save uid . 鉴定权限的时候是用effective id 。 -
exec鉴定权限, exec发现了u+s的权限, 看/etc/passwd 的权限。
前面讲到stat
函数的时候,其中也有一位用来保存当前文件时否有u+s, g+s的权限,(s体现u+s权限, x体系g+s权限) -
注意:
(1) u+s指的是: 如果一个文件有u+s的权限,那就意味着:当别的用户在调用当前这个二进制文件的时候,它的身份会切换成当前二进制文件的user的权限来执行。(2)g+s指的是:如果这个文件有g+s权限的话,那就意味着,当前不管任何用户来调用这个二进制可执行文件的时候,当前用户的身份就会切换成这个二进制文件的同组用户身份来执行。
(3)exec来鉴定权限, 鉴定权限看的是effective ui。
(4)所以当用:
ls -l /usr/bin.passwd
的时候,其实是以root身份来跑。
(5)其实u+s, g+s的作用 就是将原来root的权限打散往下放。
2、当前shell的身份从哪里来的
- 当前机器中产生的第一个进程是init进程,当init产生的时候,本身还是root权限,所以init的权限是root权限
- 然后init 进行fork+exec产生一个进程getty进程(root身份),getty进程提示你请输入login name, 然后我们输入用户名
- 然后getty进程进行exec(注意没有fork子进程), 直接替换为login 进程,然后login进程提示你继续输入 passwd.
- 如果口令验证成功,fork+exec 产生一个shell (这个shell是在passwd中根据用户名和密码找得到shell存在的路径)。
- 如果失败,继续返回回去输入用户名密码。
- shell身份固定以后再继续做任何时候,都是带着用户权限的身份去做的了
3、权限相关的常用函数
(1)getuid( ) 返回进程实际进程ID
(2)geteuid( ) 返回进程的有效ID effective uid
(3)getgid( ) 获取当前进程的真实组ID
(4)getegid( ) 返回当前进程有效的组ID effective gid
(5)setuid( ) 设置有效ID
(6)setgid( ) 设置有效的组ID
(7)setreuid( ) 交换ruid 和 euid
(8)setregid( ) 交换rgid 和 egid
(9)seteuid( )
(10)setguid( )
七、解释器文件(脚本文件)
- 解释器文件是有Unix代表性的味道,因为Unix在讲机制,策略,总是在告诉你我能怎么做,我在怎么做,我能完成什么样的功能,但是不会告诉你我能做到什么程度。
什么叫脚本文件
-
脚本文件总是有一个脚本文件的标记,脚本文件其实不在乎后缀是什么,一般我们写shell脚本叫a.sh, Python脚本的话叫a.py, 当然这个后缀没什么关系
-
脚本文件的标记就是文件顶头有一个
#!
, 下面是你要用什么, 比如说 /bin/bash
创建一个名为aa.123
的脚本文件
#!/bin/bash
ls
whoami
cat /etc/shadow
ps
- 注意: 中间一条命令的执行失败不shell影响其他的命令执行
我们看下aa.123 的属性:
然后给他改变权限, 给他可执行权限 chmod u+x aa.123
去执行这个脚本:
刚刚这个脚本文件等同于下面的操作:
其中的 test 文件为:
-
脚本的优点就是: 我们有时候用c程序要写好大一段程序去解决的时候,shell几句话就解决了。不光是shell, Python脚本也一样,同时Python比shell 更灵活一些。
-
当你的shell看到一个脚本文件的时候,它对带脚本文件和别的程序不一样,其他的程序的话,就会将整个程序装在进来,如果在装载程序的时候发现,最前面是脚本标记,也就是#! , shell就不把当前所有内容都装载进来了,只在当前shell环境下来装载解释器文件(也就是/bin/bash),然后用指定的解释器解释全部的内容,包括第一行(第一行的井号刚好就是注释)。
-
这里的解释器不一定是shell, 我们把
/bin/bash
改成/bin/cat
, 结果如下:
运行结果:
当然,这也等同于:cat test
八、system( )函数
- 根据man手册查询可知,system( )函数就相当于一个 fork, exec, wait 的简单封装
- system( )函数的小例子:
#include <stdio.h>
#include <stdlib.h>
int main() {
system("date +%s > /tmp/out");
exit(0);
}
运行结果:
- 利用fork, exec, wait 的简单封装system函数:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
pid_t pid;
fflush(NULL);
pid = fork();
if (pid == 0) {
// 子进程
execl("/bin/sh", "sh", "-c", "date +%s > /tmp/out", NULL);
perror("execl()");
exit(1);
}
// 父进程等着收尸
wait(NULL);
exit(0);
}
运行结果:
可以看到前后是一样的效果
九、进程时间:times( )函数
-
time ./primer1
是打印执行primer1的时间 -
原理: 当前父进程在等子进程的时候在掐时间,所以说当前时间(等待子进程时间)包括等在子进程结束的时间,都会纳入到这个时间当中去。
-
times( ) 函数用来完成time命令的
在 man 手册中查询times( )函数:
计时单位:clock_t
滴答数, 一秒钟的滴答数可以用一个宏来检测,如下:
tms的四个时间碎片拼一起就是一个time命令。
十、守护进程
-
守护进程是生存期较长的一种进程,一般在系统启动时启动,系统关闭时停止,没有控制终端,后台运行。
-
守护进程可以理解成服务,一些系统级模块,开机启动的时候,需要进程一直在后台跑的,这种程序被称为守护进程,比如:Httpd守护进程, dhcp(动态分配IP地址), ssh服务,等等
-
当我们执行
ps
命令的时候,看到的一些后台跑的内容,有很多本身就是守护进程。无论是windows还是linux, 都是有守护进程。 -
守护进程一般满足如下条件:
(1)脱离控制终端(因为控制终端的输入和输出会对它有影响)
(2)会话的leader
(3)group的leader
1、会话 session (终端)
-
会话是一个或多个进程组的集合
-
我们平时接触的都是虚拟终端,实际意义上的终端是一个笨设备。只会两个操作,输入和输出。一般在银行这种要求安全性比较高的地方,可能会有真正意义上的终端存在。
-
在学并发这个模块的时候,就会发现,进程实际上就是容器,在我们的处理器处理当前的调度的时候,其实它是以线程为单位来调度的,所以认为多线程的并发要比多进程的并发要更规范
-
进程组分为:前台进程组+后台进程组,最多可以有一个前台进程组,可以没有前台进程组。
-
如果一个程序运行的会久一些,我不希望它占着我的命令行,终端,我还会做别的事情,那我在这条命令后面加一个地址符号,也就意味着,当前这条命令我要放到后台去运行。
-
前台进程组能够接受标准输入,能够标准输出,后台进程组不行。如果不区分前台进程组和后台进程组,我们不知道命令行输出的是哪个进程组的。
-
所以我们的守护进程的标准的输入和输出我们会进行重定向。
2、setsid( )设置为守护进程
-
如果当前调用这个函数的进程不是一个group leader的话,会创建一个新的session。调用这个函数的进程会成为新的session的leader。其实调用setsid()进程的特点就是守护进程的特点。
-
利用
ps axj
命令可以查看工作的进程
-
TTY表示的是控制终端,守护进程是脱离控制终端的, 所以打问号?的是守护进程。
-
利用setsid()之后,当前的进程会成为session的leader, 会成为group的leader, 所以说它的PID, PGID,SID是相同的。
-
由于守护进程在执行的时候,父进程会一直在等着,比如:FTP是从开机就开始,父进程一直在等着,既然子进程调用setsid()以后会变成守护进程,直接让父进程退出,父进程不需要收尸,如果父进程退出了,那么当前守护进程的父进程的PPID值就为1, init进程
十一、系统日志
略