进程1——概念,创建,回收

进程概念

  1. 进程标识符pid
    类型pid_t 一般int_16
    命令 ps 查看进程号
axf 可以看出阶梯状
aux
ax -L 线程相关

进程号是顺次向下使用,与文件描述符不同
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); 当前进程id
pid_t getppid(void); parentid

  1. 进程产生fork()
 #include <sys/types.h>
 #include <unistd.h>

 pid_t fork(void);

setjmp的操作和这个很像,一次指向返回两种情况,因此一般该语句后都会跟随一个分支判断
fork出来的与当前进程一模一样,包括执行位置
区别4:

  • pid不同
  • ppid不同
  • 未决信号和文件锁不继承
  • 新进程资源利用量归零
    init进程:是所有进程的祖先进程 pid=1
    在这里插入图片描述
    调度器的调度策略决定哪个进程先运行,没有规定的顺序,一般想强制,可以sleep(但需要考虑移植问题)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/types.h>

int main()
{
    printf("[%d]:Begin!\n", getpid());
    fflush(NULL);
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork()");
        exit(1);
    }
    else if (pid == 0)
    {
        // child
        printf("[%d]:Child is working!\n", getpid());
    }
    else
    {
        // > 0 pid 当前进程id
        printf("[%d]:Parent is working\n", getpid());
    }

    printf("[%d]:End!\n", getpid()); // 父子共有
    return 0;
}

./fork1
./fork1 > tmpfile

./fork1 | wc -l   // 计算输出项的个数

打印出的结果不相同
因为文件是全缓冲,因此在fork前刷新全部的流,fflush(NULL)
终端是设备,行缓冲
关于fork导致资源爆炸问题
在这里插入图片描述
图中展现部分结果,因为子进程执行后没有退出,且子进程是对父进程的复制,使得子进程再次进入外层循环+1,并不断进行fork,树形增长,导致资源爆炸
进程状态:S 睡眠 Z僵尸
案例:

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

#define LEFT 30000000
#define RIGHT 30000200

int main()
{
    int mark;
    pid_t pid;
    for (int i = LEFT; i < RIGHT; i++)
    {
        pid = fork();
        if (pid < 0)
        {
            perror("fork");
            exit(1);
        }
        else if (pid == 0)
        {
            // child
            mark = 1;
            for (int j = 2; j < i / 2; j++)
            {
                if (i % j == 0)
                {
                    mark = 0;
                    break;
                }
            }
            if (mark == 1)
            {
                printf("%d is prime\n", i);
            }
            // sleep(1000); // 让父亲先结束
            exit(0); // 子进程拿到一个i值,操作后,直接退出
        }
    }
    sleep(1000); // 父进程睡眠,子进程退出,但没有父进程为其回收,会变成僵尸
    return 0;
}

僵尸进程一般会存在,但是正常情况是存在一会儿就会收尸了,本身他们并不会占用很多资源,但是会存在占用pid问题,pid一般是16bits有符号数,那进程号就是个有限的资源
因此,收尸的时候需要关注对pid的释放,还有就是子进程回收的时候是什么进程状态 僵尸?睡眠?。。。
在这里插入图片描述
理解vfork和fork的区别:
fork可以理解为深拷贝进程资源
vfork可以理解为浅拷贝进程资源(就是拷贝一个指针,指向的内容还是原来的空间)
但是现在的fork已经加入了写时复制技术
与字面意思相同:如果父进程和子进程都是对一个数据块进行,那么这两个指针指向同一块物理存储空间,没问题;如果有一个进程对一个数据块进行写,如果子进程,那么该子进程会复制一块该空间,在新空间上进行写操作,即写时复制技术

  1. 进程的消亡及释放资源
    pid_t wait(int *wstatus); // 整型地址,存放收尸状态,返回终止进程id 只有子进程死掉,发送信号,才回收
WIFEXITED(wstatus)  正常终止,返回true
WEXITSTATUS(wstatus) 正常结束的情况下,返回子进程的退出状态码

WIFSIGNALED(wstatus)  由信号终止,返回true
WTERMSIG(wstatus) 由信号终止,返回true,返回导致进程终止的信号码返回
WCOREDUMP(wstatus)  由信号终止,返回true,检测子进程是否产生了core dump文件

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.

