Linux操作系统分析实验-多线程与内核模块编程,实验一
实验一 多线程与内核模块编程
一、实验目的
1、理解Linux下进程的结构;
2、理解Linux下产生新进程的方法(系统调用—fork函数);
3、掌握如何启动另一程序的执行;
4、理解Linux下线程的结构;
5、理解Linux下产生新线程的方法;
6、理解Linux系统下多进程与多线程的区别
7、了解什么是管道
8、熟悉UNIX/LINUX支持的管道通信方式
9、理解内核模块的编写和装载方法
二、实验内容
1、利用fork函数创建新进程,并根据fork函数的返回值,判断自己是处于父进程还是子进程中;
2、在新创建的子进程中,使用exec类的函数启动另一程序的执行;分析多进程时系统的运行状态和输出结果;
3、利用最常用的三个函数pthread_create,pthread_join和pthread_exit编写了一个最简单的多线程程序。理解多线程的运行和输出情况;
4、利用信号量机制控制多线程的运行顺序,并实现多线程中数据的共享;
5、分析Linux系统下多进程与多线程中的区别。
6、编写一个HelloWorld内核模块,并进行装载和卸载操作。
三、实验原理
1、Linux系统下多进程编程:
(1)理解Linux下进程的结构:
Linux系统中一个进程在用户内存里有三部份的数据,就是“数据段”,“堆栈段”和“代码段”,一般的CPU, 如I386,都有上述三种段寄存器,以方便操作系统的运行。“代码段” 就是存放了程序代码的数据,堆栈段存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。而数据段则存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。系统如果同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据段。
(2)利用fork()函数创建进程和利用exec类的函数来启动进程的执行
一个程序调用fork函数,系统就为一个新的进程准备了前述三个段,首先,系统让新的进程与旧的进程使用同一个代码段,因为它们的程序还是相同的,对于数据段和堆栈段,系统则复制一份给新的进程,这样,父进程的所有数据都可以留给子进程,但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。
一个进程一旦调用exec类函数,它本身就“死亡”了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。
这样就实现了多进程编程和运行了。
2、Linux系统下多线程编程
(1)为什么使用多线程?
使用多线程的理由之一是和进程相比,它是一种非常“节俭”的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种“昂贵”的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右。
使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
多线程程序作为一种多任务、并发的工作方式,还有以下的优点:
1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
(2)多线程编程
Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。
利用函数pthread_create来创建一个线程,利用函数pthread_join来等待一个线程的结束。多线程编程实例见实验指导书。
3、管道通信
所谓管道,是指能够连接一个写进程和一个读进程的、并允许它们以生产者—消费者方式进行通信的一个共享文件,又称为pipe文件。由写进程从管道的写入端(句柄1)将数据写入管道,而读进程则从管道的读出端(句柄0)读出数据。
(1)有名管道
一个可以在文件系统中长期存在的、具有路径名的文件。用系统调用mknod( )建立。它克服无名管道使用上的局限性,可让更多的进程也能利用管道进行通信。因而其它进程可以知道它的存在,并能利用路径名来访问该文件。对有名管道的访问方式与访问其他文件一样,需先用open( )打开。
(2)无名管道
一个临时文件。利用pipe( )建立起来的无名文件(无路径名)。只用该系统调用所返回的文件描述符来标识该文件,故只有调用pipe( )的进程及其子孙进程才能识别此文件描述符,才能利用该文件(管道)进行通信。当这些进程不再使用此管道时,核心收回其索引结点。
二种管道的读写方式是相同的,本实验只用无名管道。
(3)pipe文件的建立
分配磁盘和内存索引结点、为读进程分配文件表项、为写进程分配文件表项、分配用户文件描述符
(4)读/写进程互斥
内核为地址设置一个读指针和一个写指针,按先进先出顺序读、写。
为使读、写进程互斥地访问pipe文件,需使各进程互斥地访问pipe文件索引结点中的直接地址项。因此,每次进程在访问pipe文件前,都需检查该索引文件是否已被上锁。若是,进程便睡眠等待,否则,将其上锁,进行读/写。操作结束后解锁,并唤醒因该索引结点上锁而睡眠的进程。
四、实验步骤
要求写出实验过程和思路(用文字表述,或画流程图,或写出伪代码都可以)。
1、Linux中的fork()函数是用于创建一个新进程的系统调用。父进程和子进程的区别在于fork()函数的返回值不同,父进程中fork()函数返回子进程的进程ID,而子进程中fork()函数返回0。因此,我们可以通过判断fork()函数的返回值来确定当前进程是父进程还是子进程。
伪代码:
初始化进程ID变量 pid 和 pid2,以及返回值变量 retpid
获取当前进程ID并赋值给 pid
输出 "before fork:pid=" 并连接 pid 的值
执行 fork 操作,返回值赋值给 retpid
获取当前进程ID并赋值给 pid2
输出 "after fork:pid=" 并连接 pid2 的值
If pid 等于 pid2,则:
输出 "this is father print=" 并连接 retpid 的值
else: 输出 "this is child print,=" 连接 retpid 的值,后跟 ",", 并连接 getpid() 的值
Return 0
Fork()函数返回值是负数,说明子进程创建失败;返回值是0,则说明处于子进程中;返回值是子进程的进程号,则说明处于父进程中。
伪代码:
* int main()
* int pid = fork()
* 返回值
* -1: 子进程创建失败
* 0: 处于子进程中
* >0: 处于父进程中,返回值是子进程进程号
* switch (pid)
* case -1:
* printf("fork fail!\n")
* exit(1)
* case 0:
* execl("/bin/ls", "ls", "-1", "-color", NULL)
* 子进程用exec()装入命令ls
* exec()后,子进程的代码被ls的代码取代
* printf("exec fail!\n")
* exit(1)
* default:
* wait(NULL)
* 父进程等待子进程结束后才执行
* printf("ls completed !\n")
* exit(0)
2、在新创建的子进程中,使用exec类的函数启动另一程序的执行;分析多进程时系统的运行状态和输出结果;
3、利用最常用的三个函数pthread_create,pthread_join和pthread_exit编写了一个最简单的多线程程序。理解多线程的运行和输出情况;
调用函数pthread_create来创建三个线程(在创建一个线程后,可以调用pthread_exit函数使当前线程终止并返回一个值,该值可由函数pthread_join()获取),如果函数返回值不为0,则说明创建线程失败,直接退出程序。调用函数pthread_join来等待所有线程结束,函数返回值不为0,则说明还有线程没有退出;打印相应的信息,退出程序。
理解多线程的运行和输出情况;
(1)运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
(2)线程间方便的通信机制。由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。
伪代码:
主程序:
定义线程标识符 id
创建线程,指定执行函数为 thread
如果创建线程失败:
打印错误消息
退出程序
循环3次:
打印主进程信息
等待线程结束,获取线程返回值
打印线程返回的值
线程函数 thread:
参数:无
循环3次:
打印线程信息
结束线程并返回值8
4、利用信号量机制控制多线程的运行顺序,并实现多线程中数据的共享;
伪代码:
主程序:
初始化信号量 sem 为 0
创建线程 t1 执行 HandleData1
创建线程 t2 执行 HandleData2
创建线程 t3 执行 ReadData1
创建线程 t4 执行 ReadData2
等待线程 t1 结束
ReadData1:
打开文件 1.dat
当没有到达文件末尾:
读取一对整数到缓冲区 stack
信号量 sem 加一
缓冲区大小 size 加一
关闭文件
ReadData2:
打开文件 2.dat
当没有到达文件末尾:
读取一对整数到缓冲区 stack
信号量 sem 加一
缓冲区大小 size 加一
关闭文件
HandleData1:
无限循环:
等待信号量 sem
从缓冲区 stack 读取数据
执行加法运算并打印结果
缓冲区大小 size 减一
HandleData2:
无限循环:
等待信号量 sem
从缓冲区 stack 读取数据
执行乘法运算并打印结果
缓冲区大小 size 减一
5、编写一个HelloWorld内核模块,并进行装载和卸载操作。
编写内核模块代码
编写初始化函数init_module或者使用宏module_init来声明。这个函数将在模块装载时执行。
编写清理函数cleanup_module或者使用宏module_exit来声明。这个函数将在模块卸载时执行。
在初始化函数中,使用printk函数打印“Hello World”信息到内核日志。
在清理函数中,同样使用printk打印一个信息表示模块被卸载。
编写Makefile
创建一个Makefile来编译内核模块。
使用内核构建系统的make命令来编译模块。
编译内核模块
运行make命令来编译你的内核模块。
装载内核模块
使用insmod命令装载编译好的内核模块。
验证模块是否装载成功
使用lsmod命令来检查模块是否在列表中。
使用dmesg命令来检查内核日志,查看“Hello World”信息是否被打印。
卸载内核模块
使用rmmod命令卸载你的内核模块。
验证模块是否卸载成功
再次使用lsmod检查模块是否从列表中移除。
使用dmesg检查内核日志,确认卸载信息被打印。
五、实验数据及源代码
(学生必须提交程序源代码,并有注释)
1、利用fork函数创建新进程,并根据fork函数的返回值,判断自己是处于父进程还是子进程中;
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{pid_t pid;pid_t pid2;pid_t retpid;pid = getpid();printf("before fork:pid=%d\n", pid);retpid = fork();pid2 = getpid();printf("after fork:pid=%d\n", pid2);//子进程返回值是0,父进程返回值是子进程pid号if (pid == pid2) {printf("this is father print%d\n", retpid);}else {printf("this is child print,%d,pid=%d\n", retpid, getpid());}return 0;
}
2、在新创建的子进程中,使用exec类的函数启动另一程序的执行;分析多进程时系统的运行状态和输出结果;
#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);}
}
3、利用最常用的三个函数pthread_create,pthread_join和pthread_exit编写了一个最简单的多线程程序。理解多线程的运行和输出情况;
#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);
}
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.datwhile (!feof(fp)) { //函数feof:若遍历到文件结束符则返回true,否则返回falsefscanf(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.datwhile (!feof(fp)) { //函数feof:若遍历到文件结束符则返回true,否则返回falsefscanf(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); //创建线程1pthread_create(&t2, NULL, (void*)HandleData2, NULL); //线程2pthread_create(&t3, NULL, (void*)ReadData1, NULL); //线程3pthread_create(&t4, NULL, (void*)ReadData2, NULL); //线程4//防止程序过早退出,等其它线程结束后,在退出程序pthread_join(t1, NULL); //用来等待一个线程的结束
}
5、编写一个HelloWorld内核模块,并进行装载和卸载操作。
Hello World.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
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) modulesclean :
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
六、实验结果分析(截屏的实验结果,与实验结果对应的实验分析)
1、实验结果与实验程序、实验步骤、实验原理的对应分析;
2、实验过程中的问题及原因和解决办法。
1、利用fork函数创建新进程,并根据fork函数的返回值,判断自己是处于父进程还是子进程中;
子进程返回值是0,父进程返回值是子进程pid号
2、在新创建的子进程中,使用exec类的函数启动另一程序的执行;
当子进程在执行时,子进程用exec()装入命令ls,exec()后,子进程的代码被ls的代码取代,这时子进程的PC指向ls的第1条语句,开始执行ls的命令代码。
当父进程在执行,父进程等待子进程结束后才执行。
分析多进程时系统的运行状态和输出结果:
第一行 :
top – :系统当前时间
up:服务器连续运行的时间,笔者见过有服务器连续运行一年以上,linux服务器还是非常稳定的。
user:当前有多少用户登录系统
load average:这个边有3个数值分别表示系统在前1分钟,5分钟,15分钟的工作负载
第二行就是显示任务的数量情况,其中zombie表示僵尸进程
第三行表示cpu的运行情况,按下1可以显示每个核的运行情况。
第四行表示内存memory的使用情况。
第五行表示交换空间swap的使用情况。
3、利用最常用的三个函数pthread_create,pthread_join和pthread_exit编写了一个最简单的多线程程序。理解多线程的运行和输出情况;
4、利用信号量机制控制多线程的运行顺序,并实现多线程中数据的共享;
5、分析Linux系统下多进程与多线程中的区别。
对比 | 多进程 | 多线程 |
独立性/共享性 | 每个进程拥有自己独立的地址空间,一个进程的崩溃不会直接影响到其他进程。但这也意味着进程间通信(IPC)需要特殊的机制,如管道、消息队列、共享内存等。 | 同一进程内的所有线程共享进程的地址空间,包括数据段、代码段和打开的文件等。这使得线程间通信变得简单和快速。 |
资源占用 | 每个进程有自己的一套资源,包括文件描述符、环境变量等。相对于线程,进程的创建、管理和上下文切换的开销较大。 | 线程比进程更轻量级,它们共享大部分进程资源,线程的创建、管理和上下文切换的开销远小于进程。 |
创建方式 | 在Linux中,新进程通常是通过`fork()`系统调用创建的,该调用会复制父进程的地址空间,创建几乎完全独立的子进程。 | 线程通常是通过专门的线程库(如POSIX线程库 - pthreads)创建的。 |
内存管理 | 进程有独立的内存空间,所以内存管理相对简单,但是资源利用率低于多线程。 | 由于共享内存空间,线程间共享数据非常方便,但是也容易发生同步和数据一致性的问题。 |
安全性 | 由于进程间内存是隔离的,因此一个进程的内存错误不会影响到其他进程。 | 线程间的错误和内存泄露可能会影响同一进程内的其他线程,因此需要更加注意同步和互斥机制。 |
区别总结:
隔离性:多进程之间相互隔离;多线程之间共享进程资源。
资源需求:多进程比多线程占用更多的资源。
通信:多线程之间的通信通常比多进程之间的通信更高效。
创建和管理开销:创建线程的开销小于创建进程。
故障容忍:进程间不会因为其他进程崩溃而受影响,而线程间可能会相互影响。
开发和维护难度:多线程程序的开发和维护通常比多进程更加复杂,特别是在需要处理同步和共享数据的问题时。
在选择使用多进程还是多线程时,通常需要根据应用程序的特性和需求来决定。如果需要大量的独立任务并且希望隔离故障,可能会选择多进程;如果任务之间需要频繁交换信息或者状态,并且对性能有较高的要求,则多线程可能是更好的选择。
6、编写一个HelloWorld内核模块,并进行装载和卸载操作。
总结:
内核模块至少需要两个基本函数:一个是在模块装载时调用的初始化函数,另一个是在模块卸载时调用的清理函数。通过`module_init`和`module_exit`宏可以分别指定这些函数。使用`printk`函数可以将信息输出到内核日志中,这在调试内核模块时非常有用。Makefile的编写需要遵循内核模块编译的特定格式,正确指向内核源代码树的位置。`insmod`和`rmmod`是装载和卸载模块的关键命令,必须具备相应权限(通常是root权限)才能执行这些操作。`lsmod`命令用于验证模块是否已装载,而`dmesg`命令则用于检查模块的输出信息,这是验证模块正确性的重要步骤。
实验中遇到的问题以及解决:
在完成第三个实验内容时编译时发现了程序代码中存在一些问题,主要是关于线程的处理方式。
经过逐一分析:
错误点:线程函数中的 pthread_exit 的位置
在线程函数 thread 中,pthread_exit 被放置在了循环内部。这意味着在第一次循环结束时,线程就会退出,导致循环不会完成其余的迭代。
解决方法:将 pthread_exit 移到循环之外。
线程函数的参数和返回类型
线程函数的正确签名应该是 void* function_name(void *arg)。原本的线程函数 thread 使用了 void thread(void* arg) 形式,这可能导致在某些编译器或设置下出现问题。
解决方法:更改线程函数的签名为 void* thread(void* arg)。
七、思考题
1、多进程并发执行时各个进程的内存分配情况如何?如何监测其分配情况?
在多进程并发执行时,每个进程都有自己的独立的内存空间。操作系统为每个进程分配内存,并且管理这些内存。每个进程的内存空间包括代码段、数据段、堆和栈等部分。
在Unix/Linux系统中,可以使用top, htop, ps等工具查看进程的内存使用情况。还可以使用valgrind等工具进行内存泄漏检测。
2、多线程中数据是如何共享的?
在多线程中,同一个进程内的所有线程共享该进程的内存空间。这意味着线程之间可以共享全局变量、静态变量、堆上的数据等。但是,线程间的数据共享需要谨慎处理,因为多个线程可能同时访问和修改同一块内存,导致数据不一致或其他并发问题。为了解决这个问题,可以使用互斥锁、条件变量、读写锁等同步机制来确保数据的一致性和线程的安全性。
3、Linux系统下多进程与多线程中的区别是什么?
进程:进程是操作系统分配资源的最小单位。每个进程有自己的内存空间和系统资源。进程间的通信(IPC)需要通过特殊的机制(如管道、消息队列、共享内存等)。
线程:线程是操作系统调度的最小单位。同一个进程的线程共享该进程的内存空间和系统资源。线程间的通信更加简单,可以直接读写共享内存。
4、程序中的sleep(5)起什么作用?
sleep()是一个系统调用,它使调用它的进程暂停执行指定的时间,单位是秒。sleep(5)将使进程暂停执行5秒。这常常用于延迟执行、等待其他进程或简单地暂停一段时间。
5、子进程1和2为什么也能对管道进行操作?
当使用管道进行进程间通信时,通常创建一个管道,然后使用fork()创建子进程。这样子进程会继承父进程的管道文件描述符,因此子进程1和2都能够对管道进行操作。这是因为管道文件描述符在fork()之后被子进程继承,所以子进程可以读写管道,实现进程间通信。
6、在Linux内核源程序中,经常利用内核模块实现的功能有哪些?
设备驱动:为特定的硬件设备提供驱动支持。
文件系统:支持不同的文件系统,如ext4, NTFS, FAT等。
网络协议:实现新的或扩展现有的网络协议。
系统调用:添加新的系统调用来扩展操作系统的功能。
这些只是一些例子,实际上内核模块可以用于实现许多内核相关的功能,而不必修改内核的核心代码。这提供了很大的灵活性和扩展性。
谢谢大家的支持,码字不易,请随手点个赞叭(^-^)
相关文章:

