OS实验1的代码与readme

目录

进程调度实验readme

sys/types.h和unistd.h都是什么库?

linux怎么运行C语言代码

linux安装gcc

程序无法识别wait函数

exec和system族函数有什么区别

缓冲区与换行符

双进程实验为什么不用wait函数的话子进程的输出总是在父进程之后

第一部分实验现象

如何实现双线程

使用Thread实现多线程,为什么没有出现多线程并发执行的场景?

两个子线程并发运行,并发访问全局变量导致出错

如何加入互斥锁?

第二部分实验现象


代码一:

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
    pid_t pid, pid1; //用于存储进程号
    pid = fork();    //创建新进程,返回值为创建进程号
    if (pid < 0)     //创建失败
    {
        fprintf(stderr, "Fork Failed");
        return 1;
    }
    else if (pid == 0) //子进程代码
    {
        pid1 = getpid();
        printf("child:pid=%d\n", pid);   //输出fork返回值
        printf("child:pid1=%d\n", pid1); //输出本进程的进程号
    }
    else //父进程代码
    {
        pid1 = getpid();
        printf("parent:pid=%d\n", pid);   //输出fork返回值
        printf("parent:pid1=%d\n", pid1); //输出本进程的进程号
        //wait(NULL);
    }
    return 0;
}

代码二:

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int test[5] = {0, 0, 0, 0, 0}; //全局变量数组
int flag = 0;                  //全局变量下标
int main()
{
    pid_t pid, pid1;
    pid = fork();
    if (pid < 0)
    {
        fprintf(stderr, "Fork Failed");
        return 1;
    }
    else if (pid == 0) //子进程
    {
        pid1 = getpid();
        //对全局变量执行操作1
        test[flag] = 1;
        flag++;
        printf("child:test number=");
        //输出全局变量内容
        for (int i = 0; i < 5; i++)
        {
            printf("%d", test[i]);
            if (i < 4)
                printf(",");
            else
                printf("\n");
        }
        printf("child:pid=%d\n", pid);
        printf("child:pid1=%d\n", pid1);
    }
    else //父进程
    {
        pid1 = getpid();
        //对全局变量执行操作2
        test[flag] = 2;
        flag++;
        printf("parent:test number=");
        //输出全局变量内容
        for (int i = 0; i < 5; i++)
        {
            printf("%d", test[i]);
            if (i < 4)
                printf(",");
            else
                printf("\n");
        }
        printf("parent:pid=%d\n", pid);
        printf("parent:pid1=%d\n", pid1);
        wait(NULL);
    }
    //返回函数值之前对全局变量执行操作3
    test[flag] = 3;
    flag++;
    printf("before return :test number=");
    //输出全局变量内容
    for (int i = 0; i < 5; i++)
    {
        printf("%d", test[i]);
        if (i < 4)
            printf(",");
        else
            printf("\n");
    }
    return 0;
}

代码三:

main.cpp

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
    pid_t pid, pid1; //用于存储进程号
    pid = fork();    //创建新进程,返回值为创建进程号
    if (pid < 0)     //创建失败
    {
        fprintf(stderr, "Fork Failed");
        return 1;
    }
    else if (pid == 0) //子进程
    {
        pid1 = getpid();
        printf("\nchild:pid=%d\n", pid);
        printf("child:pid1=%d\n", pid1);
        //选用exec族函数或是system族函数,运行代码testcode.cpp
        //execlp("/home/dzx/code/OS1/testcode","testcode",NULL);
        //system("/home/dzx/code/OS1/testcode");
    }
    else //父进程
    {
        pid1 = getpid();
        printf("\nparent:pid=%d\n", pid);
        printf("parent:pid1=%d\n", pid1);
        wait(NULL);
    }
    return 0;
}

testcode.cpp

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

int main()
{
    pid_t pid;
    printf("Welcome to the new process!\n");
    pid = getpid(); //获取本进程的进程号
    //输出本进程的进程号
    printf("the pid of this process is %d \n", pid);
    printf("Leave the new process\n");
}

代码4:

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <malloc.h>
#include <pthread.h>
int public_static = 0;    //全局变量
int *public_stack = NULL; //全局指针,指向一个局部变量
void *test1(void *ptr);   //线程一运行的函数
void *test2(void *ptr);   //线程二运行的函数
int main()
{
    int stack = 0;         //局部变量,存储在堆中
    public_stack = &stack; //将全局指针指向这个地址
    pthread_t pId1;        //创建新线程使用的句柄
    pthread_t pId2;
    int ret1, ret2;
    ret1 = pthread_create(&pId1, NULL, test1, NULL); //将线程与函数相关联
    ret2 = pthread_create(&pId2, NULL, test2, NULL);
    //pthread_join(pId1,NULL);
    //pthread_join(pId2,NULL);
    sleep(5); //等待线程结束
    return 0;
}

