-----在开始文章之前分享一个最近看到的一个编程项目技巧,也就是我们在实现一个小项目,是如何去实现它的功能的:


                        完成一个项目的流程:

                        1、明确项目目标

                        2、分析过程

                        3、逐步执行,代码实现

明确项目目标,是指我们希望程序达成什么目的,实现什么功能,从而帮我们将项目拆解成不同的单元;而一个妥当的拆解方案,难度适度递增,能帮我们逐步顺利执行,最终完成项目。这三个步骤可以说是环环相扣的(同时在这个过程中,我们要思考所需要的知识,以及如何去索取新的知识,找到切入点)。下面开始今天的主题解析:


一、进程状态:


1、进程的五种状态区别:


     (1)就绪态。这个进程当前所有运行条件就绪,只要得到了CPU时间就能直接运行(只差被CPU调度了)。

     (2)运行态。就绪态时得到了CPU调度就进入运行态开始运行。

     (3)僵尸态。子进程已经结束但是父进程还没来得及回收

     (4)等待态(浅度睡眠&深度睡眠),进程在等待某种条件,【条件成熟后可进入【就绪态】】。等待态下就算你给他CPU调度进程也无法执行。浅度睡眠等待时进程可以被(信号)唤醒,而深度睡眠等待时不能被唤醒,【只能等待的条件到了】才能结束睡眠状态。

     (5)暂停态。暂停并不是进程的终止,只是被别人(信号)暂停了,还可以恢复的。暂停状态收到信号后,进入就绪态。

Linux系统下进程编程之进程状态和守护进程解析(五)_子进程


注:进程刚fork出来的时候默认是进入就绪态的,运行,僵尸态,回收。进程调度的时候,linux操作系统是按照一定的时间片来调度的

-------- 时间片,简单说来,就是CPU分配给各个程序的运行时间,使各个程序从表面上看是同时进行的,而不会造成CPU资源浪费。总结:之所以进程之间要来回切换,操作系统要有这么多的CPU就是为了尽量充分的利用CPU的资源。这里 是一篇比较好的博客参考学习:


2、system函数介绍:


         老方法可以用man手册来查看它的用法: man 3 system:

Linux系统下进程编程之进程状态和守护进程解析(五)_子进程_02


函数说明:

1   #include <stdlib.h>
2
3 int system(const char *command);