Linux操作系统分析实验-多线程与内核模块编程,实验一
实验一 多线程与内核模块编程 一、实验目的 1、理解Linux下进程的结构; 2、理解Linux下产生新进程的方法(系统调用—fork函数); 3、掌握如何启动另一程序的执行; 4、理解Linux下线程的结构; 5、理解…...

AI绘画Stable Diffusion人物背景替换实操教程,让创意无限延伸
大家好,我是灵魂画师向阳 Stable Diffusion以其强大的能力可以实现人物背景的更换。本文将带你深入了解如何利用Stable Diffusion中的Inpaint Anything插件快速且精准地实现人物背景的替换,从而让你的图片焕发新生。 前期准备 本文会使用到Inpaint An…...

linux环境oracle11.2.0.4打补丁(p31537677_112040_Linux-x86-64.zip)
上传补丁及opatch工具 创建目录并上传opatch工具和补丁包 百度网盘地址 链接: https://pan.baidu.com/s/1EA5oerm52aesZUnb5OnQsA 提取码: ib8f [oraclerhel64 ~]$ mkdir /u01/psu [oraclerhel64 ~]$ cd /u01/psu [oraclerhel64 psu]$ ll total 514572 -rw-r--r-- 1 oracle o…...

华为 HCIP-Datacom H12-821 题库 (29)
🐣博客最下方微信公众号回复题库,领取题库和教学资源 🐤诚挚欢迎IT交流有兴趣的公众号回复交流群 🦘公众号会持续更新网络小知识😼 1.运行 IS-IS 的两台路由器,只有 IIH 发送的时间间隔相同才可以建立邻接关系 A、正…...
MySQL 为什么一定要有一个主键
引言 在数据库设计中,主键(Primary Key)是一个至关重要的概念。MySQL 作为最广泛使用的关系型数据库之一,主键在 MySQL 表设计中扮演着关键角色。主键不仅决定了表中数据的唯一性和完整性,还对数据库性能、数据查询、…...

