操作系统-进程

文章详细阐述了操作系统移植的原因,包括提高软件硬件的兼容性、开发效率和多任务处理能力。接着,深入讨论了Linux中的多任务编程,如进程和线程的概念,进程的状态以及如何通过fork、exec函数创建和调度进程。同时提到了进程的同步问题和资源管理,最后讨论了进程的退出、等待机制以及waitpid函数的使用。
摘要由CSDN通过智能技术生成

为什么移植操作系统?
1、软硬件的耦合度,提高然间的移植性(开发模式)
2、操作系统提供很多库和工具,提高开发效率
3、操作系统提供多任务机制
4、操作系统提供了丰富的网络协议栈,实现远程传输
嵌入式Linux多任务编程(多进程、多线程)
1、什么是多任务?
·单任务 和 多任务
单任务 一个任务执行之后,才可以执行下一个任务
    一个任务在执行的过程中,不可以执行下一个任务,只有执行完之后,才可以执行下一个
多任务 在响应一个任务的时候,中途回去响应其他任务,之后再返回响应原本的任务
·单核CPU 和 多核CPU
同时执行必须建立在多核的情况下
·并发 和 并行
并发 多个任务,但是只能处理其中一个任务
并行 多个任务,可以同时响应多个任务,但是执行的时候可能不是并行
2、多任务操作的实现方式:进程、线程

一、进程的概念

程序与进程的差别
程序是“静态”的,进程是“动态”的
进程是暂时的,程序是永久的。
进程与程序的组成不同。
进程与程序的对应关系。
​
程序是被动的实体,如存储在磁盘上包含一系列指令的文件(经常被称为可执行文件)。
进程则是活动实体,具有一个程序计数器用于表示下个表示执行命令和一组相关资源。
Linux系统是一个多进程系统,具有并行性、互不干扰的特点。每个进程都是一个独立的运行单位,拥有各自的权利和责任。
其中每个进程都运行在独立的虚拟地址空间,因此,即使一个进程发生异常,他也不会影响系统中的其他进程。
缺点
自己独立的空间,安全多任务机制(互不干扰);开销很大(进程的创建、进程的切换)
进程的状态
新的:进程正在创建
运行:指令正在执行
等待:进程等待发生的某个事件(如I/O完成或收到信号)
就绪:进程等待分配处理器
种植:进程已经执行完毕
进程中每个进程表示,采用进程控制块(PCB),也称为任务控制模块。
包含很多相关信息
进程状态
程序计数器
CPU寄存器
CPU调度信息
内存管理信息
记账信息
I/O状态信息
获得pid
pid给每个进程的选序号
创建一个task_struct结构体变量
getpid函数:获取调用该函数进程的进程pid
getppid函数:获取调用该函数进程的父进程pid,第一个p是parent,第二个是process
Linux的进程表示
C语言结构的task_struct来表示(双向链表)
位于内核源代码目录内的头文件<linux/sched.h>
结构体里面包含:进程状态、调度、内存管理信息、打开文件列表、指向父进程的指针及指向子进程和兄弟进程列表的指针等。
父进程(parent process):创建它的进程
子进程(child process):为它自身创建的进程
兄弟进程(sibling process):为具有同一父进程的进程

二、进程的调度

通过ps命令
-A/-el:显示系统所有的进程(包括守护进程),相当于-e
-x/-f:列出进程的详细信息
-H:显示进程树
-r:只显示正在运行的进程
top
htop:显示资源占用
没有办法改变进程调度的策略-内核解决
进程的基本三态:就绪态、执行态、等待态(用户角度)
分为三态可以更好地进行调度策略

 

1、进程因为等待输入进入而堵塞
2、调度程序选择另一个进程
3、调度程序选择一个进程开始运行
4、出现有效的输入
调度策略
1、抢占式(设置优先级)
高优先级优先
2、非抢占式
时间片轮转(最重要)
先到先服务
短进程优先

细分:

ps -ax可以看到进程的状态
nice -n 5 top查看优先级
    NI[-20,19]:nice值越小,抢占CPU能力越强,nice会影响进程的优先级
    PRI[0,139]进程的优先级,也叫动态优先级,值越小,优先级越高
    实时进程与非实时进程:
        实时进程:优先级[0,99],采用实时进程的调度算法
        非实时进程:优先级[100,139],采用01/CFS等调度方法

