嵌入式学习——2——进程(Process)

1.什么是进程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。

2.进程的内存管理

1个进程总是占据4个gb的内存空间

        前3个gb是该进程的独立内存空间,后1个gb是所有进程共享的内存空间

注意:linux系统中,进程使用的其实不是物理内存,而是虚拟内存

Linux 系统中的进程内存管理是通过将虚拟地址映射到物理内存来实现的。

4个gb全是虚拟内存,这4gb的虚拟内存,是由1gb的物理内存映射出来的

3.进程的组成部分

代码段 、数据段  、堆、栈和内存映射区域 

4.进程编号:pid

每一个进程都有自己独立的编号,称为pid

pid的分配方式为:循序寻找未使用的pid

pid的最大值:

早些时候的linxu系统,使用一个short变量来管理的pid,所以pid的最大值就是32767,取值范围就是0~32767  (0,2^7-1)

但是,现在的linux系统,使用的是一个int类型数据来管理的pid,pid取值范围变成了0~65535(0,2^15-1)

5.3个特殊的进程0 1 2

0号进程

0号进程,通常也被称为idle进程,或者也称为swapper进程

0号进程是linux启动的第一个进程,它的task_struct的comm字段为"swapper",所以也成为swpper进程。

#define INIT_TASK_COMM "swapper"

当系统中所有的进程起来后,0号进程也就蜕化为idle进程,当一个core上没有任务可运行时就会去运行idle进程。一旦运行idle进程则此core就可以进入低功耗模式了,在ARM上就是WFI。

1号进程

我们通常将init称为1号进程,其实在刚才kernel_init的时候1号线程已经创建成功,也可以理解kernel_init是1号进程的内核态,而我们所熟知的init进程是用户态的。

至此1号进程就完美的创建成功了,而且也成功执行了init可执行文件。

2号进程

2号进程,是由1号进程创建的。而且2号进程是所有内核线程父进程。

2号进程也叫kthread进程。

1 2 这3个进程本质上是一个进程,3个分身(线程)

总结:

1、linux启动的第一个进程是0号进程,是静态创建的
2、在0号进程启动后会接连创建两个进程,分别是1号进程和2和进程。
3、1号进程最终会去调用可init可执行文件,init进程最终会去创建所有的应用进程。
4、2号进程会在内核中负责创建所有的内核线程
5、所以说0号进程是1号和2号进程的父进程;1号进程是所有用户态进程的父进程;2号进程是所有内核线程的父进程。

6.进程的分类

1、交互进程:   由一个shell启动的进程,交互进程既可以在前台运行,也可以在后台运行。

2、批处理进程:这种进程和终端没有联系,是一个进程序列。

3、监控进程:    也称守护进程,是一个在后台运行且不受任何终端控制的特殊进程,用于执行特定的系统任务。

7.进程相关的shell指令

1、ps -ef 显示进程之间关系的指令

UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 08:58 ?        00:00:05 /sbin/init splas

UID:用户的id

PID:当前进程的id

PPID:父进程id

C:该进程被连接的数量

STIME:开始运行的时间

TTY:该进程所依赖的终端,

?表示不依赖任何终端

CMD:进程名

2、ps -ajx 查看进程状态的指令

PPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND
     0      1      1      1 ?            -1 Ss       0   0:05 /sbi。。。。。。。

PGID:进程组的编号

TPGID:进程的类型,-1表示的是守护进程,>0表示交互进程

STAT:进程的运行状态,进程运行状态可以使用指令 man ps 查看

3、ps -aux 显示进程占据cpu资源情况

USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.3  0.3 159812  9152 ?        Ss   10:38   0:06 /sbin/init splash

%CPU : CPU的占有率

%MEM : 内存占有率

4、向进程发送信号的指令:kill

例如:kill -9 进程号,其中kill是发送信号的意思, -9 才是杀死信号

 1) SIGHUP     2) SIGINT     3) SIGQUIT     4) SIGILL     5) SIGTRAP
 6) SIGABRT     7) SIGBUS     8) SIGFPE     9) SIGKILL    10) SIGUSR1
