Linux系统编程第三部分:进程

主要内容:

1、进程标识符pid

2、进程的产生

3、进程的消亡及释放资源

4、exec函数族

5、用户权限及组权限

6、解释器文件

7、system()

8、进程会计

9、进程时间

10、守护进程

11、系统日志

1、进程标识符pid

类型:pid_t

查看进程的相关命令:ps

注意,别和文件描述符混淆,文件描述符优先使用最小可用的,而进程号是顺次向下使用。比如现在到了1000,下一个就是1001(就算前面有释放也会继续用下面的)

相关函数:

#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void);//获取当前进程的pid
pid_t getppid(void);//获取当前进程的父进程的pid
//没有失败,总是成功

2、进程的产生

fork

通过复制当前进程的方式创建一个新进程。(注意这个“复制”就是指一模一样,连执行到的位置都一模一样)

但是也有几个点不同,fork后父子进程的区别有:

1、fork的返回值不同

2、pid不同

3、ppid不同(这很好理解,被复制的进程的父进程是复制出它的那个进程,而复制出它的进程的父进程另有其人对吧)

4、未决信号和文件锁不继承(后面会讲到)

5、资源利用量归零

这里提一个进程:init进程:进程号为1,是所有进程的祖先进程

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);
//如果成功,会在父进程中返回子进程的进程号,在子进程中返回0
//如果失败,父进程中返回-1

现在写个简单的例子来创建子进程:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    pid_t pid;
    printf("[%d]:begin!\n", getpid());
    pid = fork();
    if (pid < 0) // 创建子进程失败
    {
        perror("fork()");
        exit(1);
    }
    else if (pid == 0) // 子进程
    {
        printf("[%d]:Child process is working!\n", getpid());
    }
    else // 父进程
    {
        printf("[%d]:Parent process is working!\n", getpid());
    }
    printf("[%d]:end!\n", getpid());
    exit(0);
}

运行结果:

liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ ./fork1
[5078]:begin!
[5078]:Parent process is working!
[5078]:end!
[5079]:Child process is working!
[5079]:end!

简单分析:begin只会打印一次,因为复制创建之后的位置从fork开始,而后面分开之后父子进程会分别执行他们的end

注意,不要想当然的去猜父进程先执行还是子进程先执行,这是由调度器的调度策略决定的,但是我可以这样来让子进程一定先执行:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    pid_t pid;
    printf("[%d]:begin!\n", getpid());
    pid = fork();
    if (pid < 0) // 创建子进程失败
    {
        perror("fork()");
        exit(1);
    }
    else if (pid == 0) // 子进程
    {
        printf("[%d]:Child process is working!\n", getpid());
    }
    else // 父进程
    {
        sleep(1);//这里我让父进程睡一秒,这样子进程一定先执行,但是不推荐使用sleep,这里我只是方便演示
        printf("[%d]:Parent process is working!\n", getpid());
    }
    printf("[%d]:end!\n", getpid());
    exit(0);
}

运行结果:可以发现子进程先执行

liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ ./fork1
[5137]:begin!
[5138]:Child process is working!
[5138]:end!
[5137]:Parent process is working!
[5137]:end!

但是接下来我们看个神奇的现象:

liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ ./fork1
[5579]:begin!
[5579]:Parent process is working!
[5579]:end!
[5580]:Child process is working!
[5580]:end!
liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ ./fork1 > ./tmpfork
liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ cat ./tmpfork
[5449]:begin!
[5449]:Parent process is working!
[5449]:end!
[5449]:begin!
[5450]:Child process is working!
[5450]:end!

如果我运行程序并把它重定向到一个文件里,查看文件的内容我们发现打印了两次begin!而且两次begin的进程号都是父进程的进程号。

