精华内容
下载资源
问答
  • 实验一、并发程序设计 一、实验名称 并发程序设计 二、实验目的 掌握在程序中创建新进程的方法, 观察并理解多道程序并发执行的现象。 三、实验原理 fork():建立子进程。子进程得到父进程地址空间的一个复制。 ...

    实验一、并发程序设计

    一、实验名称

    并发程序设计

    二、实验目的

    掌握在程序中创建新进程的方法, 观察并理解多道程序并发执行的现象。

    三、实验原理

    fork():建立子进程。子进程得到父进程地址空间的一个复制。

    返回值:成功时,该函数被调用一次,但返回两次,fork()对子进程返回0,对父进程返回子进程标识符(非0值)。不成功时对父进程返回-1,没有子进程。

    img

    四、实验内容

    1.关键代码分析
    void main(void)
        {
            int x = 5;
            if (fork())
            {
                x += 30;
                printf("% d\n", x);
            }
            else
                printf("% d\n", x);
            printf(("%d\n",x);
        }
    

    fork():父进程返回子进程的 pid,子进程返回 0,出错返回 -1。

    第一次执行 if(fork()) 语句时,没有子进程,此时父进程会创建一个子进程。if 语句成立,输出 35,35。子进程复制父进程的所有代码。子进程的 fork 语句返回 0,进入 else 分支,输出 5,5。

    所以预计程序输出的结果:35\n35\n5\n5\n

    2. 实验实际结果

    img

    通过在 Linux 中运行程序输出的结果可以发现,实际结果是与预计结果一致的。

    尝试运行多次,发现每次的结果都是相同的

    img

    3.实验结果分析

    1.不会交替输出 35 和 5

    因为在并发执行每个进程前会为当前进程分配时间片,当轮到改时间片时进程才会执行。当时间片用完或者在一个时间片内该进程执行完,就会轮到下一个时间片的进程执行。而本次实验的代码非常短,运行速度非常快,在一个时间片内肯定能运行完成。所以都是父进程运行完再到子进程运行。

    2.不会创建子进程失败

    因为本次实验代码非常简单,不可能出现资源不够分配的情况,所以创建进程基本不会失败。

    五、执行程序源代码

    #include <sys/types.h>
    
    #include <unistd.h>
    
    #include <stdio.h>
    
    void main(void)
    
    {
        int x = 5;
        if (fork())
        {
            x += 30;printf("%d\n", x);
        }
    
        else
            printf("%d\n", x);
    
        printf("%d\n", x);
    }
    
    展开全文
  • 并发程序设计的概念 程序是实现算法的操作序列,每个程序在处理器上是严格有序的,称之为程序执行的内部顺序性。 进程的并发执行:多道程序设计让多个程序同时进入内存去竞争处理器,以获得运行机会。OS允许计算机...

    并发程序设计的概念

    程序是实现算法的操作序列,每个程序在处理器上是严格有序的,称之为程序执行的内部顺序性

    进程的并发执行:多道程序设计让多个程序同时进入内存去竞争处理器,以获得运行机会。OS允许计算机系统在一个时间段内存在多个正在运行的进程,即允许多个程序的并发执行。OS保证按照“顺序程序设计”方法编制的程序在并发执行时不受影响,如同独占计算机。这些按照顺序程序设计思想编制的进程在OS中并发执行属于无关的并发进程。
    在这里插入图片描述
    在这里插入图片描述
    并发程序设计:把一个具体问题求解设计成若干个可同时执行的程序模块。
    在这里插入图片描述

    并发进程的制约关系

    无关与交往的并发进程
    无关并发进程:一组并发进程分别在不同的变量集合上运行,一个进程的执行与其他并发进程的进展无关。
    交往并发进程:一组并发进程共享某些变量,一个进程的执行可能影响其他并发进程的结果
    与时间有关的错误
    对于一组交往的并发进程,执行的相对速度无法相互控制,如果程序设计不当,可能出现各种“与时间有关的”错误,即结果错误,或者永远等待

    在这里插入图片描述
    进程互斥与进程同步
    根据上面案例,交互的并发进程在执行时必须进程制约,才能保证得到合理的结果。
    进程互斥:并发进程之间因相互争夺独占性资源而产生的竞争制约关系。
    进程同步:并发进程之间,为了完成某个共同的任务,基于某个条件来协调执行先后关系而产生的协作制约关系,举例:必须完成输入进程后,才能执行计算进程。

    临界区

    互斥与临界区
    临界资源:互斥共享变量所代表的资源,即一次只能被一个进程使用的资源
    临界区:指并发进程中与互斥共享变量相关的程序段
    多个进程访问临界资源时,存在竞争制约关系,如果两个进程同时停留在相关的临界区内,就会出现与时间相关的错误
    在这里插入图片描述
    临界区管理的三个要求
    ①一次最多允许一个进程停留在相关的临界区内
    ②一个进程不能无限制地停留在临界区内
    ③一个进程不能无限制地等待进入临界区
    临界区的嵌套使用
    在这里插入图片描述
    注意进程排序级别:级别高的,不允许申请嵌套级别低的进程,防止死锁的现象出现

    临界区管理实现的尝试

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    临界区管理实现的硬件管理

    在这里插入图片描述
    在这里插入图片描述
    实现临界区管理的硬件设施
    以上两种方式(测试并建立指令,对换指令)均是忙式等待,效率低。
    简单的解决办法是在进入临界区时开关中断
    由于进程切换需要依赖中断来实现,如果屏蔽中断,则不会引起进程切换,因此为了实现对临界资源的互斥使用,可以在进程进入临界区之前关闭中断,等进程退出临界区以后在打开中断这样临界区就不会中断了,执行就有原子性
    关中断 -> 临界区 -> 开中断
    在这里插入图片描述
    关中断缺点:限制交叉执行程序的能力,关中断方法不适合多CPU系统,关中断权利赋予给用户十分危险

    展开全文
  • 操作系统实验一:并发程序设计 一、实验目的 (1)加深对进程并发执行的理解,认识多进程并发执行的实质。 (2)观察进程共享资源的现象,学习解决进程互斥和同步的方法。 二、实验要求: 本实验要求用高级...

    操作系统实验一:并发程序设计

    一、实验目的

    1)加深对进程并发执行的理解,认识多进程并发执行的实质。

    2)观察进程共享资源的现象,学习解决进程互斥和同步的方法。

    二、实验要求:

    本实验要求用高级语言,启动多进程并发运行,设计相应代码,显示进程无关并发、进程共享变量并发的运行结果。并完成实验报告。

    三、实验内容:

    分别实现以下四种情况的并发:

    1.并发的进程之间无关,显示进程名称,开始与结束时间。

    模拟多终端售票情况,并发的多个终端进程之间共享剩余票数这个共享变量。

    2.用全局变量实现。

    3.用进程间共享数据机制实现。

    4.用进程间共享数据机制和加锁机制实现。

     

    四、实验过程与结果

    1. 算法思想与设计
    2. 算法实现代码
    3. 运行结果

    转载于:https://www.cnblogs.com/lr-c/p/10863546.html

    展开全文
  • 多线程并发设计,了解如何创建线程,开启线程,等待线程,怎样在程序中启动一个可执行文件
  • 操作系统实验一:并发程序设计。包括: 1.使用fork()创建子进程 2.编写代码实现孤儿进程,编写代码创建僵尸进程 3.创建多个线程,在各个线程中打印出堆栈变量的地址, 4.创建相同数量的进程和线程比较进程控制块开销...

    前言

    第一个实验就把我干碎了!好大的压力呀。操作系统像一座大山,压在我的狗脑子上,一点气都喘不过来。

    在无数次的摆烂,抄代码,百度之后,我最终还是挺过来了,并且踉跄地把实验写完。我踩了无数个坑,分享一下我自己的实验思路。

    你需要一台 linux,并且安装了 ps,pstree 等命令,他们属于 psmisc 包。

    预备部分

    学习 top、ps、pstree 和 kill 等命令的使用。

    能通过 top 和 ps j 命令查看进程号、父进程号、可执行文件名(命令)、运行状态信息,能通过 pstree 查看系统进程树;

    能通过 kill 命令杀死制定 pid 的进程。

    学习 /proc/PID/maps 的输出信息。

    了解 /porc/PID/status 中各项内容的。


    这个直接一路打命令就完事了。。。直接贴过程:

    使用 top 命令查看各进程的系统资源占用情况:

    在这里插入图片描述

    再来使用 ps 命令,查看当前进行的进程信息快照:

    在这里插入图片描述

    我们编写一段简单的 c 程序。产生两个进程,并且通过 getchar() 进行阻塞。将该文件保存为 1.c,文件的内容如下:

    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
        pid_t pid = fork();
    
        if(pid < 0) printf("error!");   // error ckeck
        if(pid == 0) getchar();         // child progress
        if(pid > 0) getchar();          // parent progress
    
        return 0;
    }
    

    然后将程序上传到云服务器,这里通过 WinSCP 软件进行:

    在这里插入图片描述

    注:
    如果你是虚拟机,那直接传磁盘就好了
    这里我是租的云服务器

    然后使用命令 gcc 1.c -o hello 进行编译:
    在这里插入图片描述

    随后执行该文件,通过命令 ./hello & 执行,此处 & 表示在后台执行即可。这里比对前后进程信息,可以发现我们产生了两个进程:

    在这里插入图片描述

    然后使用 pstree 命令查看进程之间的关系,我们选择 7444 号进程。命令为:pstree -p 7444,可以看到结果如下:

    在这里插入图片描述

    7444 号进程产生了 7445 号子进程!也可以通过 pstree -p 来查看完整的系统进程树并且确定 hello 的位置:

    在这里插入图片描述
    最后我们 kill 掉父进程 7444,使用 kill -Kill 7444,结果如下:

    在这里插入图片描述
    将父进程 kill 掉,子进程也随着消失。再次启动 hello,此时 hello 换了个号 8935 了:

    在这里插入图片描述
    查看 8935 号线程的内存映射信息,通过命令 cat /proc/线程id/maps 即可:

    在这里插入图片描述
    比如 hello 的栈空间,虚拟变量,虚拟动态共享变量,虚拟系统调用的地址。再通过命令 cat /proc/线程id/status 进行进程状态的查看:

    在这里插入图片描述

    操作部分

    大的要来了。

    1.使用 fork 创建进程

    使用 fork() 创建子进程,形成以下父子关系:
    在这里插入图片描述
    要求:并通过 /proc 文件系统,检查进程的 pid 和 ppid 证明你成功创建相应的父子关系,并用 pstree 验证其关系。

    1A. 创建 10 个子进程。

    循环 10 次,每次都 fork 一下即可,下面是代码:

    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
        for(int i=0; i<10; i++)
        {
            pid_t pid = fork();
            if(pid < 0) printf("error!");   // error ckeck
            if(pid == 0) break;             // child progress
            if(pid > 0) continue;           // parent progress
        }
        getchar();  // prevent exit
    
        return 0;
    }
    

    将上述代码保存为 t1a.c,意为第一题的 a 部分。然后编译并且执行该代码,可以看到产生了 1+10=11 个进程:

    在这里插入图片描述
    然后依次查看进程 13819~13828 号的 status 信息,检查 PPid 字段以验证其父亲是哪位进程。通过命令:cat …/…/…/proc/138xx/status | head -n 10依次查看:
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    可以看到 10 个子进程的父进程都是 13818,也可以通过 pstree 来验证。执行命令:pstree -p 13818 可以发现 10 个子进程:

    在这里插入图片描述

    1B. 10 层子进程嵌套

    只要在 A 题的基础上交换一下父子进程两个 if 即可。如果是父进程我们直接 break,而子进程则继续 continue,继续产生子子进程。

    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
        for(int i=0; i<10; i++)
        {
            pid_t pid = fork();
            if(pid < 0) printf("error!");   // error ckeck
            if(pid == 0) continue;          // child progress
            if(pid > 0) break;              // parent progress
        }
        pause();  // prevent exit
    
        return 0;
    }
    

    再次运行代码,也是产生了 11 个进程:
    在这里插入图片描述
    通过 pstree 命令查看进程间的关系:
    在这里插入图片描述
    可以发现确实是嵌套创建了 10 个进程。

    注:
    在 1A 题中直接 kill 掉父进程即可杀完全部子进程,因为使用 getchar 作为阻塞,在父进程死后子进程被 init 收养时,因为没有可以输入的控制台,于是直接结束。而 1B 题我这里使用 getchar 并且在后台运行时,子进程总是莫名其妙被 kill 且 ps 只能显示两个进程,(因为使用的远程登陆只有一个控制台,如果用虚拟机的话开多一个控制台,然后前台运行即可避免子进程被奇怪地 kill 掉)但是用 pause 阻塞则不会出现异常,但代价是无法通过杀死父进程来杀死子进程

    于是使用命令:

    ps -efww|grep t1b |grep -v grep|cut -c 9-15|xargs kill -9
    

    杀除全部名为 t1b 的进程即可。

    1C. 树形创建

    树形创建也和上面差不多,一个父节点得一次创建 2 个子进程才能结束,然后剩下的交给子进程重复即可。代码如下:

    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
        for(int i=0; i<3; i++)
        {
            pid_t pid = fork();
            if(pid > 0)             // parent progress
            {
                pid = fork();
                if(pid > 0) break;  // generate twice
            }        
        }
        pause();  // prevent exit
    
        return 0;
    }
    

    运行代码,产生了 15 个进程,这和题目要求的树型(在数量上)吻合:

    在这里插入图片描述

    通过 pstree 命令也可以清晰地看到树形结构:

    在这里插入图片描述

    2.僵尸与孤儿进程

    编写代码实现孤儿进程,用 pstree 查看孤儿进程如何从原父进程位置转变为 init 进程所收养的过程;编写代码创建僵尸进程,用 ps j 查看其运行状态。

    要求:用 Linux 系统提供的信息,展示并记录上述进程状态及变化


    2A. 孤儿进程

    孤儿进程要求父进程结束而子进程未结束。于是编写代码,用 pause 阻塞子进程,而父进程则 sleep 几秒然后结束,这样能够创造一个孤儿进程:

    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
        pid_t pid = fork();
        if(pid == 0) while(1){};    // child progress       
        if(pid > 0) sleep(5);       // parent progress
    
        return 0;
    }
    

    可以看到父进程在结束之前一直接管子进程,而父进程结束之后,子进程则被 1 号进程收养,并且仍然存活:

    在这里插入图片描述

    再次启动程序,用查看 pstree 的信息也可以观察子进程被收养的过程。首先看到子进程(29266)原本依赖于父进程(29265),如图:

    在这里插入图片描述

    在父进程 sleep 结束之后,子进程被 init 进程接管:

    在这里插入图片描述

    2B. 僵尸进程

    父进程不使用 wait 等手段处理子进程,并且子进程先结束,那么子进程结束之后就会变成僵尸进程。代码很简单:

    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
        pid_t pid = fork();
        if(pid > 0) while(1){}; // parent progress       
        if(pid == 0) sleep(5);  // child progress
    
        return 0;
    }
    

    一开始是正常的。子进程(32132)依附于父进程(32313)

    在这里插入图片描述
    然后等到子进程结束,因为父进程没有做 wait 等处理,于是子进程变为僵尸,可以看到状态也随着变为 Z 表示 zombie:

    在这里插入图片描述
    而如果使用 wait 来处理子进程的结束,那么就不会出现僵尸:

    #include <stdio.h>
    #include <unistd.h>
    #include <wait.h>
    
    int main()
    {
        pid_t pid = fork();
        if(pid > 0)             // parent progress
        {
            wait(NULL);
            while(1){};  
        }     
        if(pid == 0) sleep(5);  // child progress
    
        return 0;
    }
    

    在这里插入图片描述

    3.线程与线程堆栈

    创建多个线程,在各个线程中打印出堆栈变量的地址。

    要求:比较各线程的 /proc/PID/maps 是否相同。检查主线程的堆栈和其他线程堆栈位置在 /proc/PID/maps 所对应的位置差异。


    主线程创建 3 个子线程并且阻塞,代码如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <malloc.h>
    
    #define __NR_gettid 186
    void speak(void* p)
    {
        pthread_attr_t attr;
        void* stack_addr;
        size_t stack_size;
        
        pthread_getattr_np(pthread_self(), &attr);
        pthread_attr_getstack(&attr, &stack_addr, &stack_size);
    
        pid_t pid = getpid();
        long int tid = (long int)syscall(__NR_gettid);
    
        printf("pid: %lu, tid: %lu, stack address: %p, stack size: %lu\n", pid, tid, stack_addr, stack_size);
    
        pause();
    }
    
    int main()
    {
        for(int i=0; i<3; i++)
        {
            pthread_t tid;
            pthread_create(&tid, NULL, (void*)&speak, NULL);
        }
        printf("\n");
        pause();
    
        return 0;
    }
    

    通过

    gcc ./t3.c -o t3 -l pthread 
    

    来链接到 pthread 并且编译代码,然后后台运行

    注:
    编译的时候带上 -l lpthread 选项
    打印地址的时候用 %p 不然地址不对

    通过 ps -mp PID -o THREAD,tid,time 查看该进程的所有线程信息,可以看到确实由一个主线程 + 三个子线程组成:

    在这里插入图片描述
    再观察子线程的输出:

    在这里插入图片描述
    堆栈大小则输出了 3 个相同的数字,是 8388608,也就是 8 x 1024 x 1024,即 8M 的大小,而堆栈地址则是每个子线程自己的堆栈地址。通过观察 /proc/PID/maps 中的数据也可以查看到,子线程的堆栈是在主线程堆区申请的“虚拟堆栈”:

    在这里插入图片描述
    其中 heap 区有三段大小相同,并且由 “只读+可读可写” 段组成的内存,是三个子线程的堆栈保护缓冲和堆栈空间,如下图:

    在这里插入图片描述
    其中堆栈保护缓冲固定为 4kb,位于上半部分。而堆栈的空间位于每个框框的下半部分的那一行。如果不进行配置,那么堆栈大小默认为 8M。从图中也可以看出堆栈的大小确实是 8M,比如线程 21448(对应上图黄色框框)堆栈大小这么计算:

    0x7f6240749000 - 0x7f623ff49000 = 0x800000 
                                    = 8388608 = 8 x 1024 x 1024 = 8M
    

    而主线程的栈空间则在 [stack] 段,大小为 0x7feca29bd000 – 0x7feca29bc000 = 4kb:

    在这里插入图片描述

    结论:三个子线程具有自己独立的栈空间内存,并且这些内存是由 pthread 库自动申请的所以位于 heap 区,而主线程的栈空间则不和他们一起存放。

    4.进程线程开销比较

    分别创建相同数量的进程和线程

    要求:比较进程控制块开销的差异、内存 vma 描述符开销的差异,并简要解释原因。


    通过百度查阅资料发现,Linux 的线程其实是由进程模拟的,每个线程拥有自己独立的 PCB,也就是 task_struct

    而子线程的内存描述符 mm_struct 和 vma 描述符 vm_area_struct 则是一致指向 main 进程的,也就是子线程共享一套地址空间。

    以 3 为例:

    创建 3 个进程,就需要消耗 3 个 task_struct,3n 个 vm_area_struct
    创建 3 个子线程,需要消耗 3 个 task_struct,1n 个 vm_area_struct

    (假设 vm_area 链表就有 n 个元素,其中 n 是虚拟内存块的数目)

    下面通过代码来证实。创建 3 个进程的代码和上文类似,我们叫做 t4_process.c,代码如下:

    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
        for(int i=0; i<2; i++)
        {
            pid_t pid = fork();
            if(pid < 0) printf("error!");   // error ckeck
            if(pid == 0) break;             // child progress
            if(pid > 0) continue;           // parent progress
        }
        pause();  // prevent exit
    
        return 0;
    }
    

    创建 3 个线程同理,叫做 t4_thread.c,代码如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <pthread.h>
    
    void speak(void* p)
    {
        pause();
    }
    
    int main()
    {
        for(int i=0; i<3; i++)
        {
            pthread_t tid;
            pthread_create(&tid, NULL, (void*)&speak, NULL);
        }
        pause();
    
        return 0;
    }
    

    因为查看 task_struct 和 vma 是 内核态程序 才能完成的事情,一般的用户程序很难实现,于是我编写一个 module 并且插入到 linux 内核中

    这个模组接收进程 id 并且打印对应进程的 task_struct 和 vma 的地址,模组的代码 t4.c 内容如下:

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/sched.h>
    #include <linux/sched/signal.h>
    #include <linux/moduleparam.h>
    
    int pid = 114514;
    module_param(pid, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    MODULE_PARM_DESC(pid, "An integer");
    
    static int hello_init(void)
    {
        struct task_struct* p;
        struct task_struct* t;
        printk(KERN_ALERT "start print proc %d 's info\n", pid);
        
        for_each_process(p)
        {
            if(pid==p->pid)
            {
                printk(KERN_ALERT "pid: %i, mm addr: %lx, vma addr %lx: \n", p->pid, p->mm, p->mm->mmap);
                t = p;
                while ((t=next_thread(t))!=p)
                    printk(KERN_ALERT "tid: %i, mm addr: %lx, vma addr %lx: \n", t->pid, t->mm, t->mm->mmap);
            }  
        }   
        
        return 0;
    }
    
    static void hello_exit(void)
    {
        
    }
    
    module_init(hello_init);
    module_exit(hello_exit);
    MODULE_LICENSE("Dual BSD/GPL");
    

    模组的 Makefile 内容如下:

    obj-m :=t4.o
    KERNELDIR :=/lib/modules/$(shell uname -r)/build
    PWD :=$(shell pwd)
    
    modules:
    	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    modules_install:
    	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
    
    clean:
    	rm -rf *.o *~ core .depend .*.cmd *.ko.* *.mod.c .tmp_versions
    

    执行 make 命令,生成 .ko 文件,再使用 insmod ./t4.ko pid=xxxx 将模组插入内核,最后通过 dmesg -c 命令查看 printk 输出。

    首先我们运行创建 3 个子线程的程序 t4_thread,然后使用内核模块查看其信息:

    在这里插入图片描述
    pid和tid是由 task_struct 获得的,tid 的值不同,说明子线程拥有不同的 PCB。

    而第一行是进程的 mm_struct 和 vm_area_struct 的地址,而后三行是三个子进程的,可以看到他们相等。说明子线程和主进程共享一套地址空间。


    再来看 3 个进程的情况:运行 t4_process 创建 3 个进程

    在这里插入图片描述
    然后反复插入模块,并且打印信息:

    在这里插入图片描述

    可以看到,三个进程的 mm_struct 和 vm_area_struct 地址均不同,说明进程之间的地址空间相互独立。

    总结:等数量的线程与进程,PCB 开销不变,进程消耗的 vma 描述符是线程的 3 倍(假设他们都申请同样大小的虚拟内存,即 vma 链表元素个数一致),因为进程的地址空间相互独立。

    5.自定义shell

    尝试自行设计一个C语言小程序,完成最基本的shell角色

    要求:给出命令行提示符、能够逐次接受命令;对于命令分成三种,内部命令(实现help命令给出用法、exit命令退出shell)、外部命令(即磁盘上的可执行文件)以及无效命令(不是上述两种命令)。


    思路:内部命令比较简单,直接执行即可。外部命令,通过 fork 创建新进程,如果是子进程就读取用户输入的外部命令,然后 execlp 执行外部命令,并且返回结果。

    内部命令一共三条,分别是 help 帮助,exit 退出,和 story 讲故事。而外部命令需要在前面添加 exec,比如 ls 命令,就需要 exec ls 才行。

    代码如下:

    #include <stdio.h>
    #include <unistd.h>
    #include <wait.h>
    
    int main()
    {
        printf("welcome to my shell!\n");
        char cmd[1024];
        while(1)
        {
            printf("myshell> ");
            scanf("%s", cmd);
            if(strcmp(cmd, "exit")==0) break;
            else if(strcmp(cmd, "help")==0)
            {
                printf("----------------\n内部命令:\n");
                printf("help  : 打印帮助信息\n");
                printf("story : 讲故事\n");
                printf("exit  : 退出\n----------------\n外部命令:\n");
                printf("exec + 命令\n----------------\n");
            }
            else if(strcmp(cmd, "story")==0)
            {
                printf("闹的挺大,我们县的高中都传疯了。先说一下,我高二,以前是个二刺猿,但现在很少看动漫,最多也就玩个二次元游戏。我有个初中同学,我俩一起入宅的。现在就在我隔壁班。我俩也经常一起吃饭出去玩。而他班上有个傻币二次猿,上课天天看动漫玩游戏买手办,成绩一直是班上倒数第一,还特别喜欢充钱。听我朋友说光明日方舟他就充了几万。而他班上入宅的人很少,他就黏住我朋友,经常在我俩一起起饭的时候凑进来。但我和我朋友一直都挺不喜欢他的,他经常在我们面前吹嘘他又氪金抽到了什么什么,我俩没有之类的话,还喜欢贬低别的动漫,经常不在意我俩的脸色就在贬低我俩喜欢的角色,他这人人品还有问题。他在他自己班上口碑也很差,几乎没什么朋友。而在上个月初,国庆假时,他玩原神充了三万rmb,他还不满足,还有角色没满命,他就去偷钱,被他父亲抓了个现行,然后他俩居然打起来了,最后他拿走五万便回学校去了。说一下,我学校是全日制寄宿式学校,每个星期六放半天假,每个月月底放2-3天假。然后事情来了,他十月月底回家时,发现他父母都不在家,只有他妹在家,他就把他妹强奸了,还威胁他妹不准告诉父母。然后这个月月初回校中午吃饭时他就和我们说他妹肯定是兄控,肯定喜欢他,于是他就和他妹做了,他还讲了细节,当时我和我朋友没相信。对了,他妹刚12。然后前天10点左右的样子,他父母可能知道他强奸他妹了,就跑来学校,当场就打断他一只腿。他却还说着什么要和他父亲拼命。当时很多人录下来了,发到各高中学校群,我们县所有高中都知道了这件事,虽然说现在学校把消息压下来了,我们学校还专门用半天时间告诉我们这件事不要乱传之类的。反正当时闹公安去了,后面发生了什么我也不知道。我现在只想劝你们早日放弃二刺猿,不要变成像他那样的人。\n");
            }
            else if(strcmp(cmd, "exec")==0)
            {
                char ecmd[1024];
                scanf("%s", ecmd);
                pid_t pid = fork();
                if(pid>0) // parent
                {
                    wait(NULL);
                    continue;
                }
                if(pid==0)  // child
                {
                    int s = execlp(ecmd, ecmd, NULL);
                    if(s==-1) printf("fail to exec %s\n", ecmd);
                    return 0;
                }
            }
            else
            {
                printf("无效命令\n");
            }
        }
        printf("bye\n");
    
        return 0;
    }
    

    演示:
    首先是 help 和 story 两个内部命令:

    在这里插入图片描述
    然后执行外部命令:分别执行 ls, ps, pwd 命令,以及磁盘上面的 hello 可执行文件

    在这里插入图片描述
    最后是无效命令和退出:

    在这里插入图片描述
    退出之后回到原本的 linux 控制台,程序结束。

    总结

    1. 使用一系列的 Linux 命令操控进程,比如 ps,kill 等命令
    2. 在 Linux 下进程通过 fork 创建分支,通过返回值判断是否为子进程
    3. 在 Linux 下通过 pthread 库创建线程
    4. 线程其实是通过进程模拟的,每个线程都有自己的 PCB,只是线程之间共享一套内存
    5. 线程栈其实在主进程的堆区被申请
    6. 创建线程的开销通常小于创建等数量的进程
    7. 通过 proc/pid/xxx 查看 PCB 信息,也可以通过内核态的程序查看task_struct
    展开全文
  • 操作系统课的实验报告,为后来者省心,特此共享,雁过留名,谢谢!
  • 并发程序设计 顺序程序设计 进程的并发执行 处理器利用率计算 并发程序设计 把一个具体问题求解设计成若干个可同时执行的程序模块的方法 特性: 无关与交往的并发进程 与时间有关的错误 进程互斥与进程同步 互斥与...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,527
精华内容 1,010
热门标签
关键字:

操作系统并发程序设计