《嵌入式系统开发实践》实验二 进程与线程

news2024/9/29 21:34:03

一、 实验目的

了解Linux中进程和线程的概念;
了解多线程程序的基本原理;
了解pthread库;
掌握用system、exec函数族、fork函数创建进程;
掌握使用pthread库中的函数编写多线程程序。

二、 实验任务与要求

应用fork函数创建子进程;
应用fork函数创建子进程,在父子进程中执行不同的任务;
新线程的创建,以及和主线程之间的关系。
三、 实验工具和环境

PC机、Linux Ubuntu操作系统。

四、 实验内容与结果

程序设计。
(1) 在父子进程中分别编写循环显示数字1-10和显示数字101-110的程序;

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

int main()
{
    pid_t pid = fork();
    if (pid == 0)
    {
        for (int i = 0; i <= 10; i++)
        {
            printf("subprocess: %d\n", i);
        }
    }
    else
    {
        for (int i = 101; i <= 110; i++)
        {
            printf("process: %d\n", i);
        }
    }
}

在这里插入图片描述

使用了fork()函数来创建一个新的进程。fork()函数是Unix-like操作系统中创建进程的主要方法,它会复制当前调用它的进程,并返回一个进程ID。复制出来的进程称为子进程,原来的进程称为父进程。

这段代码中,首先调用了fork()函数,然后根据返回值判断是父进程还是子进程。如果返回值是0,说明是子进程,它会打印出0到10的数字;如果返回值不是0,说明是父进程,它会打印出101到110的数字。由于父进程和子进程是并发执行的,所以打印的顺序可能不一定固定。

(2) 应用函数sleep的不同参数等,体现程序中父子进程的并发运行。

在父子进程中分别执行不同的任务,即在子进程中编写程序1+2+…+10的程序,将该程序写入到sum.c中,并编译并运行该程序;在父进程中执行网络连通情况的测试任务,要求子进程退出后父进程才退出。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
    pid_t pid;
    pid = fork();
    if (pid == 0)
    {
        int fd;
        int len, s_r, s_w;
        char *buf = "#include<stdio.h>\
        \nint main(){\
        \n  int i,s=0;\
        \n  for (i=1;i<=10;i++)\
        \n     s = s + i;\
        \n     printf(\"s=%d\\n\", s);\
        \n}";

        fd = open("sum.c", O_CREAT | O_RDWR, 0666);
        len = strlen(buf);
        s_w = write(fd, buf, len);
        char buf_r[500];
        lseek(fd, 0, SEEK_SET);
        s_r = read(fd, buf_r, s_w);
        buf_r[s_r] = "\0";
        printf("Read:\n %s \n", buf_r);
        system("gcc sum.c -o sum");
        close(fd);
        execlp("./sum", "sum", NULL);
    }
    else if (pid > 0)
    {
        system("ping 127.0.0.1 -c 2");
        sleep(5);
    }
    else // fork失败
    {
        perror("fork error");
        exit(0);
    }
}

在这里插入图片描述

使用了fork()函数来创建一个子进程。子进程中,首先用open()函数打开一个名为sum.c的文件,如果文件不存在,就创建一个。然后用write()函数向文件中写入一段C语言代码,这段代码的功能是计算1到10的和并打印出来。接着用read()函数读取文件中的内容,并打印出来。然后用system()函数调用gcc命令来编译sum.c文件,生成一个名为sum的可执行文件。最后用execlp()函数执行sum文件,并传入一个参数"sum"。父进程中,用system()函数调用ping命令来测试本地网络连接,然后等待5秒。

调试下列程序。程序中使用pthread线程库创建一个新线程,在父进程(也称为主线程)和新线程中分别显示进程id和线程id,并观察线程id数值。(代码test3.c)问题如下:

// test3.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
pthread_t ntid;
void printids(const char *s){ /*各线程共享的函数*/
    pid_t pid;
    pthread_t tid;
    pid = getpid();
    tid = pthread_self();
    printf("%s pid = %u tid = %u (0x%x) \n",s,(unsigned int)pid,(unsigned int)tid,(unsigned int)tid);
}

void *thread_fun(void *arg){ /*新线程执行代码*/
    printids(arg);
    return NULL;
}

int main(void){
    int err;
    /*下列函数创建线程*/
    err = pthread_create(&ntid,NULL,thread_fun,"我是新线程:");
    if(err != 0){
        fprintf(stderr,"创建线程失败:%s\n",strerror(err));
        exit(1);
    }
    printids("我是父进程");
    sleep(2);  /*等待新线程运行结束*/
    return 0;
}

在这里插入图片描述

(1) 进程在一个全局变量ntid中保存了新创建的线程的id,如果新创建的线程不调用pthread_self而是直接打印这个ntid,能不能达到同样的效果?为什么?

