LinuxC—进程

news2024/11/26 23:16:17

进程

1 进程标识符pid

基本概念

类型pid_t,是一个有符号16位整数,进程号是顺次向下使用(fd是优先使用当前可用最小的)

shell中的ps命令能够查看当前系统的进程信息快照

相关函数

  • getpid(2)获取当前进程的进程号
/* Get the process ID of the calling process.  */
extern __pid_t getpid (void) __THROW;
  • getppid(2)获取当前进程父进程的进程号
/* Get the process ID of the calling process's parent.  */
extern __pid_t getppid (void) __THROW;

2 进程的产生——fork(2)

fork(2)

  • fork() 创建一个子进程,并返回其pid
/* Clone the calling process, creating an exact copy.
   Return -1 for errors, 0 to the new process,
   and the process ID of the new process to the old process.  */
extern __pid_t fork (void) __THROWNL;
  • fork()是通过复制的方式创建一个和父进程几乎一样的进程,连执行到的代码都一样,这两个进程不一样的的地方如下:

    1. fork()的返回值不一样
    2. 父子进程的pid,ppid不同
    3. 未决信号和文件锁不继承
    4. 资源利用量清0
  • 返回值:

    • 失败返回给父进程-1
    • 成功返回给父进程子进程的pid,子进程中返回0
  • 注意fork()对于内存空间采用了写时复制的技术,就是说在创建子进程时,并没有将父进程的物理内存完全复制一份,而是将其页表复制了一份,并将这块内存的权限设置为只读,当父进程或子进程要对某块内存执行写操作的时候就会触发CPU的写保护中断,操作系统就会执行写保护中断函数,在这个函数中会将执行写操作的数据页复制一份,并重新设置其内存映射关系后将父子进程的这些数据页设置为可读写的

  • 示例1:

int main(int argc, char **argv) {

    pid_t pid;
    printf("[%d]:Begin!\n", getpid());
    fflush(NULL); //在fork()前要刷新所有的流

    pid = fork();
    if (pid < 0) {
        perror("fork():");
        exit(1);
    }
    sleep(1);
    if (pid == 0) {
        printf("[%d]:Child is working...\n", getpid());
    } else {
        printf("[%d]:Parent is working...\n", getpid());
    }

    printf("[%d]:End!\n", getpid());

    exit(0);

}

输出结果:可以看到两个进程出现了交替执行的情况,在实际生产环境中,永远不要凭空猜测父子进程哪一个先运行,这是由调度器的调度策略决定的

[8184]:Begin!
[8184]:Parent is working...
[8185]:Child is working...
[8184]:End!
[8185]:End!

注意:在调用fork()前一定要执行fflush()刷新所有的流,若没有fflush()则输出结果如下:可以看到begin执行的两次,且进程号都是父进程的,因为当我们将./main执行结果重定向到out文件中的时候就不再是stdout了,也就不再是行缓冲,而是全缓冲,由于子进程将父进程的内存复制一份,所以缓冲区中的"[10397]:Begin!"也复制了一份