11) SIGSEGV    12) SIGUSR2    13) SIGPIPE    14) SIGALRM    15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD    18) SIGCONT    19) SIGSTOP    20) SIGTSTP
21) SIGTTIN    22) SIGTTOU    23) SIGURG    24) SIGXCPU    25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF    28) SIGWINCH    29) SIGIO    30) SIGPWR
31) SIGSYS    34) SIGRTMIN    35) SIGRTMIN+1    36) SIGRTMIN+2    37) SIGRTMIN+3
38) SIGRTMIN+4    39) SIGRTMIN+5    40) SIGRTMIN+6    41) SIGRTMIN+7    42) SIGRTMIN+8
43) SIGRTMIN+9    44) SIGRTMIN+10    45) SIGRTMIN+11    46) SIGRTMIN+12    47) SIGRTMIN+13
48) SIGRTMIN+14    49) SIGRTMIN+15    50) SIGRTMAX-14    51) SIGRTMAX-13    52) SIGRTMAX-12
53) SIGRTMAX-11    54) SIGRTMAX-10    55) SIGRTMAX-9    56) SIGRTMAX-8    57) SIGRTMAX-7
58) SIGRTMAX-6    59) SIGRTMAX-5    60) SIGRTMAX-4    61) SIGRTMAX-3    62) SIGRTMAX-2
63) SIGRTMAX-1    64) SIGRTMAX    
1~31:标准信号
34~64:实时信号

5、pidof 进程名

        获取一个进程的pid

6、top

        显示任务管理器:动态的显示系统中的所有进程

7、pstree

        以树状图的形式,显示系统中所有进程关系

8、ps ef -o pid,ppid,command

        只显示系统中所有进程的pid,ppid,和进程名

8.进程状态的切换

进程五态

9.如何创建一个进程,多进程编程的时候,会发生什么

pid_t fork(void);

功能:fork函数的功能就是复制当前进程,在内核进程表中创建一个新的进程表象,该进程称为子进程,被复制的进程称为父进程

  • 子进程的代码和父进程的完全相同,同时它还会复制父进程的数据(堆数据、栈数据和静态数据)。数据的复制采用的是写时复制。此外,创建子进程后,父进程打开的文件描述符默认在子进程中也是打开的,且文件描述符的引用计数+1。

常用的一些函数

pid_t getpid(void);

功能:返回调用者进程的进程编号

pid_t getppid(void);

功能:返回调用者进程的父进程的进程编号

通过fork返回值去区分父子进程

 pid_t pids = fork();
    if (pids > 0) {
        // 父
    }else if (pids == 0){
        // 子
    }else{

        // 失败
        perror("fork");
    }

10.孤儿进程和僵尸进程

回收子进程

pid_t wait(int *stat_loc);

功能:wait函数会获取等待回收资源的子进程的状态信息,并将获取到的状态信息保存到 stat_loc指向的内存空间中,然后回收子进程的资源.

        如果不需要获取子进程死亡时候的信息的话,stat_loc参数直接传NULL或者0

注意:wait是一个阻塞型函数

void exit(int status);

功能:立刻技术当前进程,并且参数status作为该进程main函数的返回值进程返回

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

这个函数是wait函数的高阶自定义版本,允许自定义很多细节部分

参数pid:

         pid == -1        : 可以回收任意子进程的资源

         pid > 0           : 回收一个进程的资源,回收传入的pid进程的资源,但是要求该pid进程所在的进程组和父进程是一样的

         pid == 0        : 回收任意进程组编号和父进程一样的子进程的资源

         pid < -1         : 回收一个进程的资源,进程号为 |pid|那个进程的资源

参数options:一般有2个选项

        0                    :waitpid函数为阻塞型函数

        WNOHANG  :waitpid函数为非阻塞型函数

waitpid(-1,0,0) ======wait(0)

僵尸进程

        当子进程死亡,而父进程一直没有空去wait子进程的资源,此时该子进程资源就泄漏了,这样的子进程,我们称为僵尸进程

注意:僵尸进程是一个高危操作,尽量避免僵尸进程的出现

11.守护进程

守护进程:是一个不与任何用户交互,不依赖任何进程组,独立存在的,在后台持续运行的一个服务性质的进程

① 成为一个孤儿进程,脱离终端控制(这是初步脱离终端控制)

② 成为一个新的进程组的组长

③ 切换守护进程的工作目录,到系统根目录

④ 设置守护进程的掩码为 0111

⑤ 彻底脱离与终端的联系:将守护进程的标准输入,输出,错误流全都重定向到独立的文件中去

