信息安全系统设计与实现:第三章学习笔记

信息安全系统设计与实现:第三章学习笔记

20191331 lyx

教材学习内容总结

第三章 Unix/Linux进程管理

学习目标

学习了解多任务处理原则。充分理解进程和线程的概念。掌握进程的执行与建立,例如系统启动期间的初始进程、INIT进程、守护进程、登录进程以及可供用户执行命令的 sh 进程。学习和使用用于进程管理的Unix/Linux 系统调用,包括fork、wait、exec 和 exit。通过编写简单程序理解多任务处理、上下文切换和进程处理的各种原则和方法。

多任务处理

多任务处理简单说,就是同一时间给多个程序运行处理数据。

即使在系统中通常有许多其他的程序在运行,但进程也可以向每个程序提供一种假象,仿佛它在独占地使用处理器。但事实上进程是轮流使用处理器的。我们假设一个运行着三个进程的系统,如下图所示:

信息安全系统设计与实现:第三章学习笔记

UNIX

Unix是实时的、分布式系统,采用时间片方式,轮流给多个提交CPU处理请求的进程分配资源,支持多用户、多任务处理。

Windows

Windows多任务处理采用的是虚拟机技术,在内存中创建逻辑微机 (也是通过时间片轮流占用CPU),由它来运行处理程序。

总结来说,多任务处理就是指同时运行多个程序并且程序之间可以随意切换。

“多任务处理是所有操作系统的基础,也是并行编程的基础。”

我们可以通过ps命令来查看系统下的进程

信息安全系统设计与实现:第三章学习笔记

进程的概念

在操作系统中任务也称为进程。

进程是对映像的执行

操作系统内核将一系列执行视为使用系统资源的单一实体。系统资源包括内存空间、I/O设备以及最重要的CPU时间。PROC结构体包含了某个进程的所有信息。

多任务处理系统

  • type.h文件定义了系统常熟和表示进程的简单PROC结构体

  • ts.s在32位GCC汇编代码中可实现进程上下文切换

  • queue.c文件可实现队列和链表操作函数。

    enqueue()函数按优先级将PROC输入队列中。在优先级队列中,具有相同优先级的进程按照 FIFO的顺序排序。

    dequeue()函数可返回从队列或链表中删除的第一个元素。

    printList()函数可打印链表元素。

  • t.c文件定义MT系统数据结构、系统初始化代码和进程管理函数

详细结构体及源代码参见教材《Unix/Linux系统编程》P88-92

虚拟CPU:MT系统在Linux下编译链接为gcc -m32 t.c ts.s

init():当MT系统启动时,main()函数调用init()以初始化系统。

P0调用kfork()来创建优先级为1的子进程P1,并将其输入就绪队列中。

tswitich():tswitch()函数实现进程上下文切换。

tswitch()中的SAVE函数:当正在执行的某个任务调用tswitch()时,它会把返回地址保存在堆栈上,并在汇编代码中进入tswitch()。

scheduler():在执行了SAVE函数之后,任务调用scheduler()来选择下一个正在运行的任务。

tswitch()中的RESUME函数

kfork():kfork()函数创建一个子任务并将其输入readyQueue中。

body():所有创建的任务都执行同一个body()函数。

空闲任务 P0:P0的特殊之处在于它所在任务中具有最低的优先级

运行多任务处理(MT)系统

MT系统实践

实践平台:OpenEuler 20.03LTS

信息安全系统设计与实现:第三章学习笔记

编写书上给出的代码

信息安全系统设计与实现:第三章学习笔记

信息安全系统设计与实现:第三章学习笔记

代码已托管:https://gitee.com/DKY2019/xxaqxt/tree/master/第三章 MTsys 实践

编译时发生错误

信息安全系统设计与实现:第三章学习笔记

经过查阅资料

得知缺少glibc-devel

使用 yum install 安装

信息安全系统设计与实现:第三章学习笔记

显然OpenEuler操作系统无法支持该编译命令,其库中缺少必要的库。

进程同步