root@VM-24-2-ubuntu:/home/ubuntu/linux# ./main > ./out
root@VM-24-2-ubuntu:/home/ubuntu/linux# cat out
[10397]:Begin!
[10397]:Parent is working...
[10397]:End!
[10397]:Begin!
[10398]:Child is working...
[10398]:End!
  • 示例2:输出30000000到30000200之间的质数

    • 单进程环境下:
    #define LEFT 30000000
    #define RIGHT 30000200
    int main(int argc, char **argv) {
    
        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命令查看输出的时间:

    root@VM-24-2-ubuntu:/home/ubuntu/linux# time ./main > /dev/null
    
    real	0m0.829s
    user	0m0.829s
    sys		0m0.000s
    
    • 多进程情况,每得到一个i就交给一个进程来计算
    #define LEFT 30000000
    #define RIGHT 30000200
    int main(int argc, char **argv) {
    
        int i, j, mark;
        pid_t pid;
    
        for (i = LEFT; i <= RIGHT; i++) {
            mark = 1;
            pid = fork();
            if (pid < 0) {
                perror("fork():");
                exit(1);
            }
            if (pid == 0) {
                for (j = 2; j < i / 2; j++) {
                    if (i % j == 0) {
                        mark = 0;
                        break;
                    }
                }
                if (mark) {
                    printf("%d is a primer\n", i);
                }
                break; //一定得break,不然每个子进程又会进行外层循环从而到再次创建进程,最终可能导致系统崩溃
            }
        }
    
        exit(0);
    
    }
    

    通过time命令查看输出的时间:可以看到real time减少了非常多,同时内核态执行的时间大幅增加

    root@VM-24-2-ubuntu:/home/ubuntu/linux# time ./main > out
    
    real	0m0.058s
    user	0m0.000s
    sys		0m0.009s
    

init进程

init进程:1号进程,是所有进程的祖先进程

3 进程的消亡及释放资源

相关函数

  • wait() 将收尸的子进程的状态放到wstatus中去,该函数是阻塞
/* Wait for a child to die.  When one does, put its status in *wstatus
   and return its process ID.  For errors, return (pid_t) -1.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern __pid_t wait (int *wstatus);
  • waitpid() 收尸指定pid的进程,options提供了一些选项来指定wait的状态(阻塞?非阻塞等)
/* Wait for a child matching PID to die.
   If PID is greater than 0, match any process whose process ID is PID.
   If PID is (pid_t) -1, match any process. //收取任何一个子进程
   If PID is (pid_t) 0, match any process with the //收取同一组中该进程的子进程
   same process group as the current process.
   If PID is less than -1, match any process whose //收取PID绝对值对应进程组中的子进程
   process group is the absolute value of PID.
   If the WNOHANG bit is set in OPTIONS, and that child
   is not already dead, return (pid_t) 0.  If successful,
   return PID and store the dead child's status in wstatus.
   Return (pid_t) -1 for errors.  If the WUNTRACED bit is
   set in OPTIONS, return status for stopped children; otherwise don't.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern __pid_t waitpid (__pid_t __pid, int *wstatus, int __options);

options

The value of options is an OR of zero or more of the following constants:

WNOHANG     return immediately if no child has exited.

WUNTRACED   also return if a child has stopped (but not traced via ptrace(2)).  Status for traced children which have stopped is provided even if this option is not specified.

WCONTINUED (since Linux 2.6.10)
also return if a stopped child has been resumed by delivery of SIGCONT.

wait函数就等于waitpid(-1, &wstatus, 0)

  • waitid()
/* Wait for a childing matching IDTYPE and ID to change the status and
   place appropriate information in *INFOP.
   If IDTYPE is P_PID, match any process whose process ID is ID.
   If IDTYPE is P_PGID, match any process whose process group is ID.
   If IDTYPE is P_ALL, match any process.
   If the WNOHANG bit is set in OPTIONS, and that child
   is not already dead, clear *INFOP and return 0.  If successful, store
   exit code and status in *INFOP.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int waitid (idtype_t __idtype, __id_t __id, siginfo_t *__infop,
         int __options);

wstatus的判断

拿到wstatus后可以用以下宏来判断子进程的状态

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 argu‐
ment for a return statement 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.

WCOREDUMP(wstatus)
returns true if the child produced a core dump.  This macro should be employed only if WIFSIGNALED returned true.
This macro is not specified in POSIX.1-2001 and is not available on some UNIX implementations (e.g., AIX, SunOS).  Therefore, enclose its use inside #ifdef WCOREDUMP ... #endif.

WIFSTOPPED(wstatus)
returns true if the child process was stopped by delivery of a signal; this is possible only if the call was done using WUNTRACED or when the child is being traced (see ptrace(2)).

WSTOPSIG(wstatus)
returns the number of the signal which caused the child to stop.  This macro should be employed only if WIFSTOPPED returned true.

WIFCONTINUED(wstatus)
(since Linux 2.6.10) returns true if the child process was resumed by delivery of SIGCONT.

示例1——wait()函数

int main(int argc, char **argv) {

    int i;
    pid_t pid;

    for (i = 0; i <= 2; i++) {
        pid = fork();
        if (pid < 0) {
            perror("fork():");
            exit(1);
        }
        if (pid == 0) {
            printf("我是子进程【%d】\n", getpid());
            exit(0);
        }
    }

    for (i = 0; i <= 2; i++) {
        //pid = wait(&st);
        pid = wait(NULL);
        printf("【%d】子进程退出了\n", pid);
    }

    printf("主进程退出...\n");

    exit(0);

}

执行结果:可以看到主进程在所有子进程退出后才退出

我是子进程【1046】
【1046】子进程退出了
我是子进程【1047】
我是子进程【1048】
【1047】子进程退出了
【1048】子进程退出了
主进程退出..

示例2——waitpid()

int main(int argc, char **argv) {

    int i;
    pid_t pid;

    for (i = 0; i <= 2; i++) {
        pid = fork();
        if (pid < 0) {
            perror("fork():");
            exit(1);
        }
        if (pid == 0) {
            printf("我是子进程【%d】\n", getpid());
            exit(0);
        }
    }

    for (i = 0; i <= 2; i++) {
        while((pid = waitpid(-1, NULL, WNOHANG)) == 0) {
            printf("我是父进程,接着做我的事了....");
        };
        printf("【%d】子进程退出了\n", pid);
    }

    printf("主进程退出...\n");

    exit(0);

}

这个程序会打印非常多的"我是父进程,接着做我的事了…",说明了加上WNOHANG参数后waitpid是非阻塞的

wait(),waitpid()函数的作用

  • 进程创建子进程的目的可能是需要子进程分担一些任务,使用wait函数能够使得父进知道子进程的工作状态从而做出后序操作
  • 保证父进程在所有子进程退出后才退出
  • 当子进程执行结束但是内存中代表进程的数据结构还没被回收时,该子进程就是一个僵尸进程,若父进程也结束了那么该僵尸进程会被init进程领养,使用wait函数就能够回收僵尸子进程,从而避免内存泄漏,主要是能够回收其pid(pid是有限的)

4 进程分配

之前3.2中计算质数的任务时对于每个质数都创建了一个进程来进行计算,进程的创建和销毁以及上下文切换是需要时间的,创建这么多进程,每个进程又只完成计算一个数的任务,这种分配可能是得不偿失,出现进程状态转变所需时间比实际计算任务时间大很多的情况,所以我们可以考虑其他的任务分配方式,比如将这个201个数的计算分配给N个进程,每个进程指向若干个数的计算。分配方法有如下几种:

  • 分块分配,将201个数顺序分为N块,每个进行计算某一块的数据

  • 交叉分配:

    比如现在N=3,那么1号进程分配201,然后202分配给2号进程,203分配给3号进程,204又分配给1号进程,以此类推

  • 使用池:上游一个进程创建一个任务池,将这201个任务挨个放到任务池中,下游有N个进程在任务池中抢任务来执行

示例——较差分配法

#define LEFT 30000000
#define RIGHT 30000200
#define N 3

int main(int argc, char **argv) {

    int i, j, k, mark;
    pid_t pid;

    for (i = 0; i < N; i++) {
        pid = fork();
        if (pid < 0) {
            perror("fork():");
            //某个进程创建失败,需要先将已创建的进程回收后再退出
            for (k = 0; k < i; k++) {
                wait(NULL);
            }
            exit(1);
        }
        if (pid == 0) {
            for (j = LEFT + i; j <= RIGHT; j += N) {
                mark = 1;
                for (k = 2; k < j / 2; k++) {
                    if (j % k == 0) {
                        mark = 0;
                        break;
                    }
                }
                if (mark) printf("【%d】%d is a primer\n", getpid(), j);
            }
            exit(0);
        }
    }
    for (i = 0; i < N; i++) {
        wait(NULL);
    }

    exit(0);

}

结果:可以看到数是随机的,同时第一个子进程没有打印任何数,可见这种分配方式对于该问题还是不是很理想

【7557】30000001 is a primer
【7558】30000023 is a primer
【7557】30000037 is a primer
【7558】30000041 is a primer
【7558】30000059 is a primer
【7557】30000049 is a primer
【7558】30000071 is a primer
【7557】30000079 is a primer
【7558】30000083 is a primer
【7557】30000109 is a primer
【7558】30000137 is a primer
【7557】30000133 is a primer
【7558】30000149 is a primer
【7557】30000163 is a primer
【7558】30000167 is a primer
【7557】30000169 is a primer
【7557】30000193 is a primer
【7557】30000199 is a primer

5 exec(3)函数族

问题引入

之前写的程序,main进程会创建若干个相同的main进程,需要注意的是main进程是有shell(bash)进程创建的,但是可以发现这两者是完全不一样的

exec函数族

  • 函数定义
#include <unistd.h>

extern char **environ;
//用一个给出路径的的可执行文件来替换当前进程,arg是指需要传入的参数,注意这里的arg是从arg[0]开始
int execl(const char *path, const char *arg, ...
          /* (char  *) NULL */);
