Unix/Linux操作系统分析实验一 进程控制与进程互斥

您所在的位置:网站首页 进程控制模块 Unix/Linux操作系统分析实验一 进程控制与进程互斥

Unix/Linux操作系统分析实验一 进程控制与进程互斥

2023-10-14 06:14| 来源: 网络整理| 查看: 265

Unix/Linux操作系统分析实验二 内存分配与回收:Linux系统下利用链表实现动态内存分配 Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件 Unix/Linux操作系统分析实验四 设备驱动: Linux系统下的字符设备驱动程序编程 本文章用于记录自己所学的内容,方便自己回顾复习。 实验内容 利用fork函数编写一个简单的多进程程序,用ps命令查看系统中进程的运行状况,并分析输出结果。在新创建的子进程中,使用exec类的函数启动另一程序的执行;分析多进程时系统的运行状态和输出结果;利用最常用的三个函数pthread_create,pthread_join和pthread_exit编写了一个最简单的多线程程序。理解多线程的运行和输出情况;利用信号量机制控制多线程的运行顺序,并实现多线程中数据的共享;分析Linux系统下多进程与多线程中的区别;编写程序实现进程的管道通信。用系统调用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 #include #include #include #include int main() { int pid = fork(); //返回值是负数,说明子进程创建失败;返回值是0,则说明处于子进程中;返回值是子进程的进程号,则说明处于父进程中。 switch(pid) { case -1: printf("fork fail!\n"); //创建子进程失败 case 0: printf("Return value of the fork function: %d\t Child process in progress!\n", pid); //子进程正在执行 exit(1); //终止子进程的执行 default: wait(NULL); //父进程等待子进程完成 printf("Return value of the fork function: %d\t Parent process in process!\n", pid); //父进程正在执行 exit(0); //终止父进程的执行 } } 1-2.c #include #include #include #include int main() { int pid = fork(); //返回值是负数,说明子进程创建失败;返回值是0,则说明处于子进程中;返回值是子进程的进程号,则说明处于父进程中。 switch (pid) { case -1: printf("fork fail!\n"); //创建子进程失败 exit(1); //父进程退出 case 0: //子进程在执行 execl("/bin/ls", "ls", "-1", "-color", NULL); //子进程用exec( )装入命令ls ,exec( )后,子进程的代码被ls的代码取代,这时子进程的PC指向ls的第1条语句,开始执行ls的命令代码。 printf("exec fail!\n"); exit(1); //终止子进程的执行 default: //父进程在执行 wait(NULL); //父进程等待子进程结束后才执行 printf("ls completed !\n"); exit(0); //终止父进程的执行 } } 1-3.c #include #include #include void thread(void* arg) { for (int i = 0; i < 3; i++) { printf("This is a pthread %d.\n", i + 1); pthread_exit((void*)8); //使线程终止,线程结束会返回一个值,该值可由函数pthread_join()获取 } } int main(void* arg) { pthread_t id; //线程标识号 int ret; ret = pthread_create(&id, NULL, (void *)thread, NULL); //创建一个线程,并使得该线程执行thread函数 for (int i = 0; i < 3; i++) printf("This is the main process %d.\n", i + 1); if(ret!=0){ printf ("Create pthread error!\n"); //创建线程失败 exit (1); //退出程序 } void* temp; ret = pthread_join(id, &temp); //用来等待一个线程结束,直到线程退出后才执行下面的代码。 if (ret) { printf("The pthread is not exit.\n"); return -1; } printf("The pthread exits and returns a value %d.\n", (int)temp); return (0); } 1-4.c //在这个例子中,一共有4个线程,其中两个线程负责从文件读取数据到公共的缓冲区,另外两个线程从缓冲区读取数据作不同的处理(加和乘运算)。 #include #include #include #include #define MAXSTACK 100 int stack[MAXSTACK][2]; int size = 0; sem_t sem; // //从文件1.dat读取数据,每读一次,信号量加一 void ReadData1(void* arg) { FILE* fp = fopen("1.dat", "r"); //以读的方式打开文件1.dat while (!feof(fp)) { //函数feof:若遍历到文件结束符则返回true,否则返回false fscanf(fp, "%d %d", &stack[size][10], &stack[size][1]); sem_post(&sem); //增加信号量sem的值 size++; //每读一次,信号量加一 } fclose(fp); //关闭文件 } //从文件2.dat读取数据 void ReadData2(void* arg) { FILE* fp = fopen("2.dat", "r"); //以读的方式打开文件2.dat while (!feof(fp)) { //函数feof:若遍历到文件结束符则返回true,否则返回false fscanf(fp, "%d %d", &stack[size][0], &stack[size][1]); sem_post(&sem); //增加信号量sem的值 size++; //每读一次,信号量加一 } fclose(fp); //关闭文件 } //阻塞等待缓冲区有数据,读取数据后,释放缓冲区空间,继续等待 void HandleData1(void* arg) { while (1) { sem_wait(&sem); //用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一 printf("Plus:%d+%d=%d\n", stack[size][0], stack[size][1], stack[size][0] + stack[size][1]); size--; //信号量减一 } } //阻塞等待缓冲区有数据,读取数据后,释放缓冲区空间,继续等待 void HandleData2(void* arg) { while (1) { sem_wait(&sem); //用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一 printf("Multiply:%d*%d=%d\n", stack[size][0], stack[size][1], stack[size][0] * stack[size][1]); size--; //信号量减一 } } int main(void* arg) { pthread_t t1, t2, t3, t4; sem_init(&sem, 0, 0); //初始化信号量sem,第二个参数0表示此信号量只能为当前的所有线程共享,若不为0,则在进程间共享;第三个参数0表示信号量的初始值 pthread_create(&t1, NULL, (void*)HandleData1, NULL); //用来创建一个线程1 pthread_create(&t2, NULL, (void*)HandleData2, NULL); //用来创建一个线程2 pthread_create(&t3, NULL, (void*)ReadData1, NULL); //用来创建一个线程3 pthread_create(&t4, NULL, (void*)ReadData2, NULL); //用来创建一个线程4 //防止程序过早退出,等其它线程结束后,在退出程序 pthread_join(t1, NULL); //用来等待一个线程的结束 } 1-6.c #include #include #include #include #include int pid1, pid2; //存储进程的进程标识符 void main(void* arg) { int fd[2]; //句柄 char outpipe[100], inpipe[100]; //无名管道的读出端和写入端 pipe(fd); //新建立一个无名管道fd while ((pid1 = fork()) == -1); //创建子进程1 if (pid1 == 0) {//返回值为0,说明子进程1在运行 lockf(fd[1], 1, 0); //给子进程上锁,第一个参数fd[1]是文件描述符,第二个参数为1表示锁定(0表示解锁),第三个参数表示锁定或解锁的字节数,0表示从文件的当前位置到文件尾 sprintf(outpipe, "Child 1 process is sending message!"); //把字符串放入读出端数组outpipe中 write(fd[1], outpipe, 50); //向管道里写入长为50字节的字符串 sleep(5); //子进程1自我阻塞5秒(即当前进程睡眠/等待/延迟5秒) lockf(fd[1], 0, 0); //给子进程1解锁,第一个参数fd[1]是文件描述符,第二个参数为1表示锁定(0表示解锁),第三个参数表示锁定或解锁的字节数,0表示从文件的当前位置到文件尾 //__exit() 函数:直接使进程停止运行,清除其使用的内存空间,并清除其在内核中的各种数据结构。 exit(0); //子进程1退出(在执行退出之前,会将文件缓冲区中的内容写回文件,即清理I/O缓冲;) } else {//返回值大于0,说明父进程在执行 while ((pid2 = fork()) == -1); //创建子进程2 if (pid2 == 0) {//返回值为0,说明子进程2在运行 sprintf(outpipe, "Child 2 process is sending message!"); write(fd[1], outpipe, 50); //向管道里写入长为50字节的字符串,子进程之间发生互斥 sleep(5); //子进程2自我阻塞5秒(即当前进程睡眠/等待/延迟5秒) lockf(fd[1], 0, 0); //给子进程1解锁,第一个参数fd[1]是文件描述符,第二个参数为1表示锁定(0表示解锁),第三个参数表示锁定或解锁的字节数,0表示从文件的当前位置到文件尾 exit(0); //子进程2退出(在执行退出之前,会将文件缓冲区中的内容写回文件,即清理I/O缓冲;) } else { wait(0); //等待子进程1结束后,才执行以下操作 read(fd[0], inpipe, 50); //从管道里读出长为50字节的字符串 printf("%s\n", inpipe); //输出管道写入端的内容 wait(0); //等待子进程2结束后,才执行以下操作 read(fd[0], inpipe, 50); //从管道里读出长为50字节的字符串 printf("%s\n", inpipe); //输出管道写入端的内容 exit(0); //父进程退出 } } } HelloWorld.c //任何模块都要包含的三个头文件 #include //包含了宏__init和__exit #include //包含了常用的内核函数 #include //包含了对模块的版本控制 static int __init lkp_init(void) //模块加载函数,当模块被插入到内核时调用它 { printk("" "Hello World from the kernel space...\n"); //模块加载的时候系统会打印 return 0; } static void __exit lkp_cleanup(void) //模块卸载函数,当模块从内核移走时调用它 { printk("" "Good Bye World! leaving kernel space...\n"); //模块卸载的时候系统会打印 } module_init(lkp_init); //模块初始化 module_exit(lkp_cleanup); //模块退出 MODULE_LICENSE("GPL"); //模块具有GUN公共许可证 MODULE_AUTHOR("作者"); MODULE_DESCRIPTION("功能描述"); Makefile(注意:Makefile的格式要写对,例如:命名为MakeFile时,使用make命令编译时会出错,具体命名规则可在CSDN或者百度搜索) obj-m:=HelloWorld.o CURRENT_PATH:=$(shell pwd) LINUX_KERNEL:=$(shell uname -r) LINUX_KERNEL_PATH:=usr/src/linux-headers-$(LINUX_KERNEL) all: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules clean: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) cleanobj-m:=HelloWorld.o PWD:=$(shell pwd) KVER:=$(shell uname -r) KDIR:=/lib/modules/$(KVER)/build/ all: $(MAKE) -C $(KDIR) M=$(PWD) clean: rm -rf *.o *.mod.c *.mod.o *.ko *.symvers *.order *.a

如若侵权,可联系我,我会在看到消息的同时,删除侵权的部分,谢谢大家! 如果大家有疑问,可在评论区发表或者私信我,我会在看到消息的时候,尽快回复大家!



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3