一个操作系统包含许多并发进程,这些进程可以彼此交互。进程同步是指控制和协调进程交互以确保其正确执行所需要的各项规则和机制。最简单的进程同步工具是休眠和唤醒操作。

  • 睡眠模式

为实现休眠操作,我们可以在 PROC结构体中添加一个event字段,并实现ksleep(int event)函数,使进程进入休眠状态。

ROC结构体进行修改以包含加粗显示的添加字段

信息安全系统设计与实现:第三章学习笔记

  • 唤醒操作

当某个等待时间发生时,另一个执行实体将会调用kwakeup(event),唤醒正处于休眠状态等待该事件值的所有程序。如果没有任何程序休眠等待该程序,kwakeup()就不工作。kwakeup算法为。

信息安全系统设计与实现:第三章学习笔记

进程终止

  • 正常终止:进程调用exit(value),发出 exit(value)系统调用来执行在操作系统内核中的 kexit(value),这就是我们本节要讨论的情况。

  • 异常终止:进程因某个信号而异常终止。信号和信号处理将在后面第6章讨论。在这两种情况下,当进程终止时,最终都会在操作系统内核中调用kexit()。

进程家族树

信息安全系统设计与实现:第三章学习笔记

子进程终止

在任何时候,进程都可以调用内核函数

pid = kwait(int *status)

在kwait算法中,如果没有子进程,则进程会返回-1,表示错误。

否则,他将搜索僵尸子进程。如果他找到僵尸子进程,就会收集僵尸子进程的pid和退出代码。相应的,当进程终止时,他必须发出:

kwakeup(running->parent

进程的启动和退出

信息安全系统设计与实现:第三章学习笔记

进程管理系统调用实践

exec函数族

exec系列函数共有7个函数可供使用,这些函数的区别在于:指示新程序的位置是使用路径还是文件名,如果是使用文件名,则在系统的PATH环境变量所描述的路径中搜索该程序;在使用参数时使用参数列表的方式还是使用argv[]数组的方式。

exec函数族可简要表示为

#include <unistd.h>

int execl(const char *pathname, const char *arg0, ... /* (char *)0 */ );
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, .../* (char *)0, char *const envp[] */ );
int execve(const char *pathename, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */ );
int execvp(const char *filename, char *const argv[]);
int fexecve(int fd, char *const argv[], char *const envp[]);

  • execl()

execl()函数用来执行参数path字符串所指向的程序,第二个及以后的参数代表执行文件时传递的参数列表,最后一个参数必须是空指针以标志参数列表为空.

#include <unistd.h>  
      
    int main()  
    {  
            execl("/bin/ls","ls","-l","/etc",(char *)0);
            return 0;  
    } 

信息安全系统设计与实现:第三章学习笔记

  • execv()

execv()函数函数用来执行参数path字符串所指向的程序,第二个为数组指针维护的程序参数列表,该数组的最后一个成员必须是空指针。

#include <unistd.h>  
    
    int main()  
    {  
            char *argv[] = {"ls", "-l", "/etc"/*,(char *)0*/};  
            execv("/bin/ls", argv);
            return 0;  
    }
  • 其他族函数等以后有时间再做总结

信息安全系统设计与实现:第三章学习笔记

fork函数

fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。

一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

调用一次fork()之后的进程如下:

信息安全系统设计与实现:第三章学习笔记

以下面这个程序为例:

int main(){  
    printf("it's the main process step 1!!\n\n");  
    
    fork();//创建一个新的进程
    
    printf("step2 after fork() !!\n\n");  
    
    int i; scanf("%d",&i);//防止程序退出  

    return 0;  
}  

根据上面调用fork()的示意图不难理解,程序在fork()函数之前只有一条主进程,所以只打印一次step 1;而执行fork()函数之后,程序分为了两个进程,一个是原来的主进程,另一个是fork()的新进程,他们都会执行fork()函数之后的代码,所以step 2打印了两次。

信息安全系统设计与实现:第三章学习笔记

wait函数

编程过程中,有时需要让一个进程等待另一个进程,最常见的是父进程等待自己的子进程,或者父进程回收自己的子进程资源包括僵尸进程。这里简单介绍一下系统调用函数:wait()

