【Linux系统编程】-进程

目录

什么是进程

时钟中断和上下文切换

程序入口

进程的终止

命令行参数分析

1.getopt

2.getopt_long

环境变量和函数跳转

1.getenv

2.setenv

3.setjump

4.longjump

5.sigsetjmp

6.siglongjmp

进程相关函数

进程资源的获取和控制

1.setrlimit

2.getrlimit

进程标识符

1.getpid

2.getppid

3.fork

进程的销亡和释放资源

1.wait

2.waitpid

进程会计

进程时间

守护进程

1.setsid

2.setpgid

3.getpgid

exec函数族

1.execl

2.execlp

 3.execle

4.execv

5.execvp

用户权限及权限组

1.getuid&getgid

2.geteuid&getegid

3.setuid&setgid

4.setreuid&setregid

 5.seteuid&setegid

6.setfsuid&setfsgid

系统日志(/var/log)

1.openlog

2.syslog

3.closelog

守护进程的代码实现


什么是进程

        进程(程序)是存储在存储介质(硬盘)上的一段代码,它在运行时被加载到内存空间(RAM)中。CPU会为进程分配一块地址空间,这块地址空间是虚拟的,并且可能与其他进程共享(mmap)。操作系统会将文件的物理扇区映射到进程的虚拟地址空间,而内存管理单元(MMU)负责处理虚拟地址到物理地址的转换。进程通过操作系统提供的API(应用程序编程接口)来执行任务。进程的执行是通过CPU的寄存器来控制的。

时钟中断和上下文切换

        CPU为了获得控制权会启动中断时钟,每隔一段时间产生中断,让控制权重新回到CPU执行中断程序
        当进程开始执行时,操作系统会将进程的寄存器上下文(包括程序计数器、栈指针等)加载到CPU的寄存器中。进程通过这些寄存器来访问内存和执行指令。

        当进程从用户模式切换到内核模式时(例如,由于系统调用、中断或异常),CPU会自动将一些关键的寄存器状态保存到当前进程的内核栈^[1]中(硬件自动保存)。

        操作系统会更新内核寄存器到进程的进程结构的内存(PCB^[2])中(软件显式保存),样做的目的是,当进程再次被调度执行时,它可以恢复到被中断前的状态。同时,CPU的寄存器会被清空,以便为下一个进程的执行做准备。
        总结一下就是;时钟中断:硬件自动保存用户模式下的寄存器状态到内核栈。操作系统决定切换:操作系统软件显式保存内核模式下的寄存器状态到进程的PCB或其他进程结构的内存中。

补充注释

        [1]内核栈:直接保存寄存器状态,是物理内存中的一个实际区域。进程都拥有自己的内核栈(Kernel Stack),这是进程在执行内核模式代码时使用的栈。内核栈与用户栈(User Stack)是分开的
        [2]PCB:存储进程的更广泛的状态信息,是操作系统管理进程的关键数据结构,通常存储在系统的核心物理内存区域,这个区域是受操作系统保护的,普通用户进程无法直接访问。

程序入口

        main 函数是程序执行的起始点。当程序启动时,操作系统会调用 main 函数开始执行程序。

进程的终止

        正常终止:main函数返回,调用exit,调用_exit或_Exit,最后一个线程从其他期待例程返回,最后一个线程调用pthread_exit
        异常终止:调用abort,接到一个信号并终止,最后一个线程对其取消请求做出相应

        钩子函数: 在exit退出后,以注册的逆序方式运行(同defer的FILO)

int atexit(void (*function)(void));

命令行参数分析

1.getopt

int getopt(int argc, char * const argv[], const char *optstring);

        接受命令行参数-后面的字母,optstring指定了程序可以接受的选项字符以及它们是否需要参数。选项字符前可以有一个冒号(:)来表示需要参数,参数用指针optarg调用,如果未提供getopt将返回一个错误。getopt返回下一个解析到的选项字符,如果所有选项都被解析完毕,它会返回 -1。

2.getopt_long

int getopt_long(int argc, char *const *argv, const char *options, const struct option *long_options, int *opt_index);


struct option {
    const char *name;       // 长选项的名称
    int has_arg;            // 选项是否有参数
    int *flag;              // 存储选项值的位置或为 NULL
    int val;                // 选项的值或标志
};

        成功时,返回当前处理的选项的值(可以是字符或 val 字段的值)。当没有更多的选项时,返回 -1。当遇到非法选项时,返回 ':'。


参数说明
    -argc: 命令行参数的个数。
    -argv: 命令行参数的数组,第一个元素是程序名称。
    -options: 一个字符串,描述了短选项的格式。每个字符代表一个短选项,后跟一个冒号表示该选项需要一个参数(例如 "a:b::" 中 a 需要一个参数,b 可以有一个参数,c 没有参数)。
    -long_options: 一个结构体数组,描述了长选项。每个元素包含长选项的名称、是否有参数、值的存储位置和选项的值。
    -opt_index: 指向整数的指针,返回当前处理的长选项在 long_options 数组中的索引。