这也是面试经常喜欢问到的东西,其实原因就是我们之前提到过的缓冲,标准输出是行缓冲,看到\n就会刷,因此我们如果不在begin那里加入\n的话,输出到终端的也会是两个begin,但是注意文件是全缓冲,就算我们加了\n,缓冲区也不会刷,因此begin只进入了缓冲区,并且携带的是父进程的进程号,然后分裂开后进入各自的缓冲区,然后分别刷到文件,因此有两次并且进程号都是父进程的进程号。

那么这就启发我们注意了,我们在fork之前一定要刷下该刷的流!!

我们修改试试:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    pid_t pid;
    printf("[%d]:begin!\n", getpid());
    fflush(NULL); // fork之前一定刷新下流!!!!!!!!!!!!!
    pid = fork();
    if (pid < 0) // 创建子进程失败
    {
        perror("fork()");
        exit(1);
    }
    else if (pid == 0) // 子进程
    {
        printf("[%d]:Child process is working!\n", getpid());
    }
    else // 父进程
    {
        // sleep(1);
        printf("[%d]:Parent process is working!\n", getpid());
    }
    printf("[%d]:end!\n", getpid());
    exit(0);
}

运行结果:没有问题,只有一次begin

liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ ./fork1 > ./tmpfork
liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ cat ./tmpfork
[5749]:begin!
[5749]:Parent process is working!
[5749]:end!
[5750]:Child process is working!
[5750]:end!

接下来我们来直观感受一下多进程的魅力:

我们先写一个已经很熟悉的程序:筛选质数

#include <stdio.h>
#include <stdlib.h>

#define LEFT 30000000
#define RIGHT 30000200

int main()
{
    int i, j, flag;
    for (i = LEFT; i <= RIGHT; ++i)
    {
        flag = 1;
        for (j = 2; j < i / 2; ++j)
        {
            if (i % j == 0)
            {
                flag = 0;
                break;
            }
        }
        if (flag)
        {
            printf("%d is a primer.\n", i);
        }
    }
    exit(0);
}

运行结果:

liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ ./primer0
30000001 is a primer.
30000023 is a primer.
30000037 is a primer.
30000041 is a primer.
30000049 is a primer.
30000059 is a primer.
30000071 is a primer.
30000079 is a primer.
30000083 is a primer.
30000109 is a primer.
30000133 is a primer.
30000137 is a primer.
30000149 is a primer.
30000163 is a primer.
30000167 is a primer.
30000169 is a primer.
30000193 is a primer.
30000199 is a primer.

我们用time命令看看程序执行的时间:我这里不想打印这些值,就重定向到一个空设备上方便只看时间

liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ time ./primer0 > /dev/null

real    0m0.918s
user    0m0.916s
sys     0m0.001s

好,这个时候我们开始思考,刚刚我的程序相当于是单机模式,一个人干活判断了201次数字是否是质数,那么我现在如果用201个人来同时判断呢,那么一个人就只用判断一个数即可,接下来来改写程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

#define LEFT 30000000
#define RIGHT 30000200

int main()
{
    int i, j, flag;
    pid_t pid;
    for (i = LEFT; i <= RIGHT; ++i)
    {
        pid = fork();
        flag = 1;
        if (pid < 0) // 创建子进程失败
        {
            perror("fork()");
            exit(1);
        }
        if (pid == 0) // 子进程,循环里的事情交给每一个子进程
        {
            for (j = 2; j < i / 2; ++j)
            {
                if (i % j == 0)
                {
                    flag = 0;
                    break;
                }
            }
            if (flag)
            {
                printf("%d is a primer.\n", i);
            }
            exit(0); // 注意这个退出进程非常重要,如果忽略了那就不是只创建201个进程了
            // 因为我之前提到过创建进程理解成复制,父子进程代码完全一样,子进程走完后面之后会回到上面最外层for循环
            // 子进程又会再fork,每个新的子进程同样会fork,最后会占满空间崩溃。
        }
    }
    exit(0);
}

运行结果:

liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ time ./primer1 > /dev/null