进程的分类
·处理器消耗型
·I/O消耗型
进程的同步
临界资源:操作系统中将一次只允许一个进程访问的资源称为临界资源,需要互斥访问。(强行多个进程操作,会导致文件内容的破坏,需要对进程进行排序,有进程访问的时候,其他进程不可以访问)

三、进程的创建

fork函数
头文件:<unistd.h>
函数原型:pid_t fork(void);
函数功能:从调用该函数的进程复制出子进程,被复制进程被称为父进程,复制出来的进程为子进程。
函数参数:无参数
函数返回值:fork调用一次,返回两次,可能三种不同的值。
    父进程的fork,成功返回子进程的PID,失败返回-1,errno被设置。
    子进程的fork,成功返回0,失败返回-1,errno被设置。
写时复制
当我对物理空间进行修改的时候,才算更改,否则就是共享资源
可以节省资源
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <unistd.h>
​
int main(int argc,char **argv)
{
    int count = 0;
​
    __pid_t pid;
​
    pid = fork();
    fork();
    
    count ++;
​
    printf("count = %d\n",count);//出现两次,一次是在子进程,一次是在父进程
​
    # if 0
    if(pid < 0)
    {
        perror("fork error!\n");//创建失败
​
        exit(1);
    }
    if(pid > 0)//父进程做什么与子进程没有关系
    {
        printf("pid = %d\n",pid);
        printf("parent pid = %d\n",getpid());
        printf("parent!\n");
        printf("count = %d\n",count);
    }
    else if(pid == 0)
    {
        count ++;
        printf("child pid = %d\n",getpid());//获取调用该函数进程的进程pid
        printf("child ppid = %d\n",getppid());//获取调用该函数进程的父进程pid,第一个p是parent,第二个是process
        printf("child!\n");
        printf("count = %d\n",count);
    }
    #endif
​
    return 0;
}

小测试-读写鼠标键盘,同时证明进程拥有自己的独立空间

#include <stdlib.h>
#include <error.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
​
int main(int argc, char const *argv[])
{
    __pid_t pid;
​
    //int fd = open("a.txt",O_RDWR | O_CREAT | O_APPEND,0655);//进程空间共享
​
    // if(fd == -1)
    // {
    //     perror("open file error!");
    //     exit(1);
    // }
​
    //int fd = open;
​
    pid = fork();
​
    if(pid < 0)
    {
        perror("fork error");
        exit(1);
    }
    if(pid > 0)
    {
        #if 0
        int fd = open("dev/input/mouse",O_RDWR);
        int cor;
​
        while(1)
        {
            read(fd,&cor,sizeof(cor));
​
            printf("cor = %d\n",cor);
            sleep(3);
        }
​
        #endif
​
        // while(1)
        // {
            int fd = open("a.txt",O_RDWR | O_CREAT |O_APPEND,0655);
            write(fd,"hello",5);
            write(fd,"world",5);
            write(fd,"\n",1);
        // }
        
​
    }else if(pid == 0)
    {
        #if 0
        char buffer[1024];
​
        while(1)
        {
            memset(buffer, 0, sizeof(buffer));
            int n_r = read(0,buffer,sizeof(buffer)-1);//实际读到的字节数
            buffer[n_r] = '\0';
            printf("buffer = %s\n",buffer);
        }
        #endif
​
        // while(1)
        // {
            int fd = open("a.txt",O_RDWR | O_CREAT |O_APPEND,0655);
            write(fd,"hhh",3);
            write(fd,"www",3);
            write(fd,"\n",1);
        // }
    }
    return 0;
}

四、进程的创建-exec()函数族

