南京邮电大学操作系统——实验一:进程、线程的创建与并发执行

    科技2024-11-30  55

    文章目录

    实验环境实验记录实验一:观察进程标识符实验二:子进程的创建实验三:观察进程并发执行实验四:进程内存空间相互独立实验五:线程的创建及其观察实验六:多个进程的并发运行实验六:线程共享变量

    实验环境

    Ubuntu 18.04Visual Code

    实验记录

    实验一:观察进程标识符

    所用代码如下:

    #include <stdio.h> #include <sys/types.h> #include <unistd.h> int main() { //pid_t是数据类型,实际上是一个整型,通过typedef重新定义了一个名字,用于存储进程id pid_t pid; //scanf("%d",&i);//让进程等待,方便使用shell观察进程pid pid= getpid(); //getpid()函数返回当前进程的id号 printf("Process id :%d\n", pid); return 0; }

    使用如下命令打开gedit

    gedit your_file_name

    将代码复制到打开的文本编辑器里,并保存。使用如下指令进行编译

    gcc your_file_name -o output_name

    需要注意的是源代码必须以.c结尾,否则会报如下错误 运行,发现了进程的pid为: 将代码的scanf注释取消,重新运行,使用如下指令

    ps -al

    可在进程列表中观察到对于的进程:

    实验二:子进程的创建

    #include <stdio.h> #include <sys/types.h> #include <unistd.h> int main() { pid_t cid; printf("Before fork Process id :%d\n", getpid()); /* fork()函数用于创建一个新的进程,该进程为当前进程的子进程,创建的方法是:将当前进程的内存内容完整拷贝一份到内存的另一个区域,两个进程为父子关系,他们会同时(并发)执行fork()语句后面的所有语句。 fork()的返回值: 如果成功创建子进程,对于父子进程fork会返回不同的值,对于父进程它的返回值是子进程的进程id值,对于子进程它的返回值是0. 如果创建失败,返回值为-1. */ cid = fork(); printf("After fork, Process id :%d\n", getpid()); int i; //scanf("%d",&i);//阻塞函数 return 0; }

    运行结果为 可以发现两个现象:

    出现了两个Afterfock语句,原因在于使用了fock函数后,Linux创建了一个进程,该进程的EIP寄存器值被设为fock()函数后的语句在两个Afterfock语句之间出现了shell提示符,该原因为在子进程在主进程运行完成之后才被调度,此时shell进程已被调度,输出了shell提示符。将阻塞函数加入后,再次运行。 可以发现猜测是正确的。

    利用ps -al指令可以观察到PID(process id)和PPID(parent process id)

    实验三:观察进程并发执行

    实验代码为:

    #include <stdio.h> #include <sys/types.h> #include <unistd.h> int main() { pid_t cid; printf("Before fork process id :%d\n", getpid()); cid = fork(); if(cid == 0){ //该分支是子进程执行的代码 printf("Child process id (my parent pid is %d):%d\n", getppid(),getpid()); for(int i=0; i<3 ; i++) printf("hello\n"); }else{ //该分支是父进程执行的代码 printf("Parent process id :%d\n", getpid()); for(int i=0; i<3 ; i++) printf("world\n"); } return 0; }

    循环三次的运行结果为: 循环三百次的结果为: 可以看到…效果似乎不是很理想,三百次的循环也没有将一次进程调度的时间片耗尽,为此最好在循环中加一个阻塞函数sleep即可实现想要观察到的并发效果: 可以看到,在加了阻塞函数之后,进程主动放弃运行权,后再次被调度到,此时两个进程可以看出并发执行。

    实验四:进程内存空间相互独立

    实验代码为:

    #include <sys/types.h> #include <unistd.h> int main() { pid_t cid; int x = 100; cid = fork(); if (cid == 0) { //该分支是子进程执行的代码 x++; printf("In child: x=%d\n", x); } else { //该分支是父进程执行的代码 x--; printf("In parent: x=%d\n", x); } return 0; }

    编译运行,可以看到结果如下: 这验证了两个进程中的变量X是相互独立的。 添加了阻塞函数后的代码为:

    #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> #include<stdio.h> int main() { pid_t cid; int x = 100; cid = fork(); if (cid == 0) { //该分支是子进程执行的代码 x++; printf("In child: x=%d\n", x); sleep(3); } else { //该分支是父进程执行的代码 wait(NULL); x--; printf("In parent: x=%d\n", x); } return 0; }

    运行结果为: 可以看到,在加了阻塞函数之后,两个进程的调度被控制了,此时能够更加清晰的观察到两个变量是独立的。

    实验五:线程的创建及其观察

    实验代码为:

    #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <pthread.h> void *threadFunc(void *arg) { //线程函数 printf("In NEW thread\n"); } int main() { pthread_t tid; pthread_create(&tid, NULL, threadFunc, NULL); //线程创建函数 pthread_join(tid, NULL); //等待指定的线程结束 printf("In main thread\n"); return 0; }

    其中pthread_create的函数声明为:

    int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,\ void *(*start_rtn)(void*),void *arg);

    可以看到,第三个参数定义了线程的开始地址,第四个参数向线程压入了一个参数。 而pthread_join的函数声明为:

    int pthread_join(pthread_t thread, void **retval);

    其中第二个参数为线程返回的函数,这个函数的作用是等待指定的进程结束。那为什么要调用这个函数呢?这里有一篇博客解释了这个原因,也简要解释了Linux环境下线程的创建和工作过程。 https://blog.csdn.net/heybeaman/article/details/90896663 采用如下指令编译

    gcc helloThread.c -o helloThread -pthread

    运行 可以发现,两个线程都被运行了起来。 将pthread_join进行注释,重新编译运行 可以看到,新创建的线程并没有得到运行的机会。 原因可以在上面的博客中找到答案:main主线程创建完新的线程之后就运行完了,此时整个进程已经运行完毕,资源被系统回收,新创建的线程再也没有机会得到运行。

    实验六:多个进程的并发运行

    实验代码如下:

    #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <pthread.h> void* hello(void* arg){ //线程函数 for(int i=0;i<300;i++) printf("hello(%d)\n",i); } void* world(void* arg){ //线程函数 for(int i=0;i<300;i++) printf("world(%d)\n",i); } int main() { pthread_t tid1,tid2; pthread_create(&tid1, NULL, hello, NULL); pthread_create(&tid2, NULL, world, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); printf("In main thread\n"); return 0; }

    运行,可以发现效果并不好… 其原因和父子进程的原因差不多:时间片还没有耗尽时一个线程就已经结束了。我们修改一下实验代码

    #include <unistd.h> #include <stdio.h> #include <pthread.h> void *hello(void *arg) { //线程函数 for (int i = 0; i < 3; i++){ sleep(0.1); printf("hello(%d)\n", i); } } void *world(void *arg) { //线程函数 for (int i = 0; i < 3; i++){ sleep(0.1); printf("world(%d)\n", i); } } int main() { pthread_t tid1, tid2; pthread_create(&tid1, NULL, hello, NULL); pthread_create(&tid2, NULL, world, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); printf("In main thread\n"); return 0; }

    重新编译运行: 可以观察到对应的线程的并发执行。

    实验六:线程共享变量

    代码如下

    #include <unistd.h> #include <stdio.h> #include <pthread.h> int global_value=100; void *hello(void *arg) { //线程函数 for (int i = 0; i < 3; i++){ sleep(0.1); printf("hello(%d)\n", global_value++); } } void *world(void *arg) { //线程函数 for (int i = 0; i < 300; i++){ sleep(0.1); printf("world(%d)\n", global_value++); } } int main() { pthread_t tid1, tid2; pthread_create(&tid1, NULL, hello, NULL); pthread_create(&tid2, NULL, world, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); printf("In main thread,the global_value(%d)\n",global_value); return 0; }

    编译运行:

    可以看到,变量确实是共享的

    Processed: 0.016, SQL: 8