Pycharm 本地搭建 stable-diffusion-webui
一、下载工程源码 Github官方连接 https://github.com/AUTOMATIC1111/stable-diffusion-webui 二、Pycharm打开工程 1、设置环境 文件-设置-项目-Python解析器-添加解释器-添加本地解释器 Conda环境-创造新环境-Python版本3.10 注意一定要选择Python3.10版本,否…...
python/爬虫技术/lxml工具介绍/XML和HTML解析
1.lxml介绍: lxml 是一个Python库,它提供了非常强大的XML和HTML解析功能。它基于libxml2和libxslt,是处理XML和HTML文档的首选库之一。 2.安装 首先,需要安装lxml库。可以通过pip来安装,在控制台内执行安装命令。 p…...
Kafka技术详解[5]: 集群启动
目录 集群启动 相关概念 代理:Broker 控制器:Controller 启动ZooKeeper 启动Kafka 初始化ZooKeeper 初始化服务 启动任务调度器 创建数据管理器 创建远程数据管理器 创建副本管理器 创建ZK元数据缓存 创建Broker通信对象 创建网络通信对象 注册Brok…...

记一次教学版内网渗透流程
信息收集 如果觉得文章写的不错可以共同交流 http://aertyxqdp1.target.yijinglab.com/dirsearch dirsearch -u "http://aertyxqdp1.target.yijinglab.com/"发现 http://aertyxqdp1.target.yijinglab.com/joomla/http://aertyxqdp1.target.yijinglab.com/phpMyA…...