不一定能达到同样的效果,因为ntid是一个全局变量,可能被其他线程修改或覆盖。而pthread_self返回的是当前线程的id,不会受到其他线程的影响。所以,为了安全和准确地获取线程的id,最好使用pthread_self函数。

(2) 在本题中,如果没有sleep(2)函数,会出现什么结果?为什么?

如果没有sleep(2)函数,可能会出现只打印出父进程的id,而没有打印出新线程的id的情况。这是因为父进程在创建新线程后,没有等待新线程运行结束,就直接退出了。而当父进程退出时,它的所有子线程也会被终止。所以,为了让新线程有机会执行,需要在父进程中调用sleep函数,让父进程暂停一段时间。

调试下列程序。程序中只给出了线程产生、退出和等待的程序,(代码 test4.c)根据该程序完成提出的问题:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
void *thread_fun1(void *arg){
    printf("thread 1 returning\n");
    return ((void *)1);
}

void *thread_fun2(void *arg){
    printf("thread 2 exiting\n");
    pthread_exit((void *)2);
}

int main(void){
    int err;
    pthread_t tid1,tid2;
    void *tret;
    err = pthread_create(&tid1,NULL,thread_fun1,NULL);
    if(err!=0)
        fprintf(stderr,"can't create thread 1: %s\n",strerror(err));
    err = pthread_create(&tid2,NULL,thread_fun2,NULL);
    if(err!=0)
        fprintf(stderr,"can't create thread 2: %s\n",strerror(err));
    err = pthread_join(tid1,&tret);
    if(err!=0)
        fprintf(stderr,"can't join with thread 1: %s\n",strerror(err));
    printf("thread 1 exit code %d\n",(int)tret);
    err = pthread_join(tid2,&tret);
    if(err!=0)
        fprintf(stderr,"can't join with thread 2: %s\n",strerror(err));
    printf("thread 2 exit code %d\n",(int)tret);
    exit(0);
}

(1) 修改程序,在线程1、2中分别完成1到1000的加法,观察线程的执行情况与线程结束时的状态,并给出执行结果的解释;

void *thread_fun1(void *arg){
    int sum = 0;
    for(int i = 1; i <= 1000; i++){
        sum += i;
    }
    printf("thread 1 returning\n");
    return ((void *)&sum);
}

void *thread_fun2(void *arg){
    int sum = 0;
    for(int i = 1; i <= 1000; i++){
        sum += i;
    }
    printf("thread 2 exiting\n");
    pthread_exit((void *)&sum);
}

定义了两个线程函数thread_fun1thread_fun2,它们的功能都是计算1到1000的和,并把结果存储在一个int类型的变量sum中。不同的是,thread_fun1用return语句返回sum的地址,而thread_fun2用pthread_exit()函数退出线程并传递sum的地址。为了运行这两个线程函数,需要用pthread_create()函数创建两个线程,并把它们的线程ID存储在一个pthread_t类型的变量中。然后,需要用pthread_join()函数等待这两个线程结束,并获取它们的返回值。最后,需要打印出这两个线程的返回值,它们都是500500。

(2)改变线程2的程序为计算1-100的阶乘,观察线程1和线程2执行任务的时间,观察结果。

void *thread_fun2(void *arg)
{
    int i, j, num;
    unsigned long long fact;

    printf("计算1到100的阶乘\n");

    for (i = 1; i <= 100; i++)
    {
        num = i;
        fact = 1;

        for (j = 1; j <= num; j++)
        {
            fact *= j;
        }

        printf("%d! = %llu\n", num, fact);
    }
    printf("thread 2 exiting\n");
    pthread_exit((void *)1);
}

在这里插入图片描述

定义了一个线程函数thread_fun2,它的功能是计算1到100的阶乘,并把结果打印出来。阶乘是一个数的所有正整数因数的乘积,例如5! = 5 x 4 x 3 x 2 x 1 = 120。这段代码用一个for循环遍历1到100的每个数,然后用另一个for循环计算它的阶乘,并把结果存储在一个unsigned long long 类型的变量fact中。这个类型可以表示最大为18446744073709551615的无符号整数。然后,这段代码用printf()函数输出每个数和它的阶乘。最后,这段代码用pthread_exit()函数退出线程,并传递一个值为1的void*类型的指针。

编写程序,利用多线程实现生产者-消费者问题,生产者和消费者分别在2个线程中,生产者生产的产品放在链表的表头上,消费者从表头取走产品。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#define MAX 12
sem_t empty;
sem_t full;
pthread_mutex_t mutex;
int buffer[MAX];
int count = 0;
void *producer(void *arg)
{
    int i;
    for (i = 0; i < MAX; i++)
    {
        sem_wait(&empty);
        pthread_mutex_lock(&mutex);
        buffer[count++] = i;
        printf("producer: %d\n", i);
        pthread_mutex_unlock(&mutex);
        sem_post(&full);
    }
}