环境变量和函数跳转

1.getenv

char *getenv(const char *name);

        通过name获取环境变量

2.setenv

int setenv(const char *name, const char *value, int overwrite);

        overwrite如果为非零值,则表示如果环境变量已经存在,其值将被新的值覆盖;如果为零,则表示如果环境变量已存在,则函数不会改变其值。

3.setjump

int setjmp(jmp_buf env);

        首次调用 setjmp 时,它返回 0。当 longjmp 被调用并且跳转回 setjmp 时,setjmp 返回由 longjmp 传递的非零值。

4.longjump

void longjmp(jmp_buf env, int val);

        传递给 setjmp 的值,当 longjmp 调用时。如果这个值为 0,setjmp 将返回 0;否则,它将返回这个非零值。

5.sigsetjmp

int sigsetjmp(sigjmp_buf env, int savesigs);

        sigsetjmp 函数与 setjmp 类似,但它还允许你指定一个信号集,用于在恢复状态时忽略这些信号.savesigs 是一个布尔值,如果为非零值,则在恢复状态时会忽略指定的信号集

6.siglongjmp

void siglongjmp(sigjmp_buf env, int val);

        同longjmp


进程相关函数

进程资源的获取和控制

常见的资源限制类型包括:
    RLIMIT_CORE:核心文件的最大大小。
    RLIMIT_CPU:CPU 时间限制。
    RLIMIT_DATA:数据段的最大大小。
    RLIMIT_FSIZE:文件的最大大小。
    RLIMIT_NOFILE:进程可打开的文件描述符的最大数量。
    RLIMIT_STACK:栈的最大大小

1.setrlimit

int setrlimit(int resource, const struct rlimit *rlim);

        resource:指定要限制的资源类型,rlim:指向 rlimit 结构的指针,该结构包含资源限制的当前值和最大值。成功时返回 0。失败时返回 -1,并设置 errno 以指示错误类型。

2.getrlimit

int getrlimit(int resource, struct rlimit *rlim);


struct rlimit {
    rlim_t rlim_cur;  // 当前限制值
    rlim_t rlim_max;  // 最大限制值
};

        查询的资源类型

进程标识符

        进程标识符pid,类型是pid_t(一般情况下是16位无符号整形),pid进程号是循序向下使用,命令ps可以查看,init进程(pid = 1)是所有进程的祖先。fork的父子进程是写实拷贝技术,互不干涉。

1.getpid

pid_t getpid(void);

        获取当前进程pid

2.getppid

pid_t getppid(void);

        获取当前进程的父进程pid

3.fork

pid_t fork(void);

        复制当前进程产生子进程,父进程返回子进程pid,子进程返回0,未决信号和文件锁不继承,资源利用量清0

进程的销亡和释放资源

1.wait

pid_t wait(int *wstatus);

        堵塞等待,wstatus是一个整数指针,用于存储子进程退出状态的信息,可以给NULL。成功返回被等待的子进程的进程PID,失败返回 -1

2.waitpid

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

        指定要等待的子进程的PID,-1表示等待任何子进程。0表示等待与调用进程具有相同组 ID 的任何子进程。options是指定等待操作的行为,可以设置非堵塞。

进程会计

int acct(const char *filename);


struct acct {
               char ac_flag;           /* Accounting flags */
               u_int16_t ac_uid;       /* Accounting user ID */
               u_int16_t ac_gid;       /* Accounting group ID */
               u_int16_t ac_tty;       /* Controlling terminal */
               u_int32_t ac_btime;     /* Process creation time
                                          (seconds since the Epoch) */
               comp_t    ac_utime;     /* User CPU time */
               comp_t    ac_stime;     /* System CPU time */
               comp_t    ac_etime;     /* Elapsed time */
               comp_t    ac_mem;       /* Average memory usage (kB) */
               comp_t    ac_io;        /* Characters transferred (unused) */
               comp_t    ac_rw;        /* Blocks read or written (unused) */
               comp_t    ac_minflt;    /* Minor page faults */
               comp_t    ac_majflt;    /* Major page faults */
               comp_t    ac_swaps;     /* Number of swaps (unused) */
               u_int32_t ac_exitcode;  /* Process termination status
                                          (see wait(2)) */
               char      ac_comm[ACCT_COMM+1];
                                       /* Command name (basename of last
                                          executed command; null-terminated) */
               char      ac_pad[X];    /* padding bytes */
           };

           enum {          /* Bits that may be set in ac_flag field */
               AFORK = 0x01,           /* Has executed fork, but no exec */
               ASU   = 0x02,           /* Used superuser privileges */
               ACORE = 0x08,           /* Dumped core */
               AXSIG = 0x10            /* Killed by a signal */
           };

        跟踪系统活动和资源使用的系统调用,写入filename。它主要用于记录用户和系统进程的资源使用情况,如 CPU 时间、内存使用、I/O 操作等。acct 函数通常需要超级用户权限才能使用。账户记录文件通常由系统管理员管理,普通用户不应随意修改。