real    0m0.087s
user    0m0.001s
sys     0m0.024s

可以看到时间应该减少了许多,能体会到一丝并发的魅力了吗!

但是我们上面的程序还不够完善,没有涉及收尸等操作,举个例子:假如我让父进程sleep(1000),那么子进程全部会成为z(僵尸进程)。又或是让子进程sleep(1000),那么父进程结束退出后子进程成为孤儿进程,由init进程接管收尸。

僵尸进程:一个子进程结束了,但是他的父进程还活着,但该父进程没有调用 wait()/waitpid()函数来进行额外的处置,那么这个子进程就会变成一个僵尸进程。

孤儿进程:指的是在其父进程执行完成或被终止后仍继续运行的一类进程。

因此我们接下来讲进程的消亡及释放资源。

3、进程的消亡及释放资源

#include <sys/types.h>
#include <sys/wait.h>

//wait for process to change state

pid_t wait(int *wstatus);

pid_t waitpid(pid_t pid, int *wstatus, int options);

int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);


//wstatus可以用来存进程的状态,再配合宏可以查看更详细的信息,这里列举几个宏,完整版可以参考man手册
/*
	   WIFEXITED(wstatus)
              returns true if the child terminated normally, that is, by calling exit(3) or _exit(2), or by returning from main().

       WEXITSTATUS(wstatus)
              returns  the exit status of the child.  This consists of the least significant 8 bits of the status argument that the child specified in a call to exit(3) or _exit(2) or as the argument for a return state‐ment in main().  This macro should be employed only if WIFEXITED returned true.

       WIFSIGNALED(wstatus)
              returns true if the child process was terminated by a signal.

       WTERMSIG(wstatus)
              returns the number of the signal that caused the child process to terminate.  This macro should be employed only if WIFSIGNALED returned true.

*/

//pid_t的值不同时的含义
/*
	   < -1   meaning wait for any child process whose process group ID is equal to the absolute value of pid.

       -1     meaning wait for any child process.

       0      meaning wait for any child process whose process group ID is equal to that of the calling process at the time of the call to waitpid().

       > 0    meaning wait for the child whose process ID is equal to the value of pid

*/

//其实wait就是waitpid(-1,&wstatus,0)的封装

这里使用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, flag;
    pid_t pid;
    for (i = LEFT; i <= RIGHT; ++i)
    {
        pid = fork();
        flag = 1;
        if (pid < 0) // 创建子进程失败
        {
            perror("fork()");
            exit(1);
        }
        if (pid == 0) // 子进程,循环里的事情交给每一个子进程
        {
            for (j = 2; j < i / 2; ++j)
            {
                if (i % j == 0)
                {
                    flag = 0;
                    break;
                }
            }
            if (flag)
            {
                printf("%d is a primer.\n", i);
            }
            exit(0); // 注意这个退出进程非常重要,如果忽略了那就不是只创建201个进程了
            // 因为我之前提到过创建进程理解成复制,父子进程代码完全一样,子进程走完后面之后会回到上面最外层for循环
            // 子进程又会再fork,每个新的子进程同样会fork,最后会占满空间崩溃。
        }
    }
    //   int status;
    for (int n = LEFT; n <= RIGHT; ++n)
    {
        //        wait(&status);//如果要查看退出状态
        wait(NULL); // 这里我就不关心收回来的子进程的状态了
    }

    exit(0);
}

上述操作是根据任务数量(201个)来分配我们的进程数量(201个进程),那么我们现在在换一种方式,我们自定义进程数量,比如3个,来完成201个任务。

这里就涉及到了我们对于任务的分配操作了,一般来说有这么几种方式:

1、分块(把201个任务分为三块0-66 67-133 134-201,一个进程负责一块)

2、交叉分配(想象打麻将,轮流摸牌,一个进程一个进程的每次那一个任务)

3、池(这里涉及到了进程间通信竞争机制,后面再说)