void *test1(void *ptr)
{

    pid_t pid = getpid();
    for (int i = 0; i < 100; i++)
    {
        public_static++;   //全局变量+1
        (*public_stack)++; //全局指针指向的变量+1
        //输出线程中的数据信息
        printf("-----------------------the pthread 1 running, the static number is %d, the stack number is %d -----------------------\n", public_static, *public_stack);
        //sleep(1);
    }
}

void *test2(void *ptr)
{
    pid_t pid = getpid();
    for (int i = 0; i < 100; i++)
    {
        public_static++;   //全局变量+1
        (*public_stack)++; //全局指针指向的变量+1
        //输出线程中的数据信息
        printf("<<<<<<<<<<<<<<the pthread 1 running, the static number is %d, the stack number is %d <<<<<<<<<<<<<<\n", public_static, *public_stack);
        //sleep(1);
    }
}

README:

进程调度实验readme

sys/types.h和unistd.h都是什么库?

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

基本系统数据类型

是Unix/Linux系统的基本系统数据类型的头文件,含有size_t,time_t,pid_t等类型。

#include <sys/types.h>的作用_大雄不爱吃肉-CSDN博客_#include <sys/types.h>

unistd.h是unix std的意思,是POSIX标准定义的unix类系统定义符号常量的头文件,

包含了许多UNIX系统服务的函数原型,例如read函数、write函数和getpid函数。

在本次实验中用到的有fork() sleep() exit()

#include <unistd.h> 的作用_u012764241的专栏-CSDN博客_#include <unistd.h>


linux怎么运行C语言代码

Linux下使用最广泛的C/C++编译器是GCC,大多数的Linux发行版本都默认安装,不管是开发人员还是初学者,一般都将GCC作为Linux下首选的编译工具。本教程毫不犹豫地使用GCC来编译C程序。

保存文件后退出,打开终端并 cd 到当前目录,输入下面的命令:

 gcc test.c -o test

可以直接将C代码编译链接为可执行文件。

在本次实验中,使用到了pthread库的程序文件在编译时需要加入额外参数

gcc test.c -lpthread -o test

在Linux下运行C语言程序_C语言中文网


linux安装gcc

 yum -y install gcc 
​
 yum -y install gcc-c++

程序无法识别wait函数

加入头文件

#include <sys/wait.h>

exec和system族函数有什么区别

exec族函数调用说明

当我们用fork创建子进程后,经常调用exec族函数去执行新的可执行文件。

exec函数族可以实现在一个进程中启动另一个程序的功能。系统内核会使用新的程序替换原有进程中使用的程序。开始新程序的执行。新进程和原有的进程号相同。调用exec函数后,代码段中的内容被新进程的代码替换,接着更新堆栈段和数据段。

注:调用exec族函数并不创建新进程所以进程ID不变。

为什么一直说exec函数族呢?因为,在Linux中,并不存在exec这个函数,而是一组以exec开头的函数。

exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe

int execl(const char *path, const char *arg, ...);
​
int execlp(const char *file, const char *arg, ...);
​
int execle(const char *path, const char *arg, const char *envp[]);
​
int execv(const char *path, const char *argv[]);
​
int execve(const char *path, const char *argv[], const char *envp[];
​
int execvp(const char *file, const char *argv[]);

参数说明:

1)查找方式不同

path:除了execlp和execvp之外的4个函数都要求第一个参数path必须是一个完整的要执行文件的路径。如:"/bin/ls"

file:execlp和execvp的第一个参数可以是一个要执行的文件。如:"ls",系统会到默认的环境变量 "$PATH"所指 定的路径中进行查找。

参数必须以NULL表示结束。

2)参数传递方式不同

这个参数就是使用可执行文件时所需的全部命令选项字符串,包括可执行命令本身。

execl,execle,execlp中的参数传递是逐个列举的方式。

execv,execve,execvp中的参数传递是指针数组方式

3)环境变量

使用execle,execve可以指定环境变量。

环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数,比如临时文件夹位置和系统文件夹位置等。

env:显示系统当前所有环境变量。

exec函数族和fork的区别:exec执行之后,进程会使用新的程序替换旧的程序并继续执行,也可以认为原有进程变成了一个新的进程执行。使用fork后,创建一个子进程,原进程继续进行,执行的程序保持不变。

system函数调用说明

system(执行shell 命令)system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令,此命令执行完后随即返回原调用的进程,这个是不同于exec函数的地方。

包含在头文件 “stdlib.h” 中

int system(const char * command)

函数功能 执行 dos(windows系统) 或 shell(Linux/Unix系统) 命令,参数字符串command为命令名。另,在windows系统下参数字符串不区分大小写。