进程时间

clock_t times(struct tms *buf);


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 */
};

        获取进程的时间相关信息

守护进程

        守护进程(Daemon)是一种在后台运行的进程,通常用于提供系统服务,守护进程必须是后台会话(session)的领导者,拥有相同的pid,gid,sid,并且TTY(控制终端)为"?"。


    独立唯一性:守护进程不依赖于任何终端,它们通常在系统启动时自动启动,或通过系统服务管理器(如 systemd 或 init)启动。守护进程唯一不可重复。
    持续运行:守护进程通常持续运行,直到被显式地停止或系统关闭。
    低优先级:守护进程通常被赋予较低的优先级,以确保它们不会占用过多的系统资源。
    日志记录:守护进程将它们的状态和活动记录到日志文件中,而不是标准输出或标准错误。

1.setsid

pid_t setsid(void);

        setsid 创建一个新的会话^[1],并使调用进程成为该会话的领导者。调用进程同时成为新的进程组的领导者,并成为没有控制终端的进程组的领导者。只有当进程不是进程组的领导者时,setsid 才能成功调用成功返回新的会话ID也就是调用进程的PID。失败返回-1。

2.setpgid

int setpgid(pid_t pid, pid_t pgid);

        设置进程pid和gid

3.getpgid

pid_t getpgid(pid_t pid);

        获取进程

补充注释:

[1]:在操作系统内核中,会话(Session)是一个进程集合,它通常由一个或多个进程组成,并且具有一些共同的属性和行为


exec函数族

        exec函数族用于执行新程序的系统调用。这些函数允许一个正在运行的程序替换其执行的代码和数据,从而开始运行一个全新的程序。exec函数首先查找并加载,新的程序一旦新程序被加载,exec函数会替换当前进程的内存映像(pid不变),这意味着新程序的代码和数据会覆盖当前进程的内存空间,exec 函数会初始化进程的 CPU 寄存器,除了由 exec 函数显式保留的文件描述符外,当前进程的所有文件描述符会被关闭,最后操作系统会更新进程的状态,使其开始执行新程序的代码。函数不会返回,除非发生错误(-1)。注意fflush刷新缓冲区,避免重复输入输出。

1.execl

int execl(const char *pathname, const char *arg, ...  /* (char  *) NULL */);

        只传递参数列表,不传递环境,以NULL空指针作为传递参数结尾。

2.execlp

int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);

        传递参数列表,并且系统的使用PATH环境变量查找*file程序。

 3.execle

int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */);

        传递参数列表和环境。

4.execv

int execv(const char *pathname, char *const argv[]);

        传递参数数组。

5.execvp

int execvp(const char *file, char *const argv[]);

        传递参数数组,使用 PATH 环境变量查找程序。


用户权限及权限组

        在 Linux 系统中,每个进程都有几个与用户身份相关的 ID,这些 ID 决定了进程对系统资源的访问权限。以下是 UID 的三个主要类型:实际用户 ID(real UID,简称 rUID)、有效用户 ID(effective UID,简称 eUID)和保存的用户 ID(saved set-user-ID,简称 sUID)

实际用户 ID(rUID)
    定义:实际用户 ID 是用户登录时的 UID。它是用户身份的初始值,通常在用户登录时由系统设置。
    用途:rUID 用于确定用户登录时的权限。当用户执行程序时,程序的 rUID 将被设置为用户的 rUID。

有效用户 ID(eUID)
    定义:有效用户 ID 是进程在执行时用于检查文件访问权限的 UID。它决定了进程可以访问哪些文件和资源。
    用途:eUID 通常用于限制进程的权限。通过将 eUID 设置为较低权限的用户,可以防止进程访问敏感资源。例如,Web 服务器进程通常会将 eUID 设置为非 root 用户,以减少被攻击的风险。

保存的用户 ID(sUID)
    定义:保存的用户 ID 是在执行程序时由系统保存的 eUID 的副本。它在进程执行时被设置,并在进程执行结束后恢复。
    用途:sUID 用于在程序执行期间暂时提升权限。当程序需要访问敏感资源时,可以暂时将 eUID 设置为 sUID,以便获得必要的权限。程序执行完成后,eUID 会恢复为 sUID 的值,从而降低权限。

1.getuid&getgid

uid_t getuid(void);
gid_t getgid(void);

        获取调用进程的实际用户 ID 和组 ID。返回值:返回实际用户 ID 或组 ID。