我这里实现一下交叉分配:

#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, flag;
    pid_t pid;
    for (n = 0; n < N; ++n)
    {
        pid = fork(); // 创建子进程
        if (pid < 0) // 创建进程失败,这里我为了方便就比较简单的退出了,其实应该用循环来结束进程
        {
            perror("fork()");
            exit(1);
        }
        if (pid == 0)                              // 子进程
        {                                          // 开始干活
            for (i = LEFT + n; i <= RIGHT; i += N) // 交叉拿数字
            {
                flag = 1;
                for (j = 2; j < i / 2; ++j)
                {
                    if (i % j == 0) // 如果不是质数
                    {
                        flag = 0;
                        break;
                    }
                }
                if (flag) // 如果是质数
                {
                    printf("[%d]: %d is primer !\n", n, i); // 顺便把进程号打出来方便看是哪个进程计算的
                }
            }
            exit(0); // 子进程结束
        }
    }
    for (n = 0; n < N; ++n)
    {
        wait(NULL);
    }
    exit(0);
}

结果:

liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ ./primerN
[1]: 30000001 is primer !
[1]: 30000037 is primer !
[2]: 30000023 is primer !
[2]: 30000041 is primer !
[1]: 30000049 is primer !
[1]: 30000079 is primer !
[2]: 30000059 is primer !
[1]: 30000109 is primer !
[2]: 30000071 is primer !
[1]: 30000133 is primer !
[2]: 30000083 is primer !
[2]: 30000137 is primer !
[1]: 30000163 is primer !
[2]: 30000149 is primer !
[2]: 30000167 is primer !
[1]: 30000169 is primer !
[1]: 30000193 is primer !
[1]: 30000199 is primer !

细心的你肯定能发现,这些质数都是由1和2号进程算出来的,0号进程为什么没有呢,因为0号进程始终不可能拿到质数,它拿的都是3的倍数(0、3、6、9...)

并且结果也可看到有些大有些小(无序),这也是并发的一大特点。

4、exec函数族

作用:替换当前进程映像为新的进程映像

#include <unistd.h>

extern char **environ;

int execl(const char *pathname, const char *arg, ... /* (char  *) NULL */);
//pathname:新进程路径
//arg:可变参数,以NULL结尾

int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);
//file:进程的名字,这里可以直接用名字的原因在于extern char **environ环境变量
//arg:可变参数,以NULL结尾

int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */);
//和上面的一样,只是可以多char *const envp[]


int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
//其实下面三个没有加...的反而才是变参,因为上面三个最后以NULL结尾就固定了参数数量了

//成功是不返回的,已经替换进程了嘛,失败才会返回,且返回值为-1

下面写个例子来感受一下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// 命令格式:date +%s
int main()
{
    puts("begin !");
    execl("/bin/date", "date", "+%s", NULL); // 注意参数从argv[0]开始
    perror("execl()"); // 这里可以不用if来判断报错,因为如果执行了perror说明一定出错,正常情况下在execl那里就已经替换新的进程了
    exit(1);
    puts("end !");
    exit(0);
}

运行:

liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ ./ex
begin !
1710732535

“end !”是打印不出来的,因为在之前就已经替换成新的进程了

这里我们再这样运行试试:

liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ ./ex > /tmp/out
liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ cat /tmp/out
1710732756

哦豁,这次连begin也没了,为什么呢?和上面fork那里讲的面试题一样,因此一定记得:在fork或者exec之前fflush!

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// 命令格式:date +%s
int main()
{
    puts("begin !");
    fflush(NULL);                            // 刷新所有的流
    execl("/bin/date", "date", "+%s", NULL); // 注意参数从argv[0]开始
    perror("execl()"); // 这里可以不用if来判断报错,因为如果执行了perror说明一定出错,正常情况下在execl那里就已经替换新的进程了
    exit(1);
    puts("end !");
    exit(0);
}