说明:在windows系统中,system函数直接在控制台调用一个command命令。 在Linux/Unix系统中,system函数会调用fork函数产生子进程,由子进程来执行command命令,命令执行完后随即返回原调用的进程。

函数返回值 命令执行成功返回0,执行失败返回-1。

Linux 软件系列之九——exec函数族和system函数调用_laoniu_c的专栏-CSDN博客

C语言system函数使用_ywl470812087的博客-CSDN博客_c语言system函数改变颜色


缓冲区与换行符

printf函数是一个行缓冲函数,先将内容写到缓冲区,满足一定条件后,才会将内容写入对应的文件或流中。

基本条件如下:

1.缓冲区填满

2.写入的字符中有‘\n’ '\r'

3.调用fflush或stdout手动刷新缓冲区

4.调用scanf等要从缓冲区中读取数据时,也会将缓冲区内的数据刷新

5.程序结束时

所以在有进程调度或者线程调度的程序里,如果输出中含有换行符可能会导致输出结果不准确。


双进程实验为什么不用wait函数的话子进程的输出总是在父进程之后

在第一个进程加入sleep函数之后发现父进程在子进程之后运行。

可见运行顺序并不一定,这里可能是因为父进程的代码较少,运行时间更短所致。

不加入sleep,多次运行代码偶尔也能见到父进程在子进程之后。


第一部分实验现象

(1) 子进程pid = 父进程pid + 1

Fork()函数返回值,父进程返回值为子进程pid ,子进程返回值为0

有wait时,子进程调用exec函数取代父进程,完成后父进程运行

无wait时,父子进程并发,顺序依据进程调度而定,一般不相同。

(2) 加入全局变量,发现父子进程不共享全局变量。

(3) 加入exec函数。发现子进程通过exec函数让另一个程序取代了原本的进程空间,pid不变,但是原本程序段的代码不再运行。

加入system函数。发现子进程通过system函数新建了一个进程运行另一个程序,pid发生变化。原本程序段的代码依然运行。运行顺序中system调用的另一个进程总在子进程之前。


如何实现双线程

Pthread库及其函数

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);

函数参数:

\1. 线程句柄 thread:当一个新的线程调用成功之后,就会通过这个参数将线程的句柄返回给调用者,以便对这个线程进行管理。

\2. 入口函数 start_routine(): 当你的程序调用了这个接口之后,就会产生一个线程,而这个线程的入口函数就是start_routine()。如果线程创建成功,这个接口会返回0。

\3. 入口函数参数 *arg : start_routine()函数有一个参数,这个参数就是pthread_create的最后一个参数arg。这种设计可以在线程创建之前就帮它准备好一些专有数据,最典型的用法就是使用C++编程时的this指针。start_routine()有一个返回值,这个返回值可以通过pthread_join()接口获得。

\4. 线程属性 attr: pthread_create()接口的第二个参数用于设置线程的属性。这个参数是可选的,当不需要修改线程的默认属性时,给它传递NULL就行。具体线程有那些属性,我们后面再做介绍。

线程的合并是一种主动回收线程资源的方案。当一个进程或线程调用了针对其它线程的pthread_join()接口,就是线程合并了。这个接口会阻塞调用进程或线程,直到被合并的线程结束为止。当被合并线程结束,pthread_join()接口就会回收这个线程的资源,并将这个线程的返回值返回给合并者。

与线程合并相对应的另外一种线程资源回收机制是线程分离,调用接口是pthread_detach()。线程分离是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。因为线程分离是启动系统的自动回收机制,那么程序也就无法获得被分离线程的返回值,这就使得pthread_detach()接口只要拥有一个参数就行了,那就是被分离线程句柄。

原文链接:https://blog.csdn.net/jiajun2001/article/details/12624923


使用Thread实现多线程,为什么没有出现多线程并发执行的场景?

CPU的执行效率很高,如果你的主线程里面没有几行需要执行的代码,可能当你的非主线程刚创建好,主线程就已经执行完成了,然后才执行主线程,如果你把主线程中执行的代码多写几行,那样就会看到多线程并发执行的场景了,其实多线程并发执行并不是真正的多线程一起执行,而是多个线程在抢夺CPU的执行权,谁抢到执行权,谁就执行代码,只要具有足够的代码,可以体现出多线程并发的场景的。


两个子线程并发运行,并发访问全局变量导致出错

加入互斥锁或者加入join函数来控制线程运行顺序


如何加入互斥锁?

\#include <pthread.h>
​
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
​
pthread_mutex_lock(&mtx);
​
pthread_mutex_unlock(&mtx);

第二部分实验现象

在main函数里创建除了当前线程以外的两个子线程,分别对全局变量做不同的操作。

(1)由输出结果可以确认,两个子线程和当前线程共享进程信息

(2)加入join函数,子线程按顺序运行。

(3)加入互斥锁,子线程按顺序运行




 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值