01-系统编程(进程)

01-系统编程(进程)




一、进程的概念

1.什么是程序?什么是进程?

程序:一堆待执行的代码(二进制可执行文件)。 gcc hello.c -o hello 静态的文本数据
进程:当程序被CPU加载时,根据每一行代码做出相应的效果,形成动态的过程,那么这个过程就是进程。 其实说白了,进程就是一个正在执行的程序

2.开启一个新的进程(在Linux环境下)

直接在linux下,执行一个程序,就会开启相应的进程。
例如: ./hello -> 开启一个名字为hello的进程。
说明:
结束进程:ctrl+C

3.在进程开启之后,系统会为进程分配什么资源?

1)会分配进程对应内存空间
int x -> 运行程序之后,就会在栈区申请4个字节(当x是局部变量的时候)。
2)进程在系统内核中如何进行表示呢
学生管理系统—》每个学生使用结构体进行表示和管理
linux系统---------》每个进程使用结构体进行表示和管理
当进程开启之后,会为这个进程分配一个任务结构体,这个任务结构体就是用于描述这个进程的。
也就是说,进程在内核中是以结构体struct task_struct{} 进行表示的。这个结构体也被称之为进程控制块。
结构体:进程ID号、信号、文件、资源…
/usr/src/linux-headers-3.5.0-23/include/linux/sched.h ----第1229行
gec@ubuntu:/usr/src/linux-headers-4.15.0-142/include/linux$ vi sched.h +1511 ----1500行左右
练习1:在自己的ubuntu里面搜索结构体struct task_struct{}的大小
说明;
不同版本的ubuntu可能路径不一样

4.关于进程的命令

其身份信息在系统启动前就已经存在于系统分区之中,在系统启动时直接复制到内存。
2)查看进程ID号 ----》ps -ef(静态)常用 P>process
gec@ubuntu:~$ ps -ef
用户名 进程ID 父进程 cpu占用 进程启动时间 进程持续时间 进程名
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 18:29 ? 00:00:02 /sbin/init auto noprompt
root 2 0 0 18:29 ? 00:00:00 [kthreadd]
root 4 2 0 18:29 ? 00:00:00 [kworker/0:0H]
root 6 2 0 18:29 ? 00:00:00 [mm_percpu_wq]
root 7 2 0 18:29 ? 00:00:00 [ksoftirqd/0]1)查看整个Linux系统进程之间关系的命令

在这里插入图片描述
gec@ubuntu:~$ pstree
//所有的进程的祖先进程都是init,由操作系统镜像启动文件产生
(这句话没有错误如果是ubuntu12.04版本,最开始显示的就是init)
systemd─┬─ManagementAgent───6*[{ManagementAgent}]
├─ModemManager─┬─{gdbus}
│ └─{gmain}
├─NetworkManager─┬─dhclient
│ ├─dnsmasq
│ ├─{gdbus}
│ └─{gmain}
├─VGAuthService
├─accounts-daemon─┬─{gdbus}
│ └─{gmain}
├─acpid
├─agetty
├─avahi-daemon───avahi-daemon
├─bluetoothd
├─colord─┬─{gdbus}
│ └─{gmain}
├─cron
说明:
可以看到,最开始的系统进程叫systemd,这个进程的诞生比较特别,
root 8 2 0 18:29 ? 00:00:00 [rcu_sched]
在这里插入图片描述
3)查看进程CPU的占用率 —》top (动态)
gec@ubuntu:~$ top
top - 19:20:52 up 30 min, 1 user, load average: 0.60, 0.31, 0.14
Tasks: 236 total, 2 running, 171 sleeping, 0 stopped, 0 zombie
%Cpu(s): 5.6 us, 1.7 sy, 0.0 ni, 92.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st (CPU的占用率)
KiB Mem : 985312 total, 107608 free, 565800 used, 311904 buff/cache (内存的占用率)
KiB Swap: 1046524 total, 1019132 free, 27392 used. 218520 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 185064 4804 3496 S 0.0 0.5 0:01.40 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/0:+
6 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 mm_percpu_+
7 root 20 0 0 0 0 S 0.0 0.0 0:00.16 ksoftirqd/0
8 root 20 0 0 0 0 I 0.0 0.0 0:00.44 rcu_sched
9 root 20 0 0 0 0 I 0.0 0.0 0:00.00 rcu_bh
在这里插入图片描述
小练习:写一个进程每隔一秒打印一句helloworld和次数,执行10秒;用指令观察ps -ef来观察(或者动态查看)
进程运行时的进程ID和父进程ID;当进程结束之后再观察它的进程ID和父进程ID配送-