运行:

liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ ./ex > /tmp/out
liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ cat /tmp/out
begin !
1710732899

没问题了对吧,但是这里可能我们又会有一些疑惑,那我这后面的程序不就没用了吗,进程都被替换了。因此我们这里加上之前的fork,让子进程去替换新进程,把它变成一个标准的程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

// 命令格式:date +%s
int main()
{
    pid_t pid;
    puts("begin !");
    fflush(NULL); // 刷新所有的流
    pid = fork();
    if (pid < 0) // 创建子进程出错
    {
        perror("fork()");
        exit(1);
    }
    if (pid == 0) // 子进程
    {
        execl("/bin/date", "date", "+%s", NULL); // 注意参数从argv[0]开始
        perror("execl()");
        exit(1);
    }
    wait(NULL); // 父进程收尸
    puts("end !");
    exit(0);
}

运行:

liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ ./ex
begin !
1710733441
end !

这下end也打印出来了!

ok,接下来我们来实现一个属于我们自己的shell吧!

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <glob.h>

#define DELIMS " \t\n" // 定义分割

struct cmd_st // 为了程序的可拓展性和灵活性,这里专门抽象出来一个结构体
{
    glob_t globres;
};

static void display()
{
    printf("liugenyi@mysh:");
}

static void parse(const char *line, struct cmd_st *res)
{
    char *tok;
    int i = 0;
    while (1)
    {
        tok = strsep(&line, DELIMS); // 按分割符进行分割输入的命令字符串
        if (tok == NULL)
        {
            break;
        }
        if (tok[0] == '\0')
        {
            continue;
        }
        glob(tok, GLOB_NOCHECK | GLOB_APPEND * i, NULL, &res->globres); // 这里我们使用glob的GLOB_NOCHECK来存解析出来的字符,并且使用GLOB_APPEND追加,但是注意,第一次我们不追加,后续再追加
        i = 1;                                                          // 只用作第一次GLOB_APPEND不追加,i初始为0后续变为1后glob变为追加模式
    }
}

int main()
{
    struct cmd_st cmd;
    pid_t pid;
    char *linebuf = NULL;
    size_t linebufsize = 0;
    while (1)
    {
        display();                                      // 打印前置信息
        if (getline(&linebuf, &linebufsize, stdin) < 0) // 从终端获取输入的命令
        {
            break;
        }
        parse(linebuf, &cmd); // 命令解析
        if (0)                // 如果是内部命令,这里我们先只实现解析外部命令,解析内部命令的知识还不够,后面再补充
        {
        }
        else // 如果是外部命令
        {
            pid = fork(); // 开始创建进程并分配任务
            if (pid < 0)  // 创建失败
            {
                perror("fork()");
                exit(1);
            }

            if (pid == 0) // 子进程
            {
                execvp(cmd.globres.gl_pathv[0], cmd.globres.gl_pathv); // 子进程执行命令,这儿肯定使用下面三种类型的exec,因为不指定NULL
                perror("execvp()");
                exit(1);
            }
            else
            {
                wait(NULL); // 父进程收尸,继续循环即可
            }
        }
    }
    exit(0);
}

运行:

liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ ./mysh
liugenyi@mysh:ls
ex  ex.c  fork1  fork1.c  mysh  mysh.c  primer0  primer0.c  primer1  primer1.c  primerN  primerN.c  tmpfork
liugenyi@mysh:whoami
liugenyi
liugenyi@mysh:pwd
/home/liugenyi/linuxsty/process
liugenyi@mysh:

5、用户权限以及组权限

#include <unistd.h>
#include <sys/types.h>
uid_t getuid(void); // getuid() returns the real user ID of the calling process.
uid_t geteuid(void); // geteuid() returns the effective user ID of the calling process.



#include <unistd.h>
#include <sys/types.h>
gid_t getgid(void); // getgid() returns the real group ID of the calling process.
gid_t getegid(void); // getegid() returns the effective group ID of the calling process.


