当前位置:   article > 正文

Unix/Linux操作系统分析实验一 进程控制与进程互斥_利用信号量机制控制多线程的运行顺序,并实现多线程中数据的共享

利用信号量机制控制多线程的运行顺序,并实现多线程中数据的共享

Unix/Linux操作系统分析实验二 内存分配与回收:Linux系统下利用链表实现动态内存分配

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件

Unix/Linux操作系统分析实验四 设备驱动: Linux系统下的字符设备驱动程序编程

本文章用于记录自己所学的内容,方便自己回顾复习。

实验内容

  1. 利用fork函数编写一个简单的多进程程序,用ps命令查看系统中进程的运行状况,并分析输出结果。
  2. 在新创建的子进程中,使用exec类的函数启动另一程序的执行;分析多进程时系统的运行状态和输出结果;
  3. 利用最常用的三个函数pthread_create,pthread_join和pthread_exit编写了一个最简单的多线程程序。理解多线程的运行和输出情况;
  4. 利用信号量机制控制多线程的运行顺序,并实现多线程中数据的共享;
  5. 分析Linux系统下多进程与多线程中的区别;
  6. 编写程序实现进程的管道通信。用系统调用pipe( )建立一管道,二个子进程P1和P2分别向管道各写一句话:

        Child 1 is sending a message!

        Child 2 is sending a message!

        父进程从管道中读出二个来自子进程的信息并显示(要求先接收P1,后P2)。

     7.编写一个HelloWorld内核模块,并进行装载和卸载操作。

实验步骤:

内容一:利用fork函数编写一个简单的多进程程序,最后使用ps命令查看系统中进程的运行状况。

PID:进程标识符 TTY:设备终端号 TIME:进程的运行时间 CMD:执行程序的参数和命令

分析输出结果:父进程调用fork系统调用函数来创建一个子进程,fork函数返回0时,说明子进程在执行;返回子进程的PID时,说明父进程在执行。

内容二:使用父进程调用fork()创建一个子进程,通过调用exec()来用新的程序(输出/bin/ls路径下的所有文件)代替子进程的内容,然后可以调用wait()来控制进程执行顺序,子进程输出/bin/ls路径下的所有文件,父进程输出语句ls complete !。

分析多进程时系统的运行状态和输出结果:

 输入top命令查看系统运行状态和进程运行状态:

第一行说明:

top – :系统当前时间

up:服务器连续运行的时间,笔者见过有服务器连续运行一年以上,linux服务器还是非常稳定的。

user:当前有多少用户登录系统

load average:这个边有3个数值分别表示系统在前1分钟,5分钟,15分钟的工作负载,根据笔者以往的经验来看单核负载在3-5之间比较合适,经常在1以下,说明cpu利用率不高,在5以上,cpu会处于较高负载状态,会容易宕机。有一次项目上线,晚上加班观察服务器状况,这个值长时间保持在72左右,因为服务器有八核,所以每核的值为9,后来服务器就挂了。

第二行就是显示任务的数量情况,其中zombie要注意一下,这个是表示僵尸进程,出现了僵尸进程要注意下僵尸进程是如何产生的。如果不找到产生原因,即使杀死了,可能也会再次出现。

第三行表示cpu的运行情况,按下1可以显示每个核的运行情况。

第四行表示内存memory的使用情况。

第五行表示交换空间swap的使用情况。

进程的运行状态,每个表头表示的含义如下:

PID:进程编号

USER:进程所属用户

PR/NI:Priority/Nice value进程执行的优先顺序

VIRT:Virtual Image (kb) 虚拟内存使用总额

RES:Resident size (kb) 常驻内存

SHR:Shared Mem size (kb) 共享内存

S:Process Status 进程状态

%CPU:cpu使用率

%MEM:内存使用率

TIME+:进程开始运行时使用cpu的总时间

COMMAND:进程运行的命令

在top状态下按f可以查看表头字段说明。

内容三:调用函数pthread_create来创建三个线程(在创建一个线程后,可以调用pthread_exit函数使当前线程终止并返回一个值,该值可由函数pthread_join()获取),如果函数返回值不为0,则说明创建线程失败,直接退出程序。调用函数pthread_join来等待所有线程结束,函数返回值不为0,则说明还有线程没有退出;打印相应的信息,退出程序。

理解多线程的运行和输出情况;

(1)运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。

(2)线程间方便的通信机制。由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。