代码参考:

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

int main()
{
    while (1)
    {

        printf("hello world\n");
        sleep(1);
    }

    return 0;
}

运行结果:
在这里插入图片描述

(1)运行时:(有a.out)
在这里插入图片描述

(2)运行结束后:(无a.out)
在这里插入图片描述

二、进程的状态

1.进程从诞生到死亡会经历哪些状态?

就绪态 TASK_RUNNING 等待CPU资源 不占用CPU资源,不运行代码。
运行态 TASK_RUNNING 占用CPU资源,运行代码。(执行态)
暂停态 TASK_STOPPED 占用CPU资源,不运行代码。 可以到就绪态。
睡眠态 占用CPU资源,运行代码。 可以到就绪态。
TASK_INTERRUPTIBLE 响应信号 ----》浅度睡眠 pause()—>一直等待下一信号
TASK_UNINTERRUPTIBLE 不响应信号 ----》深度睡眠
僵尸态 EXIT_ZOMBIE 占用CPU资源,不运行代码。不可以到运行态。进程退出的时候,就一定会变成僵尸态
死亡态 EXIT_DEAD 不占用CPU资源,不运行代码。进程退出的时候,如果有人去帮自己回收资源,那么僵尸态就会变为死亡态
在这里插入图片描述

2.什么是僵尸态?(难点)

进程结束时,就从运行态变成僵尸态,所谓僵尸态,就是代表这个进程所占用的CPU资源和自身的任务结构体没有被释放,这个状态的进程就是僵尸态进程。

3.总结:

1)进程在暂停态时,收到继续的信号时,是切换到就绪态,而不是运行态。(重点)
2)程序的main函数执行return 0就会导致进程的退出,一定会变成僵尸态。
3)进程不可以没有父进程,也不能同时拥有两个父进程。
4)祖先进程一定要帮其他的进程回收资源。
5)孤儿进程特征就是失去父进程时,会马上寻找继父,而不是等到孤儿进程变成僵尸态再找。(孤儿进程和僵尸进程的区别)---->(重难点)
``

三、进程的函数接口

单进程程序 -> 只能一行行代码去执行。(只能运行一个while(1) )
多进程程序 -> 同时执行两行代码 -> 产生一个子进程,帮自己处理另外一件事情。(可以同时运行多个 while(1))

1.如何在一个正在运行的进程中产生一个子进程? -> fork()

#include <unistd.h>
pid_t fork(void);
函数作用:创建一个进程,将父进程的资源复制一份,申请一片新的资源给子进程,他们的资源都是独立的
返回值:
成功: (一次调用,两次返回)

0 父进程 但是返回的ID号表示的是子进程的ID号
=0 子进程
失败
返回 -1
说明:
如果进程ID最大值没有达到系统进程数的上限,子进程比父进程ID数值大;(通常情况)
但是如果进程ID达到上限,系统会分配之前分配但是已经退出的进程ID给新进程;
这样有可能出现子进程ID数值比父进程小

例题1: 在进程内部创建一个新的子进程,看看会不会同时做两件事情。

代码参考:

#include<stdio.h>
#include <unistd.h>
int main()
{
//这是单进程的程序
printf("main process\n");
/*产生一个子进程*/
fork();
/* 一个父进程,一个子进程 */
/* 接下来的代码,父进程会执行一遍,子进程也会执行一遍 */
printf("after fork\n");

return 0;
}

//------------------------------
结果分析:
结果1:子进程先运行,父进程后运行。
gec@ubuntu:/mnt/hgfs/gongxiang/lianxi1/WL$ gcc biji.c
gec@ubuntu:/mnt/hgfs/gongxiang/lianxi1/WL$ ./a.out
main process  -> 原来的进程打印出来的
after fork -> 子进程打印出来
after fork-> 父进程打印出来

结果2:父进程先运行,子进程后运行。
gec@ubuntu:/mnt/hgfs/gongxiang/lianxi1/WL$ gcc biji.c
gec@ubuntu:/mnt/hgfs/gongxiang/lianxi1/WL$ ./a.out
main process  -> 原来的进程打印出来的
after fork-> 父进程打印出来

运行结果:
在这里插入图片描述
注意:只有父进程退出,才会出现命令行,子进程退出是不会出现命令行。(但不能说父进程一定
一定是比子进程先退出,只是从此时的现象观察是父进程先退出)。

