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

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

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<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include <sys/wait.h>

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<stdio.h>
#include<stdlib.h>
#include<pthread.h>

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<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#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<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include <sys/wait.h>
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 <linux/init.h>	 //包含了宏__init和__exit
#include <linux/kernel.h> //包含了常用的内核函数
#include <linux/module.h> //包含了对模块的版本控制
 
static int __init lkp_init(void) //模块加载函数,当模块被插入到内核时调用它
{
	printk("<0>" "Hello World from the kernel space...\n"); //模块加载的时候系统会打印
	return 0;
}
 
static void __exit lkp_cleanup(void) //模块卸载函数,当模块从内核移走时调用它
{
	printk("<0>" "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



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

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

  • 5
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值