注:system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数comm字符串所代表的命令。此命令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。这个可以看它的注释,说的非常清楚,哈哈哈,这里我就不多讲了(如果看的不是很明白,我在网上找到一篇很不错的博客,可以学习一下:


           同时system函数是原子操作。原子操作意思就是整个操作一旦开始就会不被打断的执行完。原子操作的好处就是不会被人打断(不会引来竞争状态),坏处是自己单独连续占用CPU时间太长影响系统整体实时性,因此应该尽量避免不必要的原子操作,就算不得不原子操作也应该尽量原子操作的时间缩短。下面我们来看一个使用system调用ls的例子:

1#include <stdlib.h>
2#include<stdio.h>
3 int main()
4 {
5 int i;
6 i=system("ls -al");
7 return 0;
8
9 }

输出结果:

Linux系统下进程编程之进程状态和守护进程解析(五)_守护进程_03


二、守护进程:


1、什么是守护进程?

          Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。

          守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机都保持运行。守护进程经常以超级用户(root)权限运行,因为它们要使用特殊的端口(1-1024)或访问某些特殊的资源。

         守护进程的父进程是init进程,因为它真正的父进程在fork出子进程后就先于子进程exit退出了,所以它是一个由init继承的孤儿进程。守护进程是非交互式程序,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都需要特殊处理。


简单的来理解:

    (1)daemon,表示守护进程,简称为d(进程名后面带d的基本就是守护进程)

    (2)长期运行(一般是开机运行直到关机时关闭)

   (3)与控制台(终端)脱离(普通进程都和运行该进程的控制台相绑定,表现为如果终端被强制关闭了则这个终端中运行的所有进程都被会关闭,背后的问题因素还在于会话,因为一个终端里面所有运行的进程的代表-----会话被关闭了)。


2、简单的几个常用进程查看命令:

      (1)进程查看命令ps:

Linux系统下进程编程之进程状态和守护进程解析(五)_进程组_04


       (2)ps -ajx      偏向显示进程各种有关的ID号:

Linux系统下进程编程之进程状态和守护进程解析(五)_守护进程_05


     (3)ps -aux    偏向显示进程各种占用资源:


Linux系统下进程编程之进程状态和守护进程解析(五)_守护进程_06


    (4)向进程发送信号指令kill:

           a、kill -信号编号 进程ID,向一个进程发送一个信号

           b、kill -9 xxx,将向xxx这个进程发送9号信号,也就是要结束进程


    3、创建守护进程步骤:


     1、首先在这之前要清楚一些基本概念:

       进程组 :


每个进程也属于一个进程组每个进程主都有一个进程组号,该号等于该进程组组长的PID号 .一个进程只能为它自己或子进程设置进程组ID号

       会话期:

                      会话期(session)是一个或多个进程组的集合。

                       setsid()函数可以建立一个对话期:

                      如果,调用setsid的进程不是一个进程组的组长,此函数创建一个新的会话期。

          (1)此进程变成该对话期的首进程

          (2)此进程变成一个新进程组的组长进程。

          (3)此进程没有控制终端,如果在调用setsid前,该进程有控制终端,那么与该终端的联系被解除。如果该进程是一个进程组的组长,此函数返回错误。

           (4)为了保证这一点,我们先调用fork()然后exit(),此时只有子进程在运行


     2、创建守护进程的主要步骤:


(1)子进程等待父进程退出

(2)子进程使用setsid函数创建新的会话期,脱离控制台

(3)调用chdir函数将当前进程工作目录设置为/    【chdir("/");】

(4)umask设置为0以取消任何文件权限屏蔽,使得进程具有最大的权限       【umask(0);】

 (5)关闭所有文件描述符,变成守护进程后其他打开的文件描述符就没什么用了。

 (6)将0、1、2三个文件描述符定位到/dev/null(也就是把这个进程的标准输入、标准输出和标准出错信息全部绑定到/dev/null)


       3、示例代码编写:

           任何一个进程都可以将自己实现成守护进程(守护进程并不是一个特别的东西);create_daemon函数要素(当一个进程只要调用create_daemon函数,就会使被调用的函数成为一个守护进程)

1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <fcntl.h>
7
8
9 void create_daemon(void);
10
11
12int main(void)
13{
14 create_daemon();
15
16
17while (1)
18{
19 printf("I am running.\n");
20
21 sleep(1);
22}
23
24return 0;
25 }
26
27
28 // 函数作用就是把调用该函数的进程变成一个守护进程
29 void create_daemon(void)
30 {
31 pid_t pid = 0;
32
33 pid = fork();
34 if (pid < 0)
35 {
36 perror("fork");
37 exit(-1);
38 }
39 if (pid > 0)
40 {
41 exit(0); // 父进程直接退出
42 }
43
44 // 执行到这里就是子进程
45
46 // setsid将当前进程设置为一个新的会话期session,目的就是让当前进程
47 // 脱离控制台。
48 pid = setsid();
49 if (pid < 0)
50 {
51 perror("setsid");
52 exit(-1);
53 }
54
55 // 将当前进程工作目录设置为根目录
56 chdir("/");
57
58 // umask设置为0确保将来进程有最大的文件操作权限
59 umask(0);
60
61 // 关闭所有文件描述符
62 // 先要获取当前系统中所允许打开的最大文件描述符数目
63 int cnt = sysconf(_SC_OPEN_MAX);
64 int i = 0;
65 for (i=0; i<cnt; i++)
66 {
67 close(i);
68 }
69
70 open("/dev/null", O_RDWR);
71 open("/dev/null", O_RDWR);
72 open("/dev/null", O_RDWR);
73
74}

输出结果,我们可以看到终端上看不到打印 "I am running",这说明我创建守护进程成功了:

Linux系统下进程编程之进程状态和守护进程解析(五)_守护进程_07

代码说明:

             这里sysconf函数和setsid函数的使用可以分别使用man手册来查看它们具体的用法,这里我就不举例了,可以按照上面system函数的用法来参考。


三、总结:

         今天的分享就到这里了,感谢大家的支持,如有写不对的地方,欢迎指出。

---欢迎关注公众号,可以查看往期的文章,可以得到三本经典的c语言进阶电子书:

Linux系统下进程编程之进程状态和守护进程解析(五)_进程组_08

Linux爱好者(对文章中写有不对的地方,可以批评指出,虚心向您学习,大家一起进步可以进群交流有关Linux的技术等话题,群里只能讨论技术,发广告,立刻飞机):