void *consumer(void *arg)
{
    int i;
    for (i = 0; i < MAX; i++)
    {
        sem_wait(&full);
        pthread_mutex_lock(&mutex);
        int item = buffer[--count];
        printf("consumer: %d\n", item);
        pthread_mutex_unlock(&mutex);
        sem_post(&empty);
    }
}

int main()
{
    pthread_t tid1, tid2;
    sem_init(&empty, 0, MAX);
    sem_init(&full, 0, 0);
    pthread_mutex_init(&mutex, NULL);
    pthread_create(&tid1, NULL, producer, NULL);
    pthread_create(&tid2, NULL, consumer, NULL);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    sem_destroy(&empty);
    sem_destroy(&full);
    pthread_mutex_destroy(&mutex);
    return 0;
}

在这里插入图片描述

生产者和消费者,它们共享一个固定大小的缓冲区作为队列。生产者生成数据并放入缓冲区,而消费者从缓冲区取出数据并消费它。这段代码使用了信号量和互斥锁来协调对缓冲区的访问,避免数据不一致或溢出。

五、实验总结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/545575.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

行云流水| CI 3.0 云原生构建全新上线

研发过程中&#xff0c;如何直观且准确地获悉代码提交后的质量状态&#xff1f; 引入持续集成&#xff0c;可以自动化的对代码进行代码检查、单元测试、编译构建、甚至部署与发布&#xff0c;大幅提升开发人员的效率。 腾讯云 CODING 推出 CI 3.0 ——云原生构建&#xff0c;是…

LabVIEW:强大的图形化编程工具

LabVIEW&#xff08;Laboratory Virtual Instrument Engineering Workbench&#xff09;是由美国国家仪器公司&#xff08;National Instruments&#xff09;开发的一种直观而强大的工程软件&#xff0c;被广泛应用于各个领域的工程师和科学家之中。 与传统的编程语言相比&…

打包后定义配置文件针对.vue和.js文件不同配置方法

条件&#xff1a;需要打包后形成config文件&#xff0c;在打包后改变此配置文件即可改变配置&#xff0c;如api地址&#xff0c;vue中方法参数和条件。 &#xff08;1&#xff09;首先config文件要在public文件中建立&#xff0c;webpack打包后config文件才会出现在打包的dist文…

SpringMVC第四阶段:Controller中如何接收请求参数