区分父子进程的方法:
方法一:通过延时来区分父子进程
举例: 想确保子进程先运行,做法: 就是让父进程先睡眠。
该方法的弊端:虽然父进程睡眠了,但其实子进程也睡眠;最好的办法是区分开参照方法二
方法二:通过进程的返回值来区分父子进程
-> 子进程不用睡眠,父进程需要睡眠。 -> 父子进程任务不一样。 -> 思路: 通过返回值判断。

代码参考:

#include<stdio.h>
#include <unistd.h>
int main()
{
    /*单进程的程序*/
    printf("main....\n");
    pid_t id = fork(); //创建一个子进程
    if(id == -1) //失败
    {
        perror("fork error");
        return -1;
    }
    else if(id > 0) //父进程 ,但是返回的ID号 是 子进程的ID号
    {
        // while(1)
        // {
        //  printf("我是你爹.....,我的儿子身份ID是:%d\n",id);
        //  sleep(1);
        // }
        sleep(1);
        printf("我是你爹.....,我的儿子身份ID是:%d\n",id);
    }
    else if(id == 0)//子进程
    {
    // while(1)
    // {
    //  printf("我是你的好大儿.....\n");
    //  sleep(1);
    // }
        printf("我是你的好大儿.....\n");   
    }
    printf("hello\n");
    
    return 0;

}

运行结果:
在这里插入图片描述
结论:
1)父子进程执行顺序随机的。(前提是父子进程之间没有加延时来区分先后)
2)fork()之后的代码,两个进程都会执行。

小练习:写一个程序,使得子进程每隔1S就打印一次apple,父进程每隔2S就打印一次hello,指令查看id号。

代码参考:

#include<stdio.h>
#include <unistd.h>
int main()
{
//单进程的程序
printf("main....\n");
int val = 10;
pid_t id = fork(); //创建一个子进程
if(id == -1) //失败
{
perror("fork error");
return -1;
}
else if(id > 0) //父进程 ,但是返回的ID号 是 子进程的ID号
{
while(1)
{
val +=10;
printf("父进程 hello:%d\n",val);
sleep(2);
}
}
else if(id == 0)//子进程
{
while(1)
{
printf("子进程 apple:%d\n",val++);
sleep(1);
}
}
//父子进程都会执行的代码块
printf("hello\n");
return 0;
}

运行结果:
在这里插入图片描述

2.在一个进程中查看自己的PID号以及父进程的PID号

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); //获取自己的PID号
pid_t getppid(void); //获取父进程的PID号
返回值
成功:
getpid----》返回自己的ID号
getppid—》返回父进程的ID号
失败:不会失败

四、僵尸进程与孤儿进程

**孤儿进程:**一般情况下,调用fork()函数创建的子进程,父进程如果比子进程先退出,那么这个子进程称之为孤儿进程。那么,祖先进程init就会成为该子进程的父进程,回收该子进程的资源。(老爸走了 儿子还在 大家庭收养)
**僵尸进程:**父进程还存在,但是去做的别的事情了(比如在一个死循环,没有退出),此时子进程退出之后,就变成了僵尸进程。
(可以用ps -ef 查看,进程的状态栏为defunct,这就是所谓的“僵尸”进程)(老爸还在,但没空管儿子,儿子在外流浪)

1.孤儿进程的测试程序

代码参考:

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

/*
   5秒之前子进程正常,5秒之后子进程变为孤儿进程(子进程退出之后父进程没有回收其资源)

   0---->5----------->10----->
           孤儿进程       
*/

int main()
{
    pid_t id;
    id = fork();
    if (id == -1)
    {
        perror("fork fail");
        return -1;
    }
    else if (id > 0) // 父进程
    {
        // 父进程5秒之后结束
        int cnt = 5;
        while (cnt--)
        {
            sleep(1);
            printf("[%d]我是你爹 马上收摊 回家给你做饭 等我 %d\n", getpid(),cnt);
        }
    }
    else if (id == 0) // 子进程
    {
        int cnt = 10;
        while (cnt--)
        {
            sleep(1);
            printf("[%d]我是你的好大儿,在家学习嵌入式 等你回家吃饭 %d\n", getpid(),cnt);
        }
    }

    return 0;
}

运行结果:
在这里插入图片描述说明:
假如父进程先走了,子进程一直在while(1)循环,此时命令行终端无法结束子进程
就用命令kill - 9 (子进程id号)

2.僵尸进程的测试程序

代码参考:

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