内容四:创建4个线程,其中两个线程(皆以读方式打开文件1.dat)负责从文件读取数据到公共的缓冲区,另外两个线程(皆以写方式打开文件(2.dat)从缓冲区读取数据作不同的处理(加和乘运算),利用信号量机制控制多线程的运行顺序,并实现多线程中数据的共享。

内容五:分析Linux系统下多进程与多线程中的区别;

答:多进程和多线程的主要区别是:线程是进程的子集(部分),一个进程可能由多个线程组成。多进程的数据是分开的、共享复杂,需要用IPC,但同步简单;多线程共享进程数据,共享简单,但同步复杂。

内容六:用系统调用pipe( )建立一个无名管道,二个子进程P1和P2分别向管道各写一句话:

Child 1 is sending a message!

Child 2 is sending a message!

最后父进程从管道中读出二个来自子进程的信息并显示(要求先接收P1,后P2)。

内容七:首先编写一个HelloWorld.c文件,最后将内核模块进行装载(命令:insmod HelloWorld.ko)和卸载(命令:rmmod HelloWorld)操作。

实验结果分析(截屏的实验结果,与实验结果对应的实验分析)

内容一:

内容二:

内容三:

内容四:

内容六:

内容七:

实验总结:

遇到的问题:在进行内容五、分析Linux系统下多进程与多线程中的区别:编译源文件成功,运行时出现如下错误:

通过查询资料发现:段错误的原因是源文件内存的大小超过了Ubuntu所在段的大小,所以在程序执行的过程中运行到相关的步骤时就会出现段错误(核心已转储)的提示。

解决方法:

在命令行输入命令:ulimit -a(查看Ubuntu当前栈空间的大小)

输入命令:ulimit -s  1024000(将当前栈空间的大小改为100M)

正确结果:

内容七,编写一个HelloWorld内核模块,并进行装载和卸载操作,Makefile的文件名不能为MakeFile,否则会出现以下情况:

实验总结

通过这次实验,我理解了进程、线程的结构和学会创建新进程和新线程的方法,了解在Linux系统中多进程和多线程的区别,了解管道的类型及其建立方法、学会如何使用进程来通过无名管道通信,熟悉内核模块正确的编写规则和方法、理解内核模块的装载和卸载操作,并且能够熟练使用GDB调试程序。

所有实验的源代码如下:

1-1.c

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/wait.h>
  4. #include <unistd.h>
  5. int main() {
  6. int pid = fork(); //返回值是负数,说明子进程创建失败;返回值是0,则说明处于子进程中;返回值是子进程的进程号,则说明处于父进程中。
  7. switch(pid) {
  8. case -1:
  9. printf("fork fail!\n"); //创建子进程失败
  10. case 0:
  11. printf("Return value of the fork function: %d\t Child process in progress!\n", pid); //子进程正在执行
  12. exit(1); //终止子进程的执行
  13. default:
  14. wait(NULL); //父进程等待子进程完成
  15. printf("Return value of the fork function: %d\t Parent process in process!\n", pid); //父进程正在执行
  16. exit(0); //终止父进程的执行
  17. }
  18. }

1-2.c

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<unistd.h>
  4. #include <sys/wait.h>
  5. int main()
  6. {
  7. int pid = fork(); //返回值是负数,说明子进程创建失败;返回值是0,则说明处于子进程中;返回值是子进程的进程号,则说明处于父进程中。
  8. switch (pid) {
  9. case -1:
  10. printf("fork fail!\n"); //创建子进程失败
  11. exit(1); //父进程退出
  12. case 0: //子进程在执行
  13. execl("/bin/ls", "ls", "-1", "-color", NULL); //子进程用exec( )装入命令ls ,exec( )后,子进程的代码被ls的代码取代,这时子进程的PC指向ls的第1条语句,开始执行ls的命令代码。
  14. printf("exec fail!\n");
  15. exit(1); //终止子进程的执行
  16. default: //父进程在执行
  17. wait(NULL); //父进程等待子进程结束后才执行
  18. printf("ls completed !\n");
  19. exit(0); //终止父进程的执行
  20. }
  21. }

1-3.c

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<pthread.h>
  4. void thread(void* arg) {
  5. for (int i = 0; i < 3; i++) {
  6. printf("This is a pthread %d.\n", i + 1);
  7. pthread_exit((void*)8); //使线程终止,线程结束会返回一个值,该值可由函数pthread_join()获取
  8. }
  9. }
  10. int main(void* arg) {
  11. pthread_t id; //线程标识号
  12. int ret;
  13. ret = pthread_create(&id, NULL, (void *)thread, NULL); //创建一个线程,并使得该线程执行thread函数
  14. for (int i = 0; i < 3; i++)
  15. printf("This is the main process %d.\n", i + 1);
  16. if(ret!=0){
  17. printf ("Create pthread error!\n"); //创建线程失败
  18. exit (1); //退出程序
  19. }
  20. void* temp;
  21. ret = pthread_join(id, &temp); //用来等待一个线程结束,直到线程退出后才执行下面的代码。
  22. if (ret) {
  23. printf("The pthread is not exit.\n");
  24. return -1;
  25. }
  26. printf("The pthread exits and returns a value %d.\n", (int)temp);
  27. return (0);
  28. }

1-4.c

  1. //在这个例子中,一共有4个线程,其中两个线程负责从文件读取数据到公共的缓冲区,另外两个线程从缓冲区读取数据作不同的处理(加和乘运算)。
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<pthread.h>
  5. #include<semaphore.h>
  6. #define MAXSTACK 100
  7. int stack[MAXSTACK][2];
  8. int size = 0;
  9. sem_t sem; //
  10. //从文件1.dat读取数据,每读一次,信号量加一
  11. void ReadData1(void* arg) {
  12. FILE* fp = fopen("1.dat", "r"); //以读的方式打开文件1.dat
  13. while (!feof(fp)) { //函数feof:若遍历到文件结束符则返回true,否则返回false
  14. fscanf(fp, "%d %d", &stack[size][10], &stack[size][1]);
  15. sem_post(&sem); //增加信号量sem的值
  16. size++; //每读一次,信号量加一
  17. }
  18. fclose(fp); //关闭文件
  19. }
  20. //从文件2.dat读取数据
  21. void ReadData2(void* arg) {
  22. FILE* fp = fopen("2.dat", "r"); //以读的方式打开文件2.dat
  23. while (!feof(fp)) { //函数feof:若遍历到文件结束符则返回true,否则返回false
  24. fscanf(fp, "%d %d", &stack[size][0], &stack[size][1]);
  25. sem_post(&sem); //增加信号量sem的值
  26. size++; //每读一次,信号量加一
  27. }
  28. fclose(fp); //关闭文件
  29. }
  30. //阻塞等待缓冲区有数据,读取数据后,释放缓冲区空间,继续等待
  31. void HandleData1(void* arg) {
  32. while (1) {
  33. sem_wait(&sem); //用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一
  34. printf("Plus:%d+%d=%d\n", stack[size][0], stack[size][1], stack[size][0] + stack[size][1]);
  35. size--; //信号量减一
  36. }
  37. }
  38. //阻塞等待缓冲区有数据,读取数据后,释放缓冲区空间,继续等待
  39. void HandleData2(void* arg) {
  40. while (1) {
  41. sem_wait(&sem); //用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一
  42. printf("Multiply:%d*%d=%d\n", stack[size][0], stack[size][1], stack[size][0] * stack[size][1]);
  43. size--; //信号量减一
  44. }
  45. }
  46. int main(void* arg) {
  47. pthread_t t1, t2, t3, t4;
  48. sem_init(&sem, 0, 0); //初始化信号量sem,第二个参数0表示此信号量只能为当前的所有线程共享,若不为0,则在进程间共享;第三个参数0表示信号量的初始值
  49. pthread_create(&t1, NULL, (void*)HandleData1, NULL); //用来创建一个线程1
  50. pthread_create(&t2, NULL, (void*)HandleData2, NULL); //用来创建一个线程2
  51. pthread_create(&t3, NULL, (void*)ReadData1, NULL); //用来创建一个线程3
  52. pthread_create(&t4, NULL, (void*)ReadData2, NULL); //用来创建一个线程4
  53. //防止程序过早退出,等其它线程结束后,在退出程序
  54. pthread_join(t1, NULL); //用来等待一个线程的结束
  55. }

1-6.c

  1. #include<unistd.h>
  2. #include<signal.h>
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include <sys/wait.h>
  6. int pid1, pid2; //存储进程的进程标识符
  7. void main(void* arg) {
  8. int fd[2]; //句柄
  9. char outpipe[100], inpipe[100]; //无名管道的读出端和写入端
  10. pipe(fd); //新建立一个无名管道fd
  11. while ((pid1 = fork()) == -1); //创建子进程1
  12. if (pid1 == 0) {//返回值为0,说明子进程1在运行
  13. lockf(fd[1], 1, 0); //给子进程上锁,第一个参数fd[1]是文件描述符,第二个参数为1表示锁定(0表示解锁),第三个参数表示锁定或解锁的字节数,0表示从文件的当前位置到文件尾
  14. sprintf(outpipe, "Child 1 process is sending message!"); //把字符串放入读出端数组outpipe中
  15. write(fd[1], outpipe, 50); //向管道里写入长为50字节的字符串
  16. sleep(5); //子进程1自我阻塞5秒(即当前进程睡眠/等待/延迟5秒)
  17. lockf(fd[1], 0, 0); //给子进程1解锁,第一个参数fd[1]是文件描述符,第二个参数为1表示锁定(0表示解锁),第三个参数表示锁定或解锁的字节数,0表示从文件的当前位置到文件尾
  18. //__exit() 函数:直接使进程停止运行,清除其使用的内存空间,并清除其在内核中的各种数据结构。
  19. exit(0); //子进程1退出(在执行退出之前,会将文件缓冲区中的内容写回文件,即清理I/O缓冲;)
  20. }
  21. else {//返回值大于0,说明父进程在执行
  22. while ((pid2 = fork()) == -1); //创建子进程2
  23. if (pid2 == 0) {//返回值为0,说明子进程2在运行
  24. sprintf(outpipe, "Child 2 process is sending message!");
  25. write(fd[1], outpipe, 50); //向管道里写入长为50字节的字符串,子进程之间发生互斥
  26. sleep(5); //子进程2自我阻塞5秒(即当前进程睡眠/等待/延迟5秒)
  27. lockf(fd[1], 0, 0); //给子进程1解锁,第一个参数fd[1]是文件描述符,第二个参数为1表示锁定(0表示解锁),第三个参数表示锁定或解锁的字节数,0表示从文件的当前位置到文件尾
  28. exit(0); //子进程2退出(在执行退出之前,会将文件缓冲区中的内容写回文件,即清理I/O缓冲;)
  29. }
  30. else {
  31. wait(0); //等待子进程1结束后,才执行以下操作
  32. read(fd[0], inpipe, 50); //从管道里读出长为50字节的字符串
  33. printf("%s\n", inpipe); //输出管道写入端的内容
  34. wait(0); //等待子进程2结束后,才执行以下操作
  35. read(fd[0], inpipe, 50); //从管道里读出长为50字节的字符串
  36. printf("%s\n", inpipe); //输出管道写入端的内容
  37. exit(0); //父进程退出
  38. }
  39. }
  40. }

HelloWorld.c

  1. //任何模块都要包含的三个头文件
  2. #include <linux/init.h> //包含了宏__init和__exit
  3. #include <linux/kernel.h> //包含了常用的内核函数
  4. #include <linux/module.h> //包含了对模块的版本控制
  5. static int __init lkp_init(void) //模块加载函数,当模块被插入到内核时调用它
  6. {
  7. printk("<0>" "Hello World from the kernel space...\n"); //模块加载的时候系统会打印
  8. return 0;
  9. }
  10. static void __exit lkp_cleanup(void) //模块卸载函数,当模块从内核移走时调用它
  11. {
  12. printk("<0>" "Good Bye World! leaving kernel space...\n"); //模块卸载的时候系统会打印
  13. }
  14. module_init(lkp_init); //模块初始化
  15. module_exit(lkp_cleanup); //模块退出
  16. MODULE_LICENSE("GPL"); //模块具有GUN公共许可证
  17. MODULE_AUTHOR("作者");
  18. MODULE_DESCRIPTION("功能描述");

Makefile(注意:Makefile的格式要写对,例如:命名为MakeFile时,使用make命令编译时会出错,具体命名规则可在CSDN或者百度搜索)

  1. obj-m:=HelloWorld.o
  2. CURRENT_PATH:=$(shell pwd)
  3. LINUX_KERNEL:=$(shell uname -r)
  4. LINUX_KERNEL_PATH:=usr/src/linux-headers-$(LINUX_KERNEL)
  5. all:
  6. make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
  7. clean:
  8. make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) cleanobj-m:=HelloWorld.o
  9. PWD:=$(shell pwd)
  10. KVER:=$(shell uname -r)
  11. KDIR:=/lib/modules/$(KVER)/build/
  12. all:
  13. $(MAKE) -C $(KDIR) M=$(PWD)
  14. clean:
  15. rm -rf *.o *.mod.c *.mod.o *.ko *.symvers *.order *.a

如若侵权,可联系我,我会在看到消息的同时,删除侵权的部分,谢谢大家!

如果大家有疑问,可在评论区发表或者私信我,我会在看到消息的时候,尽快回复大家!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/210298
推荐阅读
相关标签
  

闽ICP备14008679号