wait的使用:

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

pid_t wait(int *status);

wait的功能:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

管道

管道是Linux由Unix那里继承过来的进程间的通信机制,它是Unix早期的一个重要通信机制。其思想是,在内存中创建一个共享文件,从而使通信双方利用这个共享文件来传递信息。由于这种方式具有单向传递数据的特点,所以这个作为传递消息的共享文件就叫做“管道”。

管道时用于进程交换数据的单向进程件通信通道。管道有一个读取端和一个写入端。

  • 管道命令处理

cmd1 | cmd2
sh将通过一个进程运行cmd1,并通过另一个进程运行cmd2,他们通过一个管道连接在一起,因此cmd1的输出变为cmd2的输入.

  • 命令管道

命令管道又叫FIFO

(1)在sh中,通过mknod命令创建一个命令管道:

(2)或在c语言中发出mknod()系统调用

(3)进程可像访问普通文件一样发个文命名管道。

myshell编程实现

shell循环可以参考

信息安全系统设计与实现:第三章学习笔记

代码:

#include <stdio.h>
#include <unistd.h>
#include <wait.h>
#include <stdlib.h>
#include <string.h>
#define MAX 128

void eval (char *cmdline);  //对用户输入的命令进行解析
int parseline (char *buf, char **argv);
int builtin_command(char **argv);

int main()
{
    char cmdline[MAX];
    
    while(1){
        printf("root@-VirtualBox:lyx-myshell$ ");
        fgets(cmdline,MAX,stdin);
        if(feof(stdin))
        {
            printf("error");
            exit(0);
        }
        eval(cmdline);
    }
}

void eval(char *cmdline)
{
    char *argv[MAX];
    char buf[MAX];
    int bg;
    pid_t pid;
    strcpy(buf,cmdline);
    bg = parseline(buf,argv);
    if(argv[0]==NULL)
        return;
    if(!builtin_command(argv)) 
    {   
    if((pid=fork()) == 0)
    {
        if(execvp(argv[0],argv) < 0) {
            printf("%s : Command not found.\n",argv[0]);
            exit(0);
        }
    }

    if(!bg){
        int status;
        if(waitpid(-1,&status,0) < 0) 
            printf("waitfg: waitpid error!");
    }
    else
        printf("%d %s",pid, cmdline);
    return;
    }
}

int builtin_command(char  **argv)
{
    if(!strcmp(argv[0], "quit"))
        exit(0);
    if(!strcmp(argv[0],"&"))
        return 1;
    return 0;
}

int parseline(char *buf,char **argv)
{
    char *delim;
    int argc;
    int bg;

    buf[strlen(buf)-1]=' ';
    while(*buf && (*buf == ' '))
        buf++;

    argc=0;
    while( (delim = strchr(buf,' '))){  
        argv[argc++] = buf;
        *delim= '\0';
        buf = delim + 1;
        while(*buf && (*buf == ' '))
            buf++;
    }

    argv[argc] = NULL;
    if(argc == 0)
        return 1;
    if((bg=(*argv[argc-1] == '&')) != 0)
        argv[--argc] = NULL;
    return bg;
}

运行结果:

信息安全系统设计与实现:第三章学习笔记

进程管理相关练习已上传码云:https://gitee.com/DKY2019/xxaqxt/tree/master/linux进程管理 PMex 相关练习

参考资料

使用 gcc -m32 编译32位程序时发生错误错误:https://www.cnblogs.com/motadou/archive/2009/11/14/1603159.html

系统编程之进程 https://www.cnblogs.com/happybirthdaytoyou/p/14301052.html

exec族函数 https://blog.csdn.net/u014530704/article/details/73848573

fork函数 https://blog.csdn.net/kxjrzyk/article/details/81603049

wait函数详解 https://blog.csdn.net/wyhh_0101/article/details/83933308

linux 管道 https://blog.csdn.net/qq_38410730/article/details/81569852





20191331lyx
2021/10/23

上一篇:C语言冒泡排序


下一篇:Redis核心原理与实践--Redis启动过程源码分析