/*
   5秒之前子进程正常,5秒之后子进程变为僵尸进程(子进程退出之后父进程没有回收其资源)

   0------->5-------->
             僵尸进程   
*/

int main()
{
    pid_t id;
    id = fork();
    if (id == -1)
    {
        perror("fork fail");
        return -1;
    }
    else if (id > 0) // 父进程
    {
        // 父进程它可以独立运行一个while(1)
        while (1)
        {
            sleep(1);
            printf("[%d]我是你爹,我在打牌,不要烦我...\n", getpid());
        }
    }
    else if (id == 0) // 子进程
    {
        // 子进程可以独立运行一个while(1)
        int cnt = 5;
        while (cnt--)
        {
            sleep(1);
            printf("[%d]我是你的好大儿,准备出去玩,给钱我.... %d\n", getpid(), cnt);
        }
    }

    return 0;
}

运行结果:
在这里插入图片描述

小练习:创建一个子进程,子进程打印hello后父进程打印world,之后父进程再创建两条进程,一个进程打印end,另外一个进程打印start
代码参考:

#include<stdio.h>
#include <unistd.h>
int main()
{
    printf("main id:%d\n",getpid());
    //创建一条子进程id1
    pid_t id1 = fork();
    if(id1>0) //父进程
    {
        printf("父进程 id:%d world\n",getpid());
        pid_t id2 = fork();
        if(id2>0) //父进程
        {
            pid_t id3 = fork();
            if(id3 >0) //父进程
            {
                sleep(1);
            }
            else if(id3 == 0) //子进程3
            {
                printf("子进程3:%d end\n",getpid());
            }
        }
        else if(id2 == 0)//子进程2
        {
            printf("子进程2:%d end\n",getpid());
        }
    }
    else if(id1 == 0) //子进程1
    {
        printf("子进程1:%d hello\n",getpid());
        return 0;
    }
    return 0;
}

运行结果:
在这里插入图片描述
总结:
孤儿进程是在失去父进程的时候马上寻找继父,祖先进程init就会成为该子进程的父进程,回收该子进程的资源。
僵尸进程就是父进程还在,子进程先退出;但是子进程资源没有被回收,危险。(只要父进程最后退出,所有资源都会被回收)

五、解决僵尸态问题

1.父进程还在,主动回收资源 -> wait()

功能: wait for process to change state
//等待一个进程为的就是帮这个进程修改状态 (从僵尸态变成死亡态)
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
参数:
status:监听子进程的退出状态
填NULL,例如: wait(NULL) -> 代表父进程只回收资源,但是不关心子进程的退出状态。(常用方法)
不填NULL,例如: int x; wait(&x);-> 代表父进程不仅回收资源,还关心子进程的退出状态,退出状态会保存到x变量中。(子进程的退出状态有两种:正常退出和异常退出)
返回值:
成功:回收资源的那个子进程的ID号
失败:-1

注意:
wait(&state)的state的值要和exit(status)一起使用,才可以查看state的状态值。
wait属于一个阻塞函数,如果子进程没有 退出变成僵尸态,那么这个函数就会阻塞,直到子进程变成僵尸态之后,才会将子进程的资源回收。status还可以保存子进程的退出状态。

情况一: 父进程工作时间长,子进程工作时间短。

代码参考:

#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
//情况1 : 父进程的运行时间比子进程的运行时间长
printf("main id:%d\n",getpid());
//创建一条子进程id1
pid_t id1 = fork();
if(id1>0) //父进程
{
int cnt=10;
while(cnt--){
sleep(1);
printf("父进程 id:%d 当前正在执行任务:%d\n",getpid(),cnt);
}
//父进程主动回收子进程的资源
wait(NULL);
}
else if(id1 == 0) //子进程1
{
printf("子进程1:%d\n",getpid());
printf("子进程1 退出...\n");
}
return 0;
}

运行结果:
在这里插入图片描述情况二: 父进程工作时间短,子进程工作时间长

代码参考:

#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
#if 0
//情况1 : 父进程的运行时间 比 子进程的运行 时间 长
printf("main id:%d\n",getpid());
//创建一条子进程id1
pid_t id1 = fork();
if(id1>0) //父进程
{
int cnt=10;
while(cnt--){
sleep(1);
printf("父进程 id:%d 当前正在执行任务:%d\n",getpid(),cnt);
}
//父进程主动回收子进程的资源
wait(NULL);
}
else if(id1 == 0) //子进程1
{
printf("子进程1:%d\n",getpid());
printf("子进程1 退出...\n");
}
#endif
//情况2 : 父进程的运行时间 短  子进程的运行 时间 长
printf("main id:%d\n",getpid());
//创建一条子进程id1
pid_t id1 = fork();
if(id1>0) //父进程
{
printf("父进程 id:%d 准备退出\n",getpid());
//父进程主动回收子进程的资源
//会阻塞等待 子进程的退出,回收资源
wait(NULL);
}
else if(id1 == 0) //子进程1
{
int cnt =10;
while(cnt--){
sleep(1);
printf("子进程1:%d 还在运行 %d\n",getpid(),cnt);
}
}
return 0;            
}