//用一个给出文件名的可执行文件(有环境变量来找到这个可执行文件的具体位置)来替换当前进程,arg是指需要传入的参数
int execlp(const char *file, const char *arg, ...
           /* (char  *) NULL */);
int execle(const char *path, const char *arg, ...
           /*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
  • 描述 用一个新的进程替换当前进程
The exec() family of functions replaces the current process image with a new process image.  The functions described in this manual page are front-ends for execve(2).  (See the manual page
for execve(2) for further details about the replacement of the current process image.)
  • 返回值:

    有返回值则表示出错没有返回值则表示已经被一个新的进程映像替换掉了

  • 注意点:
    和fork()一样,在调用前一定要调用fflush()刷新所有的流

示例:实现data+%s的功能

/**
 * 实现data+%s打印时戳的功能
 */
int main(int argc, char **argv) {

    puts("Begin!");

    fflush(NULL); //!!!

    execl("/bin/date", "date", "+%s", NULL);
    perror("excel()");
    exit(1);

    puts("End!");

    exit(0);

}

需要注意的是这个程序仍然存在一个问题,就是原进程做个很多事后突然变成了其他进程,那些事等于白做了,而且新进程和原进程的pid还是同一个,所以加上fork/wait函数后做以下改进:

/**
 * 实现data+%s打印时戳的功能
 */
int main(int argc, char **argv) {

    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);
        perror("excel():");
        exit(1);
    }

    wait(NULL);
    puts("End!");

    exit(0);

}