exec函数族
功能:执行一个二进制文件
头文件:<unistd.h>
命名规则:
L:参数以列表的形式出现
V:参数以数组的形式出现
E:为新进程提供新的环境变量
P:在用户的绝对路径path下查找可执行文件,该文件必须在用户路径下,可以指定程序文件名
重点函数:
int execl(const char *path,const char *arg,...);
int execv(const char *path,char *const argv[],...);
int execvpe(const char *file,char *const argv[],char *const envp[]);
#include <stdlib.h>
#include <error.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
​
int main(int argc, char const *argv[])
{
    __pid_t pid;
​
    //int fd = open("a.txt",O_RDWR | O_CREAT | O_APPEND,0655);//进程空间共享
​
    // if(fd == -1)
    // {
    //     perror("open file error!");
    //     exit(1);
    // }
​
    //int fd = open;
​
    pid = fork();
​
    if(pid < 0)
    {
        perror("fork error");
        exit(1);
    }
    if(pid > 0)
    {
        #if 0
        int fd = open("dev/input/mouse",O_RDWR);
        int cor;
​
        while(1)
        {
            read(fd,&cor,sizeof(cor));
​
            printf("cor = %d\n",cor);
            sleep(3);
        }
​
        #endif
​
        // while(1)
        // {
            // int fd = open("a.txt",O_RDWR | O_CREAT |O_APPEND,0655);
            // write(fd,"hello",5);
            // write(fd,"world",5);
            // write(fd,"\n",1);
        // }
​
        execlp("./write1","./write1",NULL);
​
​
        
​
    }else if(pid == 0)
    {
        #if 0
        char buffer[1024];
​
        while(1)
        {
            memset(buffer, 0, sizeof(buffer));
            int n_r = read(0,buffer,sizeof(buffer)-1);//实际读到的字节数
            buffer[n_r] = '\0';
            printf("buffer = %s\n",buffer);
        }
        #endif
​
        // while(1)
        // {
            // int fd = open("a.txt",O_RDWR | O_CREAT |O_APPEND,0655);
            // write(fd,"hhh",3);
            // write(fd,"www",3);
            // write(fd,"\n",1);
        // }
​
        execlp("./write2","./write1",NULL);
    }
    return 0;
}
#include <stdlib.h>
#include <error.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
​
int main(int argc, char const *argv[])
{
    int fd = open("a.txt",O_RDWR | O_CREAT |O_APPEND,0655);
    write(fd,"hello",5);
    write(fd,"world",5);
    write(fd,"\n",1);
​
    close(fd);
​
    return 0;
}
#include <stdlib.h>
#include <error.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
​
int main(int argc, char const *argv[])
{
    int fd = open("a.txt",O_RDWR | O_CREAT |O_APPEND,0655);
    write(fd,"hello",5);
    write(fd,"world",5);
    write(fd,"\n",1);
​
    close(fd);
    
    return 0;
}

五、进程创建(vfork\system)

vfork
头文件:#include <sys/types.h>
#include <unsitd.h>
形式:pid_t vfork(void)
功能:创建子进程
​
对fork的改进
    对fork的改进更为彻底、简单粗暴
    vfork是为子进程立即执行exec的程序专门设计的
        无需为子进程复制 虚拟内存页或页表,子进程直接共享父进程的资源
        在子进程调用exec之前,将暂停父进程
使用fork的时候,父子进程是单独独立的,但是使用vfork后,两者的数据会互相产生影响
#include <stdio.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
​
int main(int argc, char const *argv[])
{
    __pid_t pid;
    int count = 0;
​
    pid = vfork();
​
    if(pid < 0)
    {
        perror("vfrok error!");
        exit(1);
    }
​
    if(pid > 0)
    {
        for(int i = 0;i < 3;i++)
        {
            printf("Parent!\n");
            
            printf("conut = %d\n",count);
            sleep(1);//将主动权让出去
        }
    }
    else if(pid == 0)
    {
        for(int i = 0;i < 3;i++)
        {
            sleep(2);
            count++;
            //printf("Child\n");
            //sleep(1);
            execl("./demo","./demo",NULL);
        }
        //只有调用,才不会出现段错误
        //段错误是因为空间共享,空间会释放两次
        //还有一种解除段错误的方法是添加exit(1);
        //exit(1)是异常退出,不会释放空间,进程会让父进程释放
        //当子进程使用exec时,最适合调用vfork
    }
    return 0;
}
​
system函数原型
头文件:#include <stdlib.h>
int system(const char *commard);
功能:创建子进程,并加载新进程程序到子进程空间,运行起来。
参数:新程序的路径名
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
​
int main(int argc, char const *argv[])
{
    printf("hello\n");
    
    //exe函数族
    //execl会覆盖后面的代码
    //execl("./demo","./demo","hello1","hello2",NULL);
​
    //exev将命令封装进一个数组
    // char *arg[] = {"./demo","hello1","hello2",NULL};
    // execv("./demo",arg);
​
    //execlp根据路径选择
    //execlp("./demo","hello1","hello2",NULL);
​
    char *env[]={"USER = admin","PASSWD = admin",NULL};
    char *arg[]={"./demo","hello1","hello2",NULL};
​
    //execvpe("./demo",arg,env);
​
    //execl("./write2",NULL);
    system("./demo hello1 hello2");
    system("clear");
​
    printf("welcome to jsetc\n");
​
    return 0;
}