#include <sys/types.h>
#include <unistd.h>
int setuid(uid_t uid); //  sets the effective user ID of the calling process.
int setgid(gid_t gid); // setgid()  sets  the  effective group ID of the calling process.


#include <sys/types.h>
#include <unistd.h>
int setreuid(uid_t ruid, uid_t euid); //  setreuid() sets real and effective user IDs of the calling process.
int setregid(gid_t rgid, gid_t egid); // setregid() sets real and effective group ID's of the calling process.



#include <sys/types.h>
#include <unistd.h>
int seteuid(uid_t euid); // seteuid() sets the effective user ID of the calling process.
int setegid(gid_t egid); // setegid() sets the effective group ID of the calling process.

6、解释器文件

略,了解即可,其实就是脚本

7、system()

#include <stdlib.h>
int system(const char *command); 
// The system() library function uses fork(2) to create a child process that executes the shell command specified in command using execl(3) as follows:
// execl("/bin/sh", "sh", "-c", command, (char *) NULL);

其实就是fork+exec+wait的封装

8、进程会计

#include <unistd.h>

int acct(const char *filename); // 进程终止的时候会把相关的信息记录
// The  acct() system call enables or disables process accounting.  If called with the name of an existing file as its argument, accounting is turned on, and records for each terminating process are appended to filename as it terminates.  An argument of NULL causes accounting to be turned off.

9、进程时间

#include <sys/times.h>

clock_t times(struct tms *buf); // times() stores the current process times in the struct tms that buf points to.  The struct tms is as defined in <sys/times.h>:


/*
	struct tms {
               clock_t tms_utime;   // user time 
               clock_t tms_stime;   // system time
               clock_t tms_cutime;  // user time of children
               clock_t tms_cstime;  // system time of children
           };
*/

10、守护进程

守护进程是一类在后台运行的特殊进程,用于执行特定的系统任务。它独立于任何终端会话。

守护进程有什么样的特点?因为它不会有特定的父进程收尸它,因此他的父进程pid应该是init进程(进程号为0),并且它的pid、gpid、sid都相同,然后TTY(控制终端的id)为?

例如:其中下面三个就是守护进程。

liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ ps axj
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
   733     785     733     733 ?             -1 S      115   0:00 avahi-daemon: chroot helper
     1     831     831     831 ?             -1 Ssl      0   0:33 /usr/sbin/ModemManager
     1     885     885     885 ?             -1 Ssl      0   0:00 /usr/bin/python3 /usr/share/unattended-upgrades/unattended-upgrade-shutdown --wait-for-signal
     1     907     907     907 ?             -1 Ssl      0   0:05 /usr/sbin/gdm3

和守护进程相关的函数:

#include <sys/types.h>
#include <unistd.h>
pid_t setsid(void);
//setsid()  creates  a new session if the calling process is not a process group leader.  The calling process is the leader of the new session (i.e., its session ID is made the same as its process ID).  The calling process also becomes the process group leader of a new process group in the session (i.e., its process group ID is made the same as its process ID).



#include <sys/types.h>
#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);
pid_t getpgid(pid_t pid);

pid_t getpgrp(void);                 /* POSIX.1 version */
pid_t getpgrp(pid_t pid);            /* BSD version */

int setpgrp(void);                   /* System V version */
int setpgrp(pid_t pid, pid_t pgid);  /* BSD version */
//setpgid, getpgid, setpgrp, getpgrp - set/get process group

下面我们来实现一个守护进程,用于向某个文件每秒写入自增整数:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define FNAME "/tmp/out" // 写的文件路径