这时就相当于创建一个子进程,该子进程的任务就是执行/exec/date

6 shell命令实现——few

shell终端就是通过fork()-execXX()-wait()命令来实现的,一个简单的shell(bash)实现如下:

具体过程就是bash进程获取一行命令行的内容,然后对其进行解析,解析后就获取到了要执行的命令名和执行该命令用到的参数,然后就fork()子进程后调用execXX()命令后由该子进程来全权负责执行解析出来的命令,对于父进程(bash)就在此处wait子进程,子进程执行完后就经过循环进入下一个命令行

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

#define DELIMTS " \t\n"

typedef struct {
    glob_t globres;
} cmd_st;

//命令提示符
static void prompt(void) {
    printf("myShell-0.1$: ");
}

//解析命令
static void parse(char *line, cmd_st *res) {
    char *tok;
    int i = 0;
    while (1) {
        tok = strsep(&line, DELIMTS);
        if (tok == NULL)  break;
        //解析到空串""
        if (tok[0] == '\0') continue;
        /**
         * 这里碰到一个问题,我们需要一个char*类型的数组来帮助我们存储解析出来的参数,而这个参数的个数是不		  * 定的,所以需要一个结构来帮助我们存储这些参数,这时候就应该联想到main函数参数的存储,又可以联系到		   * 有一个函数能够给我们实现这样的数据存储,就是glob(),里面的glob_t就是一个类main函数参数的结构			 * 体,这个函数在flags中有GLOB_NOCHECK时表示当我们不能找到与pattern相匹配的参数时就直接存储这个		  * pattern
         */
        glob(tok, GLOB_NOCHECK | GLOB_APPEND * i, NULL, &res->globres);
        i++;
    }
}

/**
 * 实现shell执行外部命令的功能
 */
int main(int argc, char **argv) {

    pid_t pid;
    char *linebuf;
    size_t linebuf_size = 0;
    cmd_st cmd;
    while (1) {
        //1 命令提示符
        prompt();
        //2 获取一行的输入
        if (getline(&linebuf, &linebuf_size, stdin) < 0) {
            break;
        }
        parse(linebuf, &cmd);
        //3 对命令进行解析
        if (0) { //3.1 内部命令

        } else { //3.2 外部命令
            pid = fork();
            if (pid < 0) {
                perror("fork():");
                exit(1);
            } else if (pid == 0) { //子进程
                execvp(cmd.globres.gl_pathv[0], cmd.globres.gl_pathv);
                perror("execvp():");
                exit(1);
            } else { //父进程
                wait(NULL);
            }
        }
    }

    exit(0);

}