pid_t waitpid(pid_t pid, int *wstatus, int options); // 指定回收pid,opt是重点,
WNOHANG–收尸,已经死掉,直接收,正常运行时,不收// 这时是非阻塞的状态,而wait是阻塞的
pid>0,回收指定子进程
pid为0,意味着收同组中的任何一个子进程回来
pid为-1,收任何一个子进程
pid<-1,收|pid|组中的一个子进程

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

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

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

#define LEFT 30000000
#define RIGHT 30000200
#define N 3  
// 三个进程去计算

int main()
{
    int mark;
    pid_t pid;
    for (int n = 0; n < N; n++)
    {
    // 三个进程交叉分配,存在一个问题,第一个进程拿到的都是3的倍数,所以始终没有计算出素数
        pid = fork();
        if (pid < 0)
        {
            perror("fork");
            // 回收之前fork出去的子进程
            for (int i = 0; i < n; i++)
            {
                wait(NULL);
            }
            exit(1);
        }
        else if (pid == 0)
        {
        	// child
            for (int i = LEFT+n; i <= RIGHT; i+=N)  // 201 
            {
                // child
                mark = 1;
                for (int j = 2; j < i / 2; j++)
                {
                    if (i % j == 0)
                    {
                        mark = 0;
                        break;
                    }
                }
                if (mark == 1)
                {
                    printf("[%d]%d is a prime\n", n, i);
                }
            }
            exit(0); // 子进程拿到一个i值,操作后,直接退出
        }
    }
    
    int st;
    for (int n = 0; n < N; n++)
    {
        wait(&st);
    }
    
    return 0;
}
  1. exec函数族
    函数族是用一个新的进程镜像替换当前的进程镜像
 #include <unistd.h>
extern char **environ;
int execl(const char *pathname, const char *arg,/* (char  *) NULL */);  // 定参 路径,参数+NULL
int execlp(const char *file, const char *arg,/* (char  *) NULL */); // 定参 文件名,参数+NULL
int execle(const char *pathname, const char *arg,/*, (char *) NULL, char *const envp[] */); // 定参 路径,参数+NULL,环境变量

// 变参
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[]);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

/**
 * date +%s
 * */

int main()
{
    puts("begin()");

    int err = execl("/bin/date", "date", "+%s",NULL);
    perror("execl()"); // 如果成功了,已经替换成其他映像,不会回来,如果回来,一定出错,结束
    exit(1);

    puts("end()");
    return 0;
}

在这里插入图片描述
不会再打印end,已经跑路了

./ex > /tmp/out

在这里插入图片描述
注意fflush的使用
文件是全缓冲, 需要使用fflush(否则需要等缓冲满了,自动冲洗)
终端及设备是行缓冲,有’\n’即可实现刷新

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

/**
 * date +%s
 * */

int main()
{
    puts("begin()");

    fflush(NULL);
    execl("/bin/date", "date", "+%s",NULL); // pid 并没变化,却执行了其他进程
    perror("execl()"); // 如果成功了,已经替换成其他映像,不会回来,如果回来,一定出错,结束
    exit(1);

    puts("end()");
    return 0;
}

shell处理外部命令的思路

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


int main()
{
    while (1)
    {
        prompt();
        getline();
        parse(); // 解析
        if (内部命令)
        {

        }
        else
        {
            // 外部命令
            pid = fork();
            if (pid < 0)
            {
				perror();
				exit(1);
            }
            else if (pid == 0)
            {
                execXX();
                perror();
                exit(1);
            }
            else
            {
                wait(); // 等待收尸
            }
        }
    }
    
    return 0;
}

 #include <string.h>
 char *strtok(char *str, const char *delim);

解析代码:

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

#define DELIMS " \t\n"

struct cmd_st
{
    glob_t glob_res;
};

static void prompt()
{
    printf("mysh$ ");
}

// 解析
static void parse(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;
        // pat, GLOB_NOCHECK 如果没有匹配模式的,就返回tok字串 eg: ls 那么就返回ls
        glob(tok, GLOB_NOCHECK | GLOB_APPEND * i, NULL, &res->glob_res);
        i = 1; // 只有第一次不进行加入eg ls -a  解析ls的时候不是append,是直接覆盖res,后面是-a是append  res为ls -a
    }
}