Controller中如何接收请求参数 1、原生API参数类型 1.1、HttpServletRequest类 只需要在Controller的目标方法中, 直接写上HttpServletRequest对象即可获取 原生API的 request对象实例。 RequestMapping(value "/p1") public String param1(HttpServletRequest …

( 动态规划) 1035. 不相交的线 ——【Leetcode每日一题】

❓1035. 不相交的线 难度&#xff1a;中等 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在&#xff0c;可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线&#xff0c;这些直线需要同时满足满足&#xff1a; nums1[i] nums2[j]且绘制的直线不…

Postman如何做接口测试?你居然还不知道

目录 Postman如何做接口测试1&#xff1a;如何导入 swagger 接口文档 Postman如何做接口测试2&#xff1a;如何切换测试环境 Postman如何做接口测试3&#xff1a;什么&#xff1f;postman 还可以做压力测试&#xff1f; Postman如何做接口测试4&#xff1a;如何自动添加请求…

辅助驾驶功能开发-功能规范篇(16)-2-领航辅助系统NAP-自动变道-2

书接上回 2.3.4.3 系统主动变道 (1)变道需求输入 在NOA功能功能激活状态下,系统接收驾驶员请求或根据导航引导信息及道路环境信息获取变换车道的需求,包含导航引导路线的变道需求和智能避让变道需求。 导航引导变道需求 导航引导模式下的主动变道包括上高速由匝道并入主路、…

【前端知识】浅谈XSS和CSRF网络攻击

【前端知识】浅谈XSS和CSRF网络攻击 1. 常见的浏览器攻击2. XSS攻击2.1 定义2.2 类型2.2.1 Reflected XSS【反射型 - 非持久型 XSS】2.2.2 Stored XSS【存储型 - 持久型 XSS】2.2.3 DOM-based or local XSS【基于DOM或本地的XSS &#xff0c;非持久性】2.2.4 其他类型XSS攻击 2…

Python入门(十)用户输入

用户输入 1.概述2.函数input()的工作原理2.1 编写清晰的程序 3.使用int()来获取数值输入4.求模运算符 作者&#xff1a;xiou 1.概述 大多数程序旨在解决最终用户的问题&#xff0c;为此通常需要从用户那里获取一些信息。例如&#xff0c;假设有人要判断自己是否到了投票年龄。…

从C语言到C++_11(string类的常用函数)力扣58和415

目录 1. 学习string的铺垫 1.1 什么是string类 1.2 basic_string 模板类 1.3 编码表的由来 1.4 其它字符编码的string 2. string类对象的常见构造 3. sting类对象的容量操作 4. string类对象的访问及遍历操作 5. string类对象的修改操作 6. string类非成员函数 7. …

Unity之新版输入系统InputSystem如何自定义InputActions

一.前言 上一篇文章,我们介绍了如何使用新版本的InputSystem,我们知道了InputActionsAsset给我们提供了更多的灵活性,扩展性和复用性。那么这篇文章我们就来介绍一下如何创建自定义InputActionAsset 二.创建ActionAssets Input Action Asset 包含输入 Actions及其关联的B…

活动回顾|Kyligence x 亚马逊云科技,携手加速零售电商数智化转型

5月19日&#xff0c;Kyligence 与亚马逊云科技联合主办的「指标驱动&#xff0c;加速零售电商行业数智化转型」主题沙龙在上海成功举办。来自乐高、Kyligence、亚马逊云科技的专家分享了如何以数据和指标驱动&#xff0c;加速零售行业的数智化转型&#xff0c;并与现场观众进行…

【92】实测:访问不存在的function导致UR

前言 协议规定访问不存在的function会导致UR&#xff0c;今天我们就来实测一下。 这篇文章主要设计下面几个方面&#xff1a; 1、X86上的memory config 2、config方式访问不存在的设备导致UR 3、UR和advisory non fatal转换 4、header log解析 一、协议规定 协议规定如果…

SPI总线通讯协议学习

目录 什么是SPI 信号线 理解通讯原理 采样 SPI的推广 什么是SPI SPI是芯片与芯片之间的通讯,准确得说是串行同步通讯。既然都说了同步&#xff0c;那发送数据当然要和时钟线SCK配合才能发数据. 采用一主多从的模式&#xff0c;主机只有一个,而从机可以有若干个。 信号线 …

Java面试知识点(全)-Java并发-多线程JUC三- JUC集合/线程池

Java面试知识点(全) 导航&#xff1a; https://nanxiang.blog.csdn.net/article/details/130640392 注&#xff1a;随时更新 JUC集合类 为什么HashTable慢? 它的并发度是什么? 那么ConcurrentHashMap并发度是什么? Hashtable之所以效率低下主要是因为其实现使用了synchro…

学习开源项目消息推送平台需要什么基础?

有很多人问过我&#xff0c;学习开源项目消息推送平台austin需要有什么基础&#xff0c;我往往会回答&#xff1a;有SpringBoot基础就够了。 我在几年前总结过从零学习Java的路线&#xff0c;现在看来也没有很过时&#xff1a; Java基础&#xff1a;流程控制–>面向对象(包…

文件上传,解析漏洞编译器安全(23)

apache低版本解析漏洞 这个网站目录里有两个文件&#xff0c;一个是正常的php文件&#xff0c;另一个xx.php.xxx&#xff0c;源码是php源码&#xff0c;命名的文件&#xff0c;而访问中xxx的文件依旧可以执行出php代码的结果&#xff0c;而xxx就能当php文件解析&#xff0c;这…

【Linux】shell脚本编程

C/C与shell的区别 C/C是编译型 编译链接xx.c->xx 二进制机器指令 shell编程解释型 xx.sh 需要解释器&#xff08;如&#xff1a;bash&#xff09; Java 解释器编译 xx.java->xx.class 配置环境 输出一个hello my.sh #!/usr/bin/bash/echo"hello"ex…

php中Ajax的简单使用,登录表单调用Ajax判断是否正确登录利用layer.msg进行提示

php中Ajax的简单使用 jQuery中如何使用Ajax&#xff1f; jQuery 中封装了两个方法 get() 和 post() 方法用于通过 HTTP GET 或 POST 请求从服务器请求数据。 两种在客户端和服务器端进行请求-响应的常用方法是&#xff1a;GET 和 POST。 GET - 从指定的资源请求数据POST - …

Postman集合/文件夹/请求中脚本的执行顺序

Postman的Collection(集合)/Folder(集合的子文件夹)/Request(请求)中都有Pre-request Script(请求前脚本)和Tests(请求后脚本) 这个功能类似于不同范围的Test Fixture功能, 我们来探索3个问题: 脚本的执行顺序?保存在集合/子文件夹中的请求单独发送时是否会执行 集合以及子文…