7 用户权限和组权限

相关概念

linux是一个多用户多任务系统,也就是说在某一个时刻可以有多个用户登录linux系统后来执行相关的任务,为了便于管理这些用户,linux引入了用户和组的概念,每个用户有自己的相关权限,同一个用户组中的用户又具有相同的权限,用户和用户组的关系如下图所示:

Linux 用户和组

用户和组ID又分为三种,分别是real/effective/saved set,其中effective id决定了进程是否有访问某个文件的权限,三种id用法如下:

在这里插入图片描述

相关函数

  • getuid(2) 返回当前进程的实际用户id
/* Get the real user ID of the calling process.  */
extern __uid_t getuid (void) __THROW;
  • geteuid(2) 返回当前进程的有效用户id
/* Get the effective user ID of the calling process.  */
extern __uid_t geteuid (void) __THROW;
  • getgid(2) 返回当前进程的实际组id
/* Get the real group ID of the calling process.  */
extern __gid_t getgid (void) __THROW;
  • getegid(2) 返回当前进程的有效组id
/* Get the effective group ID of the calling process.  */
extern __gid_t getegid (void) __THROW;
  • setuid(2) 设置当前进程的有效用户id
/* Set the user ID of the calling process to UID.
   If the calling process is the super-user, set the real
   and effective user IDs, and the saved set-user-ID to UID;
   if not, the effective user ID is set to UID.  */
extern int setuid (__uid_t __uid) __THROW __wur;
  • setgid(2) 设置当前进程的有效组id
/* Set the group ID of the calling process to GID.
   If the calling process is the super-user, set the real
   and effective group IDs, and the saved set-group-ID to GID;
   if not, the effective group ID is set to GID.  */
extern int setgid (__gid_t __gid) __THROW __wur;
  • setreuid(2) 交换real或effective用户id(该函数是原子操作)
/* Set the real user ID of the calling process to RUID,
   and the effective user ID of the calling process to EUID.  */
extern int setreuid (__uid_t __ruid, __uid_t __euid) __THROW __wur;
  • setregid(2) 交换real或effective组id(该函数是原子操作)
/* Set the real group ID of the calling process to RGID,
   and the effective group ID of the calling process to EGID.  */
extern int setregid (__gid_t __rgid, __gid_t __egid) __THROW __wur;
  • seteuid(2) 更改有效用户id
/* Set the effective user ID of the calling process to UID.  */
extern int seteuid (__uid_t __uid) __THROW __wur;
  • setegid(2) 更改有效组id
/* Set the effective group ID of the calling process to GID.  */
extern int setegid (__gid_t __gid) __THROW __wur;

实际用途举例——passwd

passwd命令使用来修改用户的密码的,比如我们的普通用户就可以调用这个命令来修改自己的密码,密码是保存在/etc/shadow文件中的,这个文件只有root用户拥有修改的权限,而普通用户想要修改这个文件就必须修改当前执行者的euid,这样才能对阴影口令文件进行修改,这时suid就其作用了,当我们查看passwd的属性时可见:

root@VM-24-2-ubuntu:/home/ubuntu/linux/tmp# ls -l /usr/bin/passwd 
-rwsr-xr-x 1 root root 59640 Mar 14  2022 /usr/bin/passwd

可见出现了s权限标识,有了这个标识,就表示这个可执行文件在执行时(也就是execXX()该函数发现了u+s这样权限时),euid将被修改为其所有者的uid(这里passwd的uid就是0),这样我们普通用户在执行passwd权限时就能够修改阴影口令文件了。

示例

int main(int argc, char **argv) {

    pid_t pid;
    struct passwd *pw;
    if (argc < 3) {
        fprintf(stderr, "Usage...\n");
        exit(1);
    }

    pid = fork();
    if (pid < 0) {
        perror("fork():");
        exit(1);
    }
    if (pid == 0) {
        pw = getpwnam(argv[1]);
        setuid(pw->pw_uid);
        execvp(argv[2], argv + 2);
        perror("execvp():");
        exit(1);
    } else {
        wait(NULL);
    }

    exit(0);
}

8 system()/进程会计/进程时间

system()

  • 功能:执行一个shell命令