八、进程退出(exit VS _exit)

_exit
    不刷缓冲区
执行流程:
    关闭进程打开的文件描述符、释放该进程持有的文件锁
    关闭该进程打开的信息量、消息队列
    取消该进程通过mmap()创建的内存映射
    将该进程的所有子进程交给Init托管
    给父进程发送一个SIGCHLD信号(结束信号)
exit函数
POSIX标准和ANSIC定义的标准函数
头文件:#include <stdlib.h>
其实是对系统调用_exit的封装
函数原型:void exit(int status);
函数功能:终止当前进程
参数说明:用于识别进程的退出状态,shell或父进程可以获取该值
    -0:表示进程可以正常退出
    - -1/1:表示进程异常退出
    - 2~n:表示用户可以自定义
echo $?  查看当前进程结束返回值
exit执行流程:
    调用退出处理程序(通过atexit、on_exit注册的函数)
    刷新stdio流缓冲区
    使用由status提供的值执行_exit系统调用函数
        关闭进程打开的文件描述符、释放进程持有的文件锁
        关闭进程打开的信号量、消息队列
        取消该进程通过mmap创建的内存映射
abort函数
头文件:#include <stdlib.h>
定义函数:void abort(void);
函数说明:abort()将引起进程异常的终止,此时所有已打开的文件流会自动关闭,所有的缓冲区数据会自动写回。
返回值:无
两者的区别:
exit是库函数是对_exit系统调用的封装
    调用_exit之前,他会执行各种动作
        调用推出处理程序(通过atext和on_exit注册的回调函数)
        刷新stdio流缓冲区
        使用由status提供的值执行_exit系统调用
atexit/on_exit函数
退出处理程序
    在exit推出后可以自动执行用户注册的退出处理程序
    执行顺序预注册顺序相反
    函数原型:int atexit(void(*function)(void));
    函数原型:int on_exit(void(8function)(int,void*),void *arg);
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
​
#include <stdlib.h>
​
void my_exit(void)
{
    printf("my exit2\n");
}
​
void my_exit2(int ret,void *p)
{
    printf("my exit2\n");
    printf("ret = %d\n",ret);
    printf("p = %s\n",(char*)p);
}
​
int main(int argc,char **argv,char **env)
{
    
    //atexit(my_exit);
    on_exit(my_exit2,"hello world");
​
    printf("hello world!\n");
    exit(1);
​
    while(1);
​
    return 0;
}

 

九、进程等待(僵尸进程、孤儿进程)

使用ps命令查看进程
-A/-e:显示系统所有的进程(包括守护进程),相当于-e
-a:显示所有终端下的所有用户运行的进程
-u:显示用户名、CPU百分比和内存的使用
-x/-f:列出进程的详细信息
-H;显示进程树
-r:只显示正在运行的进程
-o:分类输出
$ ps -axf -o pid,pidd,state,tty,command
监视进程:$top
父进程运行结束时,子进程资源回收
僵尸进程:
    子进程终止了,但是父进程还活着,子进程的资源没有被立即回收,子进程就是僵尸进程。
    
    为什么子进程会变成僵尸进程?
    子进程已经终止不再运行,但是父进程还在运行,它没有释放子进程所占有的资源,所以变成了僵尸进程。