[Python学习日记-31] Python 中的函数
[Python学习日记-31] Python 中的函数 简介 语法定义 函数的参数 简介 引子: 你是某公司的一个高级程序员,现在老板让你写一个监控程序,需要24小时全年无休的监控公司网站服务器的系统状况,当 CPU、Memory、Disk 等指标的使用…...

Kafak入门技术详解
抱歉,没有太多的时间进行详细校对 目录 一、Kafka简介 1.消息队列 1.1为什么需要消息队列 1.2消息队列 1.3消息队列的分类 1.4P2P和发布订阅MQ的比较 1.5消息系统的使用场景 1.6常见的消息系统 2.Kafka简介 2.1简介 2.2设计目标 2.3 kafka核心的概念 二…...
X-Spreadsheet:Web端Excel电子表格工具库
在数字化时代,数据管理与分析的重要性日益凸显。传统的电子表格软件如Microsoft Excel和Google Sheets在数据处理方面发挥着重要作用,但在Web端,一款名为X-Spreadsheet的工具库正以其独特的优势逐渐崭露头角。本文将详细介绍X-Spreadsheet&am…...

为什么很多APP取消网页版
厂商为了增加用户黏度把所有的内容都放在 APP 上,京东的网页也搜索不到东西了,就算看到东西要跳转过来还需要先登录一下。 对比亚马逊这类的其他的购物网站,基本上都是网页内容和 APP 的内容都是同步的,网页直接看也可以下单&…...