运行结果:
在这里插入图片描述

2.父进程还在,不主动回收资源

验证父进程还在,但是不主动回收资源,结果就是这个子进程会一直等待父进程的退出之后,然后去寻找继父帮自己回收资源。(只要主进程退出,那么僵尸进程的资源同样会被系统回收)

3.父进程先退出

父进程比子进程先退出,此时子进程就变成了孤儿进程,孤儿进程会马上找继父,孤儿进程退出之后由继父回收资源。

六、进程的退出 -> exit() /_Exit() / _exit()

这个函数功能都是可以让进程退出,并且表示自己的退出状态,并传递给父进程。

1.exit() ----会清除缓冲区

功能: cause normal process termination//导致一个进程的结束。
#include <stdlib.h>
void exit(int status);
函数作用:
先清洗缓冲区,再退出
参数:
status: 退出状态值
0 -> 进程正常退出
非0 -> 进程异常退出
返回值:无

2._Exit() / _exit()--------不会清洗缓冲区,直接退出

功能: terminate the calling process
//结束一个正在运行的进程
#include <unistd.h>
void _exit(int status);
#include <stdlib.h>
void _Exit(int status);
参数:
status: 退出状态值
0 -> 进程正常退出
非0 -> 进程异常退出
返回值:无

说明:
1.wait(&state)的state的值要和exit(status)一起使用,才可以查看state的状态值。
2.在linux中,以_开头的函数通常是被调用的函数## 3.缓冲区问题

3.缓冲区问题

#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
/单进程的程序/
printf("main ");
//先清洗缓冲区,再退出当前进程
//exit(0);//return 0;
//直接退出当前进程,不会帮助你清洗缓冲区
_exit(0);
printf(“11112222\n”);
}

4.exit()与return语句区别

例子1:
int main(int argc,char *argv[])
{
printf(“helloworld!\n”);
return 0;
}//输出helloworld

例子2:
int main(int argc,char *argv[])
{
printf(“helloworld!\n”);
exit(0);

}//输出helloworld

例子3:
int fun()
{
return 0;
}
int main(int argc,char *argv[])
{
fun();
printf(“helloworld!\n”);
return 0;
}//输出helloworld

例子4:
int fun()
{
exit(0); -> 整个进程马上结束
}
int main(int argc,char *argv[])
{
fun();
printf(“helloworld!\n”);
return 0;
}//不会输出helloworld

结论:
1)在main函数中,exit()与return语句作用是一样。 -> 因为main函数的返回就等价于进程的退出。
2)不在main函数中,exit()代表进程的退出,return只是代表函数的返回。

5.退出状态。 -> 父进程监听子进程的退出状态,如果子进程正常退出在,则输出一个ok,如果子进程异常退出,则输出一个error

代码参考:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
/*
    通过fork函数的返回值来区分父子进程
*/

int main()
{
    pid_t id;
    id = fork();
    if (id == -1)
    {
        perror("fork fail");
        return -1;
    }
    else if (id > 0) // 父进程的空间
    {
        int status;

        // 阻塞等待子进程的退出回收其资源,同时接收它的退出状态
        wait(&status);
        if(status == 0)
        {
            printf("OK\n");
        }
        else
        {
            printf("no ok\n");
        }

        //阻塞等待子进程的退出回收其资源,不接收它的退出状态--->常用
        // wait(NULL); //这种用法比较多

        printf("[%d]我是你爹 好大儿的id号是%d\n", getpid(), id);
        sleep(1);
    }
    else if (id == 0) // 子进程
    {

        printf("[%d]我是你的好大儿 爹的id号是%d\n", getpid(), getppid());
        sleep(1);

        // exit(0); //0--->表示子进程正常退出

        //非0异常
        exit(-1); //-1--->表示子进程异常退出
        exit(5); //-1--->表示子进程异常退出
    }

    return 0;
}

运行结果:
在这里插入图片描述


总结

总结…懒得总结…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值