static int daemonize(void)
{
    pid_t pid;
    int fd;
    pid = fork();
    if (pid < 0) // 创建进程失败
    {
        perror("fork()");
        return -1;
    }
    if (pid == 0) // 子进程
    {
        fd = open("/dev/null", O_RDWR);
        if (fd < 0) // 文件打开失败
        {
            perror("open()");
            return -1;
        }
        // 继续操作
        // 重定向0、1、2文件描述符
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        if (fd > 2)
        {
            close(fd);
        }
        setsid();   // 创建守护进程
        chdir("/"); // 更改工作目录到根目录(也可以不做这个操作,这里是觉得一般根目录不会出什么问题)
        return 0;
    }
    exit(0); // 父进程直接退出,不收尸
}

int main()
{
    int i;
    FILE *fp;
    if (daemonize() < 0)
    {
        exit(1);
    }
    fp = fopen(FNAME, "w");
    if (fp == NULL)
    {
        perror("fopen()"); // 其实这里用perror没用,因为守护进程脱离终端,这里先这么写着,后面重构
        exit(1);
    }
    for (i = 0;; ++i)
    {
        fprintf(fp, "%d\n", i); // 注意文件是全缓冲,换行符不会刷流,因此进行fflush操作
        fflush(NULL);
        sleep(1); // 每秒写一次
    }
    exit(0);
}

运行一下看看结果:

liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ make daemon
cc     daemon.c   -o daemon
liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ ./daemon
liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ ps axj
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
   1  128892  128892  128892 ?             -1 Ss    1000   0:00 ./daemon
liugenyi@liugenyi-virtual-machine:~/linuxsty/process$ tail -f /tmp/out
104
105
106
107
108
109
110

11、系统日志

它位于我们的/var/log路径,系统日志是很重要的,但是又不能让任意的人随意去写系统日志,因此要做一个权限分割,这里可以直接用一个叫做syslogd的服务,我们只需要往这个服务里书写日志,这个服务就会帮我们统一写进去,只有syslogd才有权限书写系统日志。

和系统日志有关的函数:

#include <syslog.h>

void openlog(const char *ident, int option, int facility);//建立和log服务的连接
/*
	*ident:人物/身份
	option:特殊要求
	facility:来源
	
	   Values for option
       The option argument to openlog() is a bit mask constructed by ORing together any of the following values:

       LOG_CONS       Write directly to the system console if there is an error while sending to the system logger.

       LOG_NDELAY     Open  the  connection  immediately (normally, the connection is opened when the first message is logged).  This may be useful, for example, if a subsequent chroot(2) would make
                      the pathname used internally by the logging facility unreachable.

       LOG_NOWAIT     Don't wait for child processes that may have been created while logging the message.  (The GNU C library does not create a child process,  so  this  option  has  no  effect  on
                      Linux.)

       LOG_ODELAY     The converse of LOG_NDELAY; opening of the connection is delayed until syslog() is called.  (This is the default, and need not be specified.)

       LOG_PERROR     (Not in POSIX.1-2001 or POSIX.1-2008.)  Also log the message to stderr.

       LOG_PID        Include the caller's PID with each message.
       
       
       Values for facility
       The facility argument is used to specify what type of program is logging the message.  This lets the configuration file specify that messages from different facilities will be handled differ‐
       ently.

       LOG_AUTH       security/authorization messages

       LOG_AUTHPRIV   security/authorization messages (private)

       LOG_CRON       clock daemon (cron and at)

       LOG_DAEMON     system daemons without separate facility value

       LOG_FTP        ftp daemon

       LOG_KERN       kernel messages (these can't be generated from user processes)
       
       LOG_LOCAL0 through LOG_LOCAL7       reserved for local use

       LOG_LPR        line printer subsystem

       LOG_MAIL       mail subsystem

       LOG_NEWS       USENET news subsystem

       LOG_SYSLOG     messages generated internally by syslogd(8)

       LOG_USER (default)            generic user-level messages
       LOG_UUCP       UUCP subsystem
	
*/
void syslog(int priority, const char *format, ...);//写日志
/* 和printf的用法差不多
	priority:优先级(级别)
	format:格式

	Values for level
       This determines the importance of the message.  The levels are, in order of decreasing importance:

       LOG_EMERG      system is unusable

       LOG_ALERT      action must be taken immediately

       LOG_CRIT       critical conditions

       LOG_ERR        error conditions

       LOG_WARNING    warning conditions

       LOG_NOTICE     normal, but significant, condition
       
       LOG_INFO       informational message

       LOG_DEBUG      debug-level message

       The function setlogmask(3) can be used to restrict logging to specified levels only.

*/
void closelog(void);//关闭和log服务的连接