int main()
{
    char * linebuf = NULL;
    size_t linebuf_size = 0;
    struct cmd_st cmd;
    while (1)
    {
        prompt();
        // char** buf, size_t* n, file* stream
        if (getline(&linebuf, &linebuf_size, stdin) < 0)
        {
            break;
        }

        parse(linebuf, &cmd); // 解析
        if (0)
        {
            // to do  内部命令
        }
        else
        {
            // 外部命令
            pid_t pid = fork();
            if (pid < 0)
            {
                perror("fork()");
                exit(1);
            }
            else if (pid == 0)
            {
                // execL();  可变参数  前三个不用  eg 第一个参数就是ls  后面是pathv,参数中包括了命令本身,
                execvp(cmd.glob_res.gl_pathv[0], cmd.glob_res.gl_pathv);
                perror("execvp()");
                exit(1);
            }
            else
            {
                wait(NULL); // 等待收尸
            }
        }
    }
    
    return 0;
}

  1. 用户权限及组权限
    u+s
    g+s
    uid_t getuid(); // 返回实际用户id + e表示有效的
    getgid(); // 返回组id
    setuid(uid_t uid); // 设置实际用户id
    setgid(gid_t gid);
    setreuid(uid_t ruid, euid); // 交换实际uid和有效uid
    setregid();
    seteuid();
    setegid();

  2. 观摩课:解释器文件
    #!脚本标记
    加载后面的解释器
    使用该解释器,解释文件中的命令(此时第一行是#开头,那就将其解释为注释)

#!/bin/bash

ls
whoami
cat /etc/shadow
ps
  1. system(); // execute a shell command
    execl(“/bin/sh”, “sh”, “-c”, command, (char *) NULL);
    相当于fork exec wait的简单封装

  2. 进程会计
    acct

  3. 进程时间(time命令)
    time 命令

#include <sys/times.h>
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 */
};
  1. 守护进程
    脱离控制终端
    会话的领导者
    进程组的领导者

会话/终端:一个终端的登录就产生了一个会话
session的标识sid

最多可以有一个前台进程组
后台不能接受输入输出

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

pid_t setsid(void); // group leader进程是不可以进行调用该函数的,也就是父进程是无法调用的,必须是子进程调用,因为守护进程不需要收尸

调用setsid的进程会成为当前新进程组的leader,并脱离控制终端
成为守护进程(父进程就是init 1号) 类型为?

getpgrp(); 当前进程组的id
getpgid(pid_t) 根据进程号获取进程组号

  1. 系统日志书写
    每个程序都有必要写系统日志,系统日志/var/log
    但不能任何程序都有权限写

syslogd服务去写,控制权限的服务

单实例守护进程:锁文件/var/run/sshd----无法多次启动一个进程,由此控制
开机启动脚本:/etc/rc*

#include <syslog.h>
// 人物 特殊要求 来源
void openlog(const char *ident, int option, int facility); // 写
// 级别 内容 
void syslog(int priority, const char *format, ...); // 提交内容
void closelog(void);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <syslog.h>

#define FNAME "/tmp/out"

static int daemonize(void)
{
    FILE* fd;
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork");
        exit(1)
    }
    else if (pid > 0)
    {
        exit(0); // 父进程退出
    }
    else
    {
        // 脱离终端 012需要重定向  不能定向到终端
        fd = open("dev/null", O_RDWR);
        if (fd < 0)
        {
            perror("open");
            return (-1);
        }
        // 守护进程打开文件,使其具有文件描述符012, 任何一个读写err的库例程都不会产生效果
        dup2(fd, 0);  // 文件输入输出重定向
        dup2(fd, 1);
        dup2(fd, 2);
        if (fd < 2)
            close(fd);

        setsid(); // 子进程设置为守护进程 
        chdir("/"); // 工作路径设置到根目录 因为从父进程继承来的工作目录可能是个挂载的目录,
        // 如果守护进程的工作目录也是一个挂载目录,那么该文件系统将不可以被卸载
        // umask(0);
        return 0;
    }
}

int main()
{
    // 人物:任意字段-一般用文件名,  pid,   守护进程类别
    openlog("mydaemon", LOG_PID, LOG_DAEMON);
    // 守护进程
    if (daemonize())
    {
        syslog(LOG_ERR, "init failed");
        exit(1);
    }
    else
    {
        syslog(LOG_INFO, "successed");
    }
    
    // 守护进程做的任务
    FILE* fp = fopen(FNAME, "w+");
    if (fp == NULL)
    {
        syslog(LOG_ERR, "write file failed");
        exit(1);
    }

    syslog(LOG_INFO, "%s opened", FNAME);
    for (int i = 0; ;i++)
    {
        fprintf(fp, "%d", i);
        fflush(NULL);
        syslog(LOG_INFO, "%d wrote", i);
        sleep(1);
    }
    closelog();
    fclose(fp);

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值