Kubernetes高级功能
资源配额 什么是资源配额 资源配额,通过 ResourceQuota 对象来定义,对每个命名空间的资源消耗总量提供限制。 它可以限制命名空间中某种类型的对象的总数目上限,也可以限制命名空间中的 Pod 可以使用的计算资源的总上限。 资源配额应用 创建的…...

(作业)第三期书生·浦语大模型实战营(十一卷王场)--书生入门岛通关第1关Linux 基础知识
关卡任务 闯关任务需要在关键步骤中截图: 任务描述 完成所需时间 闯关任务 完成SSH连接与端口映射并运行hello_world.py 10min 可选任务 1 将Linux基础命令在开发机上完成一遍 10min 可选任务 2 使用 VSCODE 远程连接开发机并创建一个conda环境 10min 可选任务 3 创…...
【python爬取网页信息并存储】
爬取网页信息并存储是一个常见的任务,通常涉及以下几个步骤: 发送HTTP请求:使用库如requests来发送HTTP请求获取网页内容。解析网页内容:使用库如BeautifulSoup或lxml来解析HTML内容,提取所需信息。存储数据ÿ…...

TCP、UDP
TCP和UDP的区别 是否面向连接:UDP 在传送数据之前不需要先建立连接。而 TCP 提供面向连接的服务,在传送数据之前必须先建立连接,数据传送结束后要释放连接。 是否是可靠传输:远地主机在收到 UDP 报文后,不需要给出任…...

聊聊暖通空调系统的优化控制方法
目录 暖通空调系统的优化控制方法✈️part1 初版回归网络建模✈️part2 更新的回归网络✈️ 聊聊暖通空调系统的优化控制方法 这篇文章简单分享一下暖通空调(HVAC)领域常常提到的”优化控制“这一概念指的是什么,它控制的是哪些参数&#…...
2024年合肥市职业院校技能大赛(中职组)赛 网络安任务书样题
2024年合肥市职业院校技能大赛--中职组赛 网络安任务书样题 一、竞赛项目简介:二、竞赛注意事项模块A: 理论技能与职业素养模块B: 网络安全事件响应、数字取证调查和应用安全任务一:应急响应任务二:操作系统取证任务三:网络数据包分析任务四:代码审计 模块C:CTF 夺旗…...

制造企业如何提升项目管理效率?惠科股份选择奥博思PowerProject项目管理系统
全球知名的显示方案综合服务商 - 惠科股份有限公司与北京奥博思达成合作,基于奥博思 PowerProject 搭建企业级项目管理平台。满足惠科多产品多业务领域的项目全周期管理。助力企业在技术研发、产品创新等方面继续取得行业领先优势。 同时,PowerProject …...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...

Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...

ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...