/* Execute the given line as a shell command.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int system (const char *__command) __wur;
  • 示例
int main(int argc, char **argv) {

    system("date +%s");

    exit(0);
}
  • 工作原理:实际上就是简单fork-exec-wait的实现,比如上面的例子system可以认为就是如下实现:
int main(int argc, char **argv) {

    pid_t pid;

    pid = fork();

    if (pid < 0) {
        perror("fork():");
        exit(1);
    }
    if (pid == 0) {
        execl("/bin/sh", "sh", "-c", "date +%s", NULL);
        perror("execl():");
        exit(1);
    } else {
        wait(NULL);
    }

    exit(0);
}

进程会计

  • acct()函数,当有进程消亡的时候会将其属性写到name文件中
/* Turn accounting on if NAME is an existing file.  The system will then write
   a record for each process as it terminates, to this file.  If NAME is NULL,
   turn accounting off.  This call is restricted to the super-user.  */
extern int acct (const char *__name) __THROW;

进程时间

  • times(2)函数
/* Store the CPU time used by this process and all its
   dead children (and their dead children) in BUFFER.
   Return the elapsed real time, or (clock_t) -1 for errors.
   All times are in CLK_TCKths of a second.  */
extern clock_t times (struct tms *__buffer) __THROW;
  • tms结构体
/* Structure describing CPU time used by a process and its children.  */
struct tms
  {
    clock_t tms_utime;    /* User CPU time.  */
    clock_t tms_stime;    /* System CPU time.  */

    clock_t tms_cutime;       /* User CPU time of dead children.  */
    clock_t tms_cstime;       /* System CPU time of dead children.  */
  };

9 守护进程

什么是守护进程

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

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

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

守护进程的名称通常以d结尾,比如sshd、xinetd、crond等

基本概念

  • 进程组

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

    • 会话期(session)是一个或多个进程组的集合。
    • setsid()函数可以建立一个对话期:如果,调用setsid的进程不是一个进程组的组长,此函数创建一个新的会话期。

相关函数

  • setsid() 创建一个session并设置进程组id
/* Create a new session with the calling process as its leader.
   The process group IDs of the session and the calling process
   are set to the process ID of the calling process, which is returned.  */
extern __pid_t setsid (void) __THROW;
  • getpgrp() 返回当前进程所在的进程组的id
/* Get the process group ID of the calling process.  */
extern __pid_t getpgrp (void) __THROW;
  • getpgid() 返回pid进程的进程组id
/* Get the process group ID of process PID.  */
extern __pid_t __getpgid (__pid_t __pid) __THROW;
  • setpgid() 将进程pid放到进程组pgid中
/* Set the process group ID of the process matching PID to PGID.
   If PID is zero, the current process's process group ID is set.
   If PGID is zero, the process ID of the process is used.  */
extern int setpgid (__pid_t __pid, __pid_t __pgid) __THROW;

守护进程的创建

#define FNAME "/home/ubuntu/linux/out"

static int daemonize() {
    pid_t pid;
    int fd;
    pid = fork();
    if (pid < 0) {
        return -1;
    }
    if (pid > 0) exit(0);
    fd = open("/dev/null", O_RDWR);
    if (fd < 0) {
        return -1;
    }
    //关闭终端
    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);
    if (fd > 2) close(fd);
    //创建新的session
    setsid();
    //切换工作路径
    chdir("/");
    umask(0);

    return 0;

}

int main(int argc, char **argv) {

    FILE *fp;
    int i;
    openlog("mydaemon", LOG_PID, LOG_DAEMON);
    //创建守护进程
    if (daemonize()) {
        syslog(LOG_ERR, "daemonize() failed!");
        exit(1);
    } else {
        syslog(LOG_INFO, "daemonize() succeeded!");
    }

    //守护进程打开一个文件并持续向文件中写数据
    fp = fopen(FNAME, "w");
    if (fp == NULL) {
        syslog(LOG_ERR, "fopen():%s", strerror(errno));
        exit(1);
    }

    syslog(LOG_INFO, "%s was opened!", FNAME);

    for (i = 0; ; i++) {
        fprintf(fp, "%d\n", i);
        fflush(fp);
        syslog(LOG_DEBUG, "%d is printed.", i);
        sleep(1);
    }
    fclose(fp);
    closelog();

    exit(0);
}

10 系统日志