下面我们对上面的守护进程的程序进行优化,书写日志:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <syslog.h>
#include <errno.h>
#include <string.h>

#define FNAME "/tmp/out"

static int daemonize(void)
{
    pid_t pid;
    int fd;
    pid = fork();
    if (pid < 0) // 创建进程失败
    {
        // perror("fork()"); 有了下面的系统日志就不用再重复报错了
        return -1;
    }
    if (pid == 0) // 子进程
    {
        fd = open("/dev/null", O_RDWR);
        if (fd < 0) // 文件打开失败
        {
            // perror("open()"); 有了下面的系统日志就不用再重复报错了
            return -1;
        }
        // 继续操作
        // 重定向0、1、2文件描述符
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        if (fd > 2)
        {
            close(fd);
        }
        setsid();   // 创建守护进程
        chdir("/"); // 更改工作目录到根目录(也可以不做这个操作,这里是觉得一般根目录不会出什么问题)
        return 0;
    }
    exit(0); // 父进程直接退出,不收尸
}

int main()
{
    int i;
    FILE *fp;
    openlog("gengenshuai", LOG_PID, LOG_DAEMON);
    if (daemonize() < 0)
    {
        syslog(LOG_ERR, "daemonize() failed!"); // 如果失败,进行系统日志的书写
        exit(1);
    }
    else
    {
        syslog(LOG_INFO, "daemonize() successded!"); // 如果成功,也进行系统日志的书写
    }
    fp = fopen(FNAME, "w");
    if (fp == NULL)
    {
        // perror("fopen()"); // 其实这里用perror没用,因为守护进程脱离终端,这里先这么写着,后面重构
        syslog(LOG_ERR, "fopen():%s", strerror(errno));
        exit(1);
    }
    // 如果文件打开成功
    syslog(LOG_INFO, "%s open successded!", FNAME);
    for (i = 0;; ++i)
    {
        fprintf(fp, "%d\n", i); // 注意文件是全缓冲,换行符不会刷流,因此进行fflush操作
        fflush(NULL);
        sleep(1);
    }
    exit(0);
}

运行后查看日志文件,这里的日志文件位置以及名字不同的操作系统不相同,我这里是ubuntu,主日志文件是syslog

liugenyi@liugenyi-virtual-machine:/var/log$ tail syslog
Apr 10 17:13:57 liugenyi-virtual-machine systemd[1]: Starting Network Manager Script Dispatcher Service...
Apr 10 17:13:57 liugenyi-virtual-machine dbus-daemon[737]: [system] Successfully activated service 'org.freedesktop.nm_dispatcher'
Apr 10 17:13:57 liugenyi-virtual-machine systemd[1]: Started Network Manager Script Dispatcher Service.
Apr 10 17:14:08 liugenyi-virtual-machine systemd[1]: NetworkManager-dispatcher.service: Succeeded.
Apr 10 17:17:02 liugenyi-virtual-machine CRON[130750]: (root) CMD (   cd / && run-parts --report /etc/cron.hourly)
Apr 10 17:24:44 liugenyi-virtual-machine gengenshuai[131007]: daemonize() successded!
Apr 10 17:24:45 liugenyi-virtual-machine gengenshuai[131007]: /tmp/out open successded!

可以看到最后两行我们自己书写的系统日志信息。

  • 30
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值