​
孤儿进程:
    子进程活着,父进程终止,子进程就是孤儿进程。
    
    为了能够回收孤儿进程终止后的资源,孤儿进程会被托管给我们前面介绍的pid==1的init进程,每当托管的子进程终止的时候,init就会立即主动回收孤儿进程资源,回收资源的速度很快,所以孤儿进程没有机会变成僵尸进程。
进程PID == 0
作用:
    调度进程,实现进程间的调度和切换,该进程根据调度算法,会让CPU轮换的执行所有的进程。
    怎么实现?
    当pc指向不同的进程时,cpu就去执行不同的进程,这样就可以实现切换。
怎么来的?
    由OS演变而来,OS启动后,最后有一部分代码会持续的执行,这个就是PID==0的进程。
    由于这个进程是OS的一部分,凡是由OS代码演变来的进程,都称之为系统进程。
进程PID==1
作用:
    1)初始化:
    被称为init进程,作用是,会读取各种各样的系统文件,使用文件中的数据来初始化OS的启动。
    2)托管孤儿进程
    3)原始父进程
    原始进程---》进程---》进程---》终端进程---》a.out进程
    
怎么运行的?
    这个进程不是OS演变而来的,不属于OS的代码,这个进程是一个独立的程序,程序代码放在了/sbin/init下,当OS启动起来之后,OS回去执行init程序,将他的我代码加载到内存,这个进程就运行起来了。
进程PID==2
作用:
    页精灵进程,专门负责虚拟内存的请页操作。
    疑问:什么是页精灵?
    也称为守护进程。
    当OS支持虚拟内存机制时,加载应用程序到内存时,并不会进行完整的代码拷贝,只会拷贝当前要运行的那部分代码,当这部分代码运行完毕后,会再拷贝另一部分需要运行的代码到内存中,拷贝时是按照一页一页来操作,每一页大概4096字节,这就是换页操作。

十、进程等待(wait)

wait函数-僵尸进程
函数原型:pid_t wait(int *status)
头文件:#include <sys/wait.h>
函数功能:等待子进程的终止及消息
参数说明:子进程调用exit/_exit时的status
返回值:
    Wait调用成功,会返回已终止子进程的pid
    Wait调用失败,返回-1,设置errno值
        若子进程没有终止,wait调用会阻塞父进程,直到子进程终止,子进程终止后,该调用立即返回。
#include <stdio.h>
#include <sys/wait.h>
#include <error.h>
#include <stdlib.h>
#include <unistd.h>
​
int main(int argc, char const *argv[])
{
    __pid_t pid;
​
    pid = fork();
​
    if(pid < 0)
    {
        perror("fork error");
        exit(1);
    }
​
    if(pid == 0)
    {
        
        for(int i = 0;i < 3;i++)
        {
            printf("helloworld\n");
            sleep(1);
        }
        exit(3);
    }
    else if(pid > 0)
    {
        printf("welcome to jsetc\n");
​
        int ret;
        wait(&ret);
​
        int num = WEXITSTATUS(ret);//获取状态值
​
        printf("child is exit! = %d\n",num);
    }
    return 0;
}

十一、进程等待(waitpid)

waitpid函数
函数原型:
    pid_t waitpid(pid_t pid,int *status,int options);
头文件:
    #include <sys/type.h>
    #include <sys/wait.h> 
函数说明:
    waitpid()会暂停目前进程的执行,知道有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。紫禁城的状态会由参数status返回,而子进程的进程识别码也会一块返回。如果不在意结束状态值,则参数status可以设置为NULL。参数pid为欲等待的子进程识别码,其他数值意义如下:
    pid < -1:等待进程组识别码为pid绝对值的任何进程
    pid = -1:等待任何子进程,相当于wait()
    pid = 0:等待进程组识别码与目前进程相同的任何子进程
    pid > 0:等待任何子进程识别码为pid的子进程
参数Options可以为0或下面的OR组合:
    WNOHANG 如果没有任何已经结束的子进程则马上返回,不予等待
    WUNTRACED  如果子进程进入暂停执行的情况则马上返回,但结束状态不予理会。
    子进程的结束状态返回后存于status,底下有几个宏可判别结束情况:
    WIFEXITED(status)如果子进程正常结束则为非0值
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值