系统日志只有syslogd服务能够写,当我们要写系统日志的时候可以通过一定的接口将要写的日志交给这个服务来帮我们写

相关函数

  • openlog(3) iden指任意的一个字段,option是特殊要求
/* Open connection to system logger.

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern void openlog (const char *__ident, int __option, int __facility);

​ 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.

​ 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 differently.

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_LOCAL7reserved 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
  • syslog(3) 提交内容,priority表示级别,重要程度,fmt就是提交的内容
/* Generate a log message using FMT string and option arguments.

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern void syslog (int __pri, const char *__fmt, ...)
     __attribute__ ((__format__ (__printf__, 2, 3)));

​ 级别的选项

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
  • closelog(3) 断开服务
/* Close descriptor used to write to system logger.

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern void closelog (void);

===

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/153660.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中断处理程序

文章目录前言前置知识中断概念中断分类实验操作实验一实验二实验三前言 博客记录《操作系统真象还原》第七章实验的操作~ 实验环境&#xff1a;ubuntu18.04VMware &#xff0c; Bochs下载安装 实验内容&#xff1a; 编写中断处理程序&#xff08; 操作 8259A 打开中断&…

2023年浙江食品安全管理员考试真题题库及答案

百分百题库提供食品安全管理员考试试题、食品安全管理员考试预测题、食品安全管理员考试真题、食品安全管理员证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 一、多选题 1.餐具清洗消毒水池与以下哪些水池应分开&#xff…

机器学习— —导入数据

DAY1集成开发环境原始数据展示主要函数介绍data.iloc()函数dataset.iloc[0]dataset.iloc[:,:-1]dataset.iloc[:,3]dataset.iloc[1:3,1]可执行代码导入结果展示我的写在最后集成开发环境 Spyder (前身是 Pydee) 是一个强大的交互式 Python 语言开发环境&#xff0c;提供高级的代…

如何搭建Python环境和安装Pycharm

1、 搭建Python的基础环境 Anaconda简介 Anaconda指的是一个开源的Python发行版本&#xff0c;其包含了conda、Python等180多个科学包及其依赖项。 因为包含了大量的科学包&#xff0c;Anaconda 的下载文件比较大&#xff08;约 531 MB&#xff09;&#xff0c;如果只需要某些…

【Kotlin】标准库函数 ② ( run 标准库函数 | run 函数传入 Lambda 表达式作为参数 | run 函数传入函数引用作为参数 )

文章目录一、run 标准库函数1、run 函数传入 Lambda 表达式作为参数2、run 函数传入函数引用作为参数Kotlin 语言中 , 在 Standard.kt 源码中 , 为所有类型定义了一批标准库函数 , 所有的 Kotlin 类型都可以调用这些函数 ; 一、run 标准库函数 1、run 函数传入 Lambda 表达式作…

Odoo丨5步轻松实现在Odoo中打开企微会话框

文章目录一、前言二、实现方案三、接口调用步骤一、前言 企业微信作为一个很好的企业级应用发布平台&#xff0c;尤其是提供的数据和接口&#xff0c;极大地为很多企业级应用提供便利&#xff0c;在日常中应用广泛&#xff01; 最近在项目中就遇到一个与企业微信相关的场景开…

商标异议解读

商标异议解读《商标法》第三十五条规定&#xff0c;对初步审定公告的商标提出异议的&#xff0c;商标局应当听取异议人和被异议人陈述事实和理由&#xff0c;经调查核实后&#xff0c;自公告期满之日起十二个月内做出是否准予注册的决定&#xff0c;并书面通知异议人和被异议人…

Python初次实现MapReduce——WordCount

前言 Hadoop 本身是用 Java 开发的&#xff0c;所以之前的MapReduce代码小练都是由Java代码编写&#xff0c;但是通过Hadoop Streaming&#xff0c;我们可以使用任意语言来编写程序&#xff0c;让Hadoop 运行。 本文用Python语言实现了词频统计功能&#xff0c;最后通过Hadoo…

Redis从青铜到王者,从环境搭建到熟练使用

一、常见的非关系型数据库NOSQL分类NOSQL类型主要数据库产品类型特色K-V键值对存储类型Redis、Memcached使用key可以快速的查询到value&#xff0c;Memcached可以支持String类型的值value&#xff0c;Redis支持的值的数据类型很多如&#xff1a;String\set\hash\sortset\list等…

1月重磅福利——Softing在线培训课程上线

尽管有标准化和界面友好的工具&#xff0c;但车辆诊断的复杂性仍需要或多或少的专业知识支持&#xff0c;其具体取决于应用领域和要求。无论是初学者还是高级工程师&#xff0c;我们都很乐意为您日常遇到的问题提供相关支持&#xff0c;并就车辆诊断、ODX和OTX标准以及工具的使…

LeetCode 179. 最大数

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 179. 最大数&#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 一、题目名称 LeetCode 179. …

01Hashmap并发问题-ConcurrentHashMap-线程安全集合类-并发编程(Java)

1 概述 线程安全类可以分为3个大类&#xff1a; 遗留的的线程安全集合Hashtable&#xff0c;Vector使用Collections装饰的线程安全集合&#xff0c;如&#xff1a; Collections.syncronizedCollectionCollections.syncronizedListCollections.syncronizedMapCollections.sync…

Linux系列文章 —— vim的基本操作(误入vim退出请先按「ESC」再按:q不保存退出,相关操作请阅读本文)

系列文章目录 文章目录系列文章目录前言一、vim的基本概念二、vim的基本操作1.退出vim编辑器2.进入vim编辑器3.模式功能及切换三、vim命令模式命令集1.光标移动2.查找字符3.复制粘贴删除4.撤销、重做与重复做5.插入模式6.保存与退出7.环境修改四、总结1.vim的三种基本模式2.vim…

springboot之webmvc和webflux浅析

webmvc和webflux作为spring framework的两个重要模块&#xff0c;代表了两个IO模型&#xff0c;阻塞式和非阻塞式。 1、webmvc webmvc是基于servlet的阻塞式模型&#xff0c;一个请求到达服务器后会单独分配一个线程去处理请求&#xff0c;如果请求包含IO操作&#xff0c;线程…

车载以太网 - DoIP时间参数 - 06

时间参数在所有的协议定义中都无法被忽略的一块重要部分,之前的CAN&CANFD诊断协议总,ISO 16765就有相关的诊断时间参数的定义;在DoIP中,也有同样的时间参数定义,不过他是放在ISO 13400 - 2中,今天我们一起来看下这一块的内容。 ISO 13400原文文档 中文释义 时间参数定…

北大硕士LeetCode算法专题课-基础算法之排序

接连上篇&#xff1a;北大硕士LeetCode算法专题课---算法复杂度介绍_骨灰级收藏家的博客-CSDN博客 冒泡排序 冒泡排序&#xff08;Bubble Sort&#xff09;是一种很原始的排序方法&#xff0c;就是通过不断地交换“大数”的位置达到排序的目的。 因为不断出现“大数”类似于水…

HTML实现狗屁不通文章生成器

演示 实现 css html, body {background: radial-gradient(#181818, #000000);margin: 0;padding: 0;border: 0;-ms-overflow-style: none;}.btn {display: inline-block;color: #fff;cursor: pointer;font-size: 1em;font-weight: 400;max-weight: 20%;position: relative;tex…

【零基础】学python数据结构与算法笔记8

文章目录前言46.数据结构介绍47.列表48.栈的介绍49.栈的应用&#xff1a;括号匹配问题50.队列的介绍51.队列的实现52.队列的内置模块总结前言 学习python数据结构与算法&#xff0c;学习常用的算法&#xff0c; b站学习链接 46.数据结构介绍 数据结构是指相互之间存在着一种…

双点双向重发布以及路由策略

目录前言实验要求基础配置启动rip 及 ospf 协议双向重发布路由策略前言 1&#xff0c;由于ASBR在重发布路由条目时&#xff0c;将清除原有协议携带的度量&#xff0c;会添加新协议的度量种子&#xff0c;一旦2&#xff0c;使用双点双向重发布时&#xff0c;可能会出现选路不佳…

docker容器日志清理

最近发现linux&#xff08;Centos 7&#xff09;虚拟机的空间不够了&#xff0c;想创建新的容器都失败。剩下不到100M。之前还有好几个G。然后每天不定期查看磁盘空间&#xff0c;发现不断被蚕食。今天比昨天就少了100M&#xff1b;然后下午比上午又少了50M。谁在吞噬服务器的硬…