2.geteuid&getegid

uid_t geteuid(void);
gid_t getegid(void);

        获取调用进程的有效用户 ID 和组 ID。返回值:返回有效用户 ID 或组 ID。

3.setuid&setgid

int setuid(uid_t uid);
int setgid(gid_t gid);

        设置调用进程的实际用户 ID 和组 ID。返回值:成功时返回 0,失败时返回 -1,并设置 errno。权限:通常只有超级用户(root)可以设置其他用户的 UID 或 GID。对于非特权进程,只能将 UID 或 GID 设置为实际用户 ID、有效用户 ID 或保存的设置用户 ID。

4.setreuid&setregid

int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);

        设置调用进程的实际用户 ID 和有效用户 ID,以及实际组 ID 和有效组 ID。返回值:成功时返回 0,失败时返回 -1,并设置 errno。特殊行为:如果传递 -1 作为参数,则相应的 ID 不会被更改。

 5.seteuid&setegid

int seteuid(uid_t euid);
int setegid(gid_t egid);

        仅设置调用进程的有效用户 ID 和组 ID。返回值:成功时返回 0,失败时返回 -1,并设置 errno。

6.setfsuid&setfsgid

int setfsuid(uid_t uid);
int setfsgid(gid_t gid);

        设置文件系统用户 ID 和组 ID,这些 ID 用于文件系统相关操作。返回值:成功时返回 0,失败时返回 -1,并设置 errno。


系统日志(/var/log)

        syslogd 是 Linux 和类 Unix 系统中用于日志记录的守护进程服务。它负责收集系统和应用程序的日志信息,并将这些信息记录到文件或发送到其他日志服务器。只有syslog才有权限些日志,其他要请求sysylogd服务来写日志。

1.openlog

void openlog(const char *ident, int option, int facility);

        与syslogd进行关联,ident是人物,随便给个字段就行,option是行为,看man手册宏

2.syslog

void syslog(int priority, const char *format, ...);

        向syslogd提交日志内容,priority是级别宏值,format是内容

3.closelog

void closelog(void);

        关闭与syslogd关联


守护进程的代码实现

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

#define FILENAME "~/桌面/linux sys/test.txt"

static FILE *fp;

static void protect_process(void) {
    pid_t pid;
    int fd;
    pid = fork(); // 创建子进程

    if (pid < 0) {
        perror("fork()"); // 显示错误信息
        exit(1); // 退出父进程
    }

    if(pid > 0) {
        exit(0); // 父进程退出
    }

    if (pid == 0) {
        // 子进程继续执行
        fd = open("/dev/null", O_RDWR); // 打开 /dev/null
        if (fd < 0) {
            perror("open()"); // 显示错误信息
            exit(1); // 退出子进程
        }
    }

    // 将标准输入、标准输出和标准错误重定向到 /dev/null
    dup2(fd, 0); // 重定向标准输入
    dup2(fd, 1); // 重定向标准输出
    dup2(fd, 2); // 重定向标准错误

    if (fd > 2) // 如果文件描述符 fd 大于 2,则关闭它
        close(fd);

    // 创建新的会话,并使当前进程成为会话领导者、进程组领导者和没有控制终端的进程组的领导者
    setsid();

    // 更改工作目录为根目录
    chdir("/");

    // 设置文件权限掩码为 0,即不屏蔽任何权限
    umask(0);
}

static void process_exit(int s) {
    closelog();//关闭日志
    fclose(fp); // 关闭文件
}


// 守护进程向 test.txt 文件写入数字
int main() {
    openlog("p_p", LOG_PID, LOG_DAEMON);//打开日志
    struct sigaction sa;

    //退出清理
    sa.sa_handler = process_exit;
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask, SIGINT);
    sigaddset(&sa.sa_mask, SIGQUIT);
    sigaddset(&sa.sa_mask, SIGTERM);
    sa.sa_flags = 0;

    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGQUIT, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);

//signal的方法同一handler可能导致重复调用handler产生问题,用sigaction屏蔽其他信号避免了这种问题
//  signal(SIGQUIT, process_exit);
//  signal(SIGQUIT, process_exit);
//  signal(SIGIERM, process_exit);

    protect_process(); // 保护进程
    syslog(LOG_INFO, "protect_process success");
    // 使用 fopen 打开文件,注意这里应该使用文件名的字符串形式
    fp = fopen(FILENAME, "w");
    if (fp == NULL) {
        perror("fopen()"); // 显示错误信息
    }

    for(int i = 0; ; i++) {
        fprintf(fp, "%d\n", i); // 写入数字和换行符
        fflush(fp); // 刷新缓冲区,确保数据写入文件
        sleep(1); // 休眠 1 秒
    }

    exit(0); // 正常退出
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值