代码如下:

// 1 子进程成为孤儿进程
    pid_t pid_n = fork();
    if (pid_n == -1) {
        perror("fork");
        return 1;
    }
    if(pid_n > 0) {return;}

    // 2 成为新的进程组组长
    if (setsid() == -1) {
        perror("setsid");
        return 1;
    }

    // 3 切换目录到根
    if (chdir("/") == -1) {
        perror("chdir");
        return 1;
    }
    
    // 4 设置掩码为0111
    umask(0111);

    // 5 脱离终端(stdio、in、err)
    int fp = open("01test.c", O_RDWR | O_CREAT);
    if (fp == -1) {
        perror("open");
        return 1;
    }

    if (dup2(fp, 0) == -1 || dup2(fp, 1) || dup2(fp, 2)) {
        perror("dup2");
        return 1;
    }  

    while (1)
    {
        // 守护干活
    }

12.练习

/*

创建一对父子进程:

父进程负责向文件中写入 长方形的长和宽

子进程负责读取文件中的长宽信息后,计算长方形的面积

*/


int width;
int height;

void scranf_width();
void scranf_height();

void scranf_width(){
    printf("请输入长方体宽度(整数):");
    if (scanf("%d",&width) == 1) {
        printf("请输入长方体高度(整数):");
        return;
    }
    printf("输入不合法清重新输入\n");
    while (getchar() != '\n') { break; }
    scranf_width();
}
void scranf_height(){
    if (scanf("%d",&height) == 1) {
        return;
    }
    printf("输入不合法清重新输入\n");
    while (getchar() != '\n') { break; }
    printf("请输入长方体高度(整数):");

    scranf_height();
}

int main(int argc, char const *argv[]) {
    pid_t pids = fork();
    if (pids > 0) {
        // 父
        int np = open("homework.txt",O_CREAT | O_WRONLY | O_TRUNC, 0666);
        if (np == -1) {
            perror("open");
            return 1;
        }
        scranf_width(); // 获取宽度
        write(np, &width, sizeof(int));
        scranf_height(); // 获取高度
        write(np, &height, sizeof(int));

        close(np);
        printf("计算中\n");
        wait(NULL);
    }else if (pids == 0){
        // 子
        while (1) { // 循环遍历读取文件
            sleep(1);  // 增加阻尼,防止频繁调用
            int c_np = open("homework.txt",O_RDONLY);
            if (c_np == -1) {
                perror("open");
                return 1;
            }
            if (read(c_np, &width, sizeof(int)) != 0  && read(c_np, &height, sizeof(int)) != 0)
            {
                printf("计算结果:长方体的宽:%d,高:%d,面积是:%d\n", width, height, width*height);
                close(c_np);    
                break;
            }else{
                close(c_np);    
            }
        }
    }else{
        perror("fork");
    }
    return 0;
}

  • 14
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
sscanf函数是C语言中一个非常常用的函数,它可以将一个字符串按照指定的格式转换成相应的数据类型。在嵌入式开发中,sscanf函数也是非常常见的,因为很多时候需要从串口或者其他外部设备中读取数据,并将其转换成相应的数据类型进行处理。下面是一些sscanf函数的使用技巧: 1. 使用sscanf函数时一定要注意格式字符串的正确性。格式字符串中的占位符必须与待转换的数据类型相对应,否则会发生未知错误。 2. 如果待转换的字符串中包含多个数据,可以使用多个占位符进行转换。例如,如果待转换的字符串为"1,2,3",可以使用" %d,%d,%d"的格式字符串进行转换。 3. 可以使用sscanf函数的返回值来判断转换是否成功。如果返回值等于待转换字符串的长度,则说明转换成功,否则转换失败。 4. 如果待转换的字符串中包含浮点数,可以使用"%f"或者"%lf"的格式字符串进行转换。 5. 如果待转换的字符串中包含十六进制数,可以使用"%x"的格式字符串进行转换。 6. 如果待转换的字符串中包含字符或字符串,可以使用"%c"或者"%s"的格式字符串进行转换。 7. 如果待转换的字符串中包含指针类型的数据,可以使用"%p"的格式字符串进行转换。 总之,在使用sscanf函数时一定要注意格式字符串的正确性,否则很容易出现转换错误的情况。同时,还应该注意sscanf函数返回值的判断,以确保转换的正确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值