实验目的: 1.学会在操作系统安装之前,根据硬件配置情况,制定安装计划。 2.掌握多操作系统安装前,利用硬盘分区工具(如PQMagic、分区助手6.3等)为Linux准备分区。 3.掌握Linux操作系统的安装步骤。 4.掌握Linux系统的简单配置方法。 5.掌握Linux系统的启动、关闭步骤。 实验内容: 如图所示,已经使用VMware虚拟机正确在安装好linux系统。 掌握Linux一般命令格式 掌握有关文件和目录操作的常用命令 掌握有关进程操作的常用命令 熟练使用man命令 1. 正确地进入登出系统, |
学习使用vi 编辑器建立,编辑,显示及加工处理文本文件。
进入和退出vi
利用文本插入方式建立一个文件
在新建的文本文件上移动光标位置
对该文件执行删除,复原,修改,替换等操作
输入vim file.c进入文本编辑器,输入相应c++代码 |
Esc再输入:wq保存退出文件 输入sudo yum install gcc-c++下载c++ 输入g++ file.c -o file 无报错 再输入./file进行运行 |
输入man date >file10 |
X命令删除当前字,dd实现向后删除,u命令是还原操作,c,r,s命令都可以进入insert模式,/xxxx进行对于xxxx文字的检索,光标会跳转至检索文字的位置。 了解shell的特点和主要种类 掌握shell脚本的建立和执行方式 掌握bash的基本语法 学会编写shell脚本 #!/bin/bash # 1. 包含常用命令的脚本 echo "当前日期:" date echo "当前日历:" cal echo "当前工作目录:" pwd echo "当前目录内容:" ls # 2. 配置历史命令环境 echo "配置历史命令环境..." HISTSIZE=1000 HISTFILESIZE=2000 HISTCONTROL=ignoredups:erasedups HISTTIMEFORMAT="%F %T " # 3. 命令补齐功能 echo "启用命令补齐功能..." source /etc/bash_completion # 4. 定义别名 echo "定义别名..." alias ll='ls -al' alias grep='grep --color=auto' # 5. 编辑并执行自 shell 脚本 echo "编辑并执行自 shell 脚本..." vi myscript.sh chmod +x myscript.sh ./myscript.sh # 6. 按习题4.14要求编写脚本并执行 echo "按习题4.14要求编写脚本并执行..." vi myscript2.sh chmod +x myscript2.sh ./myscript2.sh # 7. 按习题4.18要求编写脚本并执行 echo "按习题4.18要求编写脚本并执行..." vi myscript3.sh chmod +x myscript3.sh ./myscript3.sh # 8. 运行例4.20的程序 echo "运行例4.20的程序..." vi example.sh chmod +x example.sh ./example.sh # 9. 测试取消"e"选项的情况 echo "测试取消'e'选项的情况..." set -e echo "这是一条测试语句" echo "如果前一条命令失败,这条语句将不会执行" echo "脚本执行完毕。" |
3、实验结果分析: (1)利用vi建立脚本文件ex1,运用三种方法执行脚本 |
(2)运行history命令,并配置历史命令环境,使bash保存500条历史命令 (3)利用tab键补全bash命令 (6)编辑脚本ex3,输入斐波那契数列前十项和 掌握Linux操作系统内核编译的方法,加深对Linux系统整体结构的理解。 编译内核的解决方案: 1安装编译内核所需的软件包 2下载源代码 3解压缩 4给内核打补丁 5配置内核 6编译内核 7安装内核 实验步骤 本实验将在Ubuntu 16下动手编译并安装一个新的Linux内核。该发行版的下载地址为 http://mirrors.aliyun.com/ubuntu-releases/16.04/ubuntu-16.04.7-server-amd64.iso 用uname –r命令,可以查看当前内核的版本号 使用root用户登录,修改/etc/ssh/sshd_config文件,把 PermitRootLogin without-password 改为PermitRootLogin Yes 保存退出,重启。 这样可远程以root用户身份登录ubuntu系统 1安装编译内核所需的软件包。 apt-get update apt-get install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison 2下载源代码 Linux内核源代码可以从官方网站The Linux Kernel Archives中下载。本实验,选择国内镜像网站下载。 wget http://mirrors.aliyun.com/linux-kernel/v5.x/linux-5.16.12.tar.xz 3解压缩 xz -d l linux-5.16.12.tar.xz tar -xvf linux-5.16.12.tar cp linux-5.16.12 /usr/src -rf 4配置内核 cp -v /usr/src/linux-headers-$(uname -r)/.config /usr/src/linux-5.16.12 cd /usr/src/linux-5.16.12/ 运行命令make menuconfig,保存退出 使用文本编辑器,修改Makefile文件,将版本号中的第3位改为学生学号的后3位(例如023),例如 5编译内核 cd /usr/src/linux-5.16.12/ nohup make >xxx 2>&1 & 上述命令是在后台运行,进程结束后,运行结果保存在xxx文件中。 在后台运行时,会出现该进程的PID值。 可以用ps –ef命令查看PID在不在了,如果不在了,就表明编译结束了 也可以在前台执行如下命令 make 无论是在前台编译,还是在后台编译,整个编译过程需要花费较多时间,机器性能不同,编译时间也不同。在一台双核的机器上,可以使用make –j4,使得make最多允许4条编译命令同时执行。在一台4核的机器上,可以使用make –j8,使得make最多允许8条编译命令同时执行,从而更有效的利用CPU资源。 7 安装内核 cd /usr/src/linux-5.16.12 make modules_install make install 重启动之后,用uname –r命令查看内核当前的版本号,可以看到目前Ubuntu的内核已经更换为新的内核版本了。 1.更新软件源并安装所需软件包 sudo yum groupinstall "Development Tools" sudo yum install ncurses-devel elfutils-libelf-devel openssl-devel bc 2. 下载最新稳定版Linux内核源码 wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.16.18.tar.xz3. 3.解压内核源码 xz -d linux-5.16.18.tar.xz tar -xvf linux-5.16.18.tar 4复制源码到内核源码目录: sudo cp -r linux-5.16.18 /usr/src/kernels 5. 进入内核源码目录并复制配置文件: cd /usr/src/kernels/linux-5.16.18 cp /boot/config-$(uname -r) .config 在CentOS 7上安装ncurses开发库的命令: sudo yum install ncurses-devel 安装更新的软件源: sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm 安装GCC最新版本: sudo yum install gcc gcc-c++ 安装CentOS软件集合(SCL)源: sudo yum install centos-release-scl 安装devtoolset-9软件包,它包含了GCC 9.3.1版本: sudo yum install devtoolset-9 启用GCC 9.3.1: sudo scl enable devtoolset-9 bash 5.切换到内核源代码目录,并使用新版GCC重新编译: cd /usr/src/kernels/linux-5.16.18 make clean make menuconfig 缺少libelf开发库 sudo yum install elfutils-libelf-devel 缺少OpenSSL开发库 sudo yum install openssl-devel 6. 修改内核版本号(将最后三位改为你的学号,如2130110259): 7. 开始编译内核(并行任务数可根据CPU数调整): make bzImage -j4 8. 安装模块和内核: make modules_install sudo make install 9. 配置内核引导项: sudo grub2-mkconfig -o /boot/grub2/grub.cfg 10. 重启系统并检查内核版本: reboot uname -r |
- 掌握 C 语言编译的基本用法。
- 掌握 gdb 调试工具的基本用法。
- 理解 make 工具的功能,学会编制 makefile 的方法。
- 利用 gcc 编译 C 语言程序,使用不同选项,观察并分析显示结果。
- 用 gdb 调试一个编译后的 C 语言程序。
- 编写一个由多个文件构成的 C 语言程序,编制 makefile,运行 make 工具进行维护。
Linux系统大致可分为三层:
靠近硬件的底层是内核,即Linux操作系统常驻内存部分。
中间层是内核之外的shell层,即操作系统的系统程序部分。
最高层是应用层,即用户程序部分
从结构上看,Linux操作系统是采用单块结构的操作系统。
一般说来,可以将操作系统划分为内核和系统程序两部分。
●进程控制系统用于进程管理、进程同步、进程通信、进程调度和内存管理等。
●内存管理控制内存分配与回收。
●文件系统管理文件、分配文件空间、管理空闲空间、控制对文件的访问并为用户检索数据。
●Linux系统支持三种类型的硬件设备:字符设备、块设备和网络设备。
●核心底层的硬件控制负责处理中断以及与机器通信。
Linux的各个发行版本(distribution),都是使用Linus主导开发并发布的同一个Linux内核,因此在内核层不存在什么兼容性问题。每个版本都不一样的感觉,只是在发行版本的最外层才有所体现,而绝不是Linux本身特别是内核不统一或是不兼容。SUSE、RedHat、Ubuntu、Slackware等直接说成是Linux是不确切的,它们是Linux的发行版本,更确切地说,应该叫做“以Linux为核心的操作系统软件包”。
1. 利用-I搜索头文件路径,利用-D声明DOPTION,之后编译运行hello.c #include <stdio.h> #define fatal "please call Lawrence for help" int main() { /* testing CPP option */ printf("display -D variable %s\n", "testing -D "); printf("display overwrite fatal=%s\n", fatal); printf("Hello, everybody!!\n"); return 0; } 2. 初步尝试编译时产生问题,进入gdb调试后发现p只分配了30字节内存而后续访问p[50],导致内存越界访问,同时在释放p后再次尝试访问p[0],修改代码后再次编译运行 #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char **argv) { char *p; if ((p = malloc(31 * sizeof(char))) == NULL) { fprintf(stderr, "Memory allocation failed\n"); return 1; } strncpy(p, "not 30 bytes", 30); p[30] = '\0'; printf("p=<%s>\n", p); if (argc == 2) { if (strcmp(argv[1], "-b") == 0) { if (strlen(p) < 29) { p[strlen(p)] = 'a'; p[strlen(p) + 1] = '\0'; } } else if (strcmp(argv[1], "-f") == 0) { free(p); p = NULL; } } if (p != NULL) { free(p); } return 0; } 3. 编译后进入gdb,尝试运行成功 #include <stdio.h> #include <stdlib.h> int make_key(void); int get_key_num(void); int number(void); int main(void) { int ret = make_key(); printf("make_key returns %d\n", ret); exit(EXIT_SUCCESS); return 0; // 可选,因为 exit 已经结束了程序 } int make_key(void) { int ret = get_key_num(); return ret; } int get_key_num(void) { int ret = number(); return ret; } int number(void) { return 10; // 返回一个具体的数值 } 4. 编写当前目录下makefile文件,建立需要的a.c b.c c.c d.c assmb.s,之后在终端进行make指令,ls查看当前目录下成功生成proc文件,最后使用make clean指令清除编译过程中产生的.o文件。 Makefile文件 # 定义对象文件变量,以.c和.s后缀的源文件名作为键 OBJECTS = a.o b.o c.o d.o assmb.o # 定义默认的目标(可执行文件) prog: $(OBJECTS) gcc -o prog $(OBJECTS) -L/home/user/lib -lm # 规则来生成C语言源文件的对象文件 %.o: %.c defs.h gcc -c $< -o $@ # 规则来生成汇编语言源文件的对象文件 %.o: %.s as $< -o $@ # 清理生成的对象文件 clean: rm -f $(OBJECTS) prog # 默认的目标(当执行 make 时没有指定目标时) .PHONY: all clean all: prog 5. /* main.c */ #include <stdlib.h> #include "a.h" extern void function_two(); extern void function_three(); int main() { function_two(); function_three(); exit (EXIT_SUCCESS); } 接着,用vi编辑器创建文件2.c /* 2.c */ #include "a.h" #include "b.h" void function_two() { } 然后,用vi编辑器创建文件3.c /* 3.c */ #include "b.h" #include "c.h" void function_three() { } 头文件实际上都是空文件,可以用touch命令来创建它们: $ touch a.h $ touch b.h $ touch c.h 用vi编辑器创建如下文件,名为Makefile1 myapp: main.o 2.o 3.o gcc -o myapp main.o 2.o 3.o main.o: main.c a.h gcc -c main.c 2.o: 2.c a.h b.h gcc -c 2.c 3.o: 3.c b.h c.h gcc -c 3.c
|
3、实验结果分析:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #define BUFFER_SIZE 4096 /** * Copy the contents of the source file to the destination file. * @param source_file Path to the source file. * @param dest_file Path to the destination file. * @return 0 on success, -1 on failure. */ int copy_file(const char *source_file, const char *dest_file) { int source_fd = open(source_file, O_RDONLY); if (source_fd == -1) { fprintf(stderr, "Failed to open source file: %s\n", source_file); return -1; } int dest_fd = open(dest_file, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (dest_fd == -1) { fprintf(stderr, "Failed to open destination file: %s\n", dest_file); close(source_fd); return -1; } char buffer[BUFFER_SIZE]; ssize_t bytes_read, bytes_written; while ((bytes_read = read(source_fd, buffer, BUFFER_SIZE)) > 0) { bytes_written = write(dest_fd, buffer, bytes_read); if (bytes_written != bytes_read) { fprintf(stderr, "Failed to write to destination file: %s\n", dest_file); close(source_fd); close(dest_fd); return -1; } } if (bytes_read == -1) { fprintf(stderr, "Failed to read from source file: %s\n", source_file); close(source_fd); close(dest_fd); return -1; } close(source_fd); close(dest_fd); return 0; } int main(int argc, char *argv[]) { if (argc != 3) { fprintf(stderr, "Usage: %s <source_file> <destination_file>\n", argv[0]); return EXIT_FAILURE; } if (copy_file(argv[1], argv[2]) != 0) { return EXIT_FAILURE; } return EXIT_SUCCESS; } 二:先在父进程中创建子进程,父进程打印自己PID和子进程PID,随后等待直到子进程自己销毁,子进程打印自己和其父PID并输入当前在子进程中,之后销毁回到父进程,输入当前所在父进程中 #include <stdio.h> 三:先在程序中创建一个管道,利用write函数写入数据到管道中,之后利用read函数读取管道内容 /* pipedemo.c */ #include <unistd.h> #include <stdio.h> #include <stdlib.h> // 包含exit函数的声明 #include <string.h> // 包含strlen函数的声明 #include <errno.h> int main(int argc, char **argv) { static const char mesg[] = "Happy New Years to you!"; char buf[BUFSIZ]; size_t rcount, wcount; // 声明wcount int p_fd[2]; size_t n; if (pipe(p_fd) < 0) { fprintf(stderr, "%s: pipe failed: %s\n", argv[0], strerror(errno)); exit(1); } printf("Read end=fd %d, write end=fd %d\n", p_fd[0], p_fd[1]); n = strlen(mesg); if ((wcount = write(p_fd[1], mesg, n)) != n) { fprintf(stderr, "%s: write failed: %s\n", argv[0], strerror(errno)); exit(1); } if ((rcount = read(p_fd[0], buf, BUFSIZ)) != wcount) { fprintf(stderr, "%s: read failed: %s\n", argv[0], strerror(errno)); exit(1); } buf[rcount] = '\0'; printf("Read <%s> from pipe\n", buf); close(p_fd[0]); close(p_fd[1]); return 0; } 四:首先创建一个队列,输入获取的消息,把消息加到队列中,之后利用msgctl函数提取队列显示消息 #include <unistd.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define MSGTYPE 1 struct msg { long type; char text[BUFSIZ]; }; int main(void) { int qid; key_t key; int msgid; struct msg msg_snd, msg_rcv; char *hello = "Hello, World!"; key = ftok("somefile", 'a'); if (key == -1) { perror("ftok failed"); exit(EXIT_FAILURE); } msgid = msgget(key, 0666 | IPC_CREAT); if (msgid == -1) { perror("msgget failed"); exit(EXIT_FAILURE); } msg_snd.type = MSGTYPE; strcpy(msg_snd.text, hello); if (msgsnd(msgid, &msg_snd, strlen(msg_snd.text), 0) == -1) { perror("msgsnd failed"); exit(EXIT_FAILURE); } if (msgrcv(msgid, &msg_rcv, sizeof(msg_rcv.text), MSGTYPE, 0) == -1) { perror("msgrcv failed"); exit(EXIT_FAILURE); } printf("Received message of type: %ld\n", msg_rcv.type); printf("Message text: %s\n", msg_rcv.text); if (msgctl(msgid, IPC_RMID, NULL) == -1) { perror("msgctl(IPC_RMID) failed"); exit(EXIT_FAILURE); } return EXIT_SUCCESS; } 五:先创建一个子进程,输出一行信息表示父进程向子进程发送信号,随后父进程进入等待,子进程接收信号,输出自己的PID和其父的PID,之后终止自己进程,回到父进程结束程序 #include <stdio.h> |
了解Linux线程管理方面的知识,掌握线程创建的用法。
实验背景知识
线程是操作系统能够调度和执行的基本单位,在Linux中也被称之为轻量级进程,LWP:light weight process 轻量级的进程,本质仍是进程(在Linux环境下)。
进程:独立地址空间,拥有PCB
线程:也有PCB,但没有独立的地址空间(共享)
区别:在于是否共享地址空间。 独居(进程);合租(线程)。
Linux下: 线程:最小的执行单位,调度的基本单位。
进程:最小分配资源单位,可看成是只有一个线程的进程。
类Unix系统中,早期是没有“线程”概念的,80年代才引入,借助进程机制实现出了线程的概念。因此在这类系统中,进程和线程关系密切。
轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都是clone。从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三级页表是相同的。进程可以蜕变成线程,线程可看做寄存器和栈的集合,在linux下,线程最是小的执行单位;进程是最小的分配资源单位。对于进程来说,相同的地址(同一个虚拟地址)在不同的进程中,反复使用而不冲突。原因是他们虽虚拟址一样,但,页目录、页表、物理页面各不相同。相同的虚拟址,映射到不同的物理页面内存单元,最终访问不同的物理页面。但线程不同,两个线程具有各自独立的PCB,但共享同一个页目录,也就共享同一个页表和物理页面。所以两个PCB共享一个地址空间。
实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内核函数clone。如果复制对方的地址空间,那么就产出一个“进程”;如果共享对方的地址空间,就产生一个“线程”。因此:Linux内核是不区分进程和线程的。只在用户层面上进行区分。所以,线程所有操作函数 pthread_* 是库函数,而非系统调用。
pthread_create函数:创建一个新线程。其作用,对应进程中fork() 函数。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
返回值:成功:0; 失败:错误号 -----Linux环境下,所有线程特点,失败均直接返回错误号。
参数:
pthread_t:当前Linux中可理解为:typedef unsigned long int pthread_t;
参数1:传出参数,保存系统为我们分配好的线程ID
参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
参数4:线程主函数执行期间所使用的参数,如要传多个参数, 可以用结构封装。
pthread_join函数 阻塞等待线程退出,获取线程退出状态,其作用对应进程中 waitpid() 函数。
int pthread_join(pthread_t thread, void **retval); 成功:0;失败:错误号
参数:thread:线程ID
retval:存储线程结束状态。
调用该函数的线程将挂起等待,直到id为thread的线程终止。
thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。
如果thread线程被别的线程调用pthread_cancel异常终止掉,retval所指向的单元里存放的是常数PTHREAD_CANCELED。
如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。
如果对thread线程的终止状态不感兴趣,可以传NULL给retval参数。
- 理解系统管理的内涵和作用。
- 学会对用户和组进行一般管理。
- 学会在Linux环境下配置邮件环境。
- 学会网络配置的一般方法。
为新用户建立账号和工作组,删除本地用户和组。
在U盘上建立文件系统,并进行安装。
在Linux环境下收发邮件。
配置网络。
主要实验步骤
1.分别以普通用户和 root 身份登录,看能否建立新用户账号
2.为新用户(如 Zhang)建立账号和工作组,并进行相应配置。以该用户身份登录,修改密码等。最后删除该用户
编写shell脚本,批量创建和删除用户。编写一个shell脚本myadduser.sh,完成下列功能,创建30个用户stu001,stu002,..,stu030,初始密码设置分别为001,002,…,030。
编写一个批量删除用户的脚本mydeluser.sh,完成以下功能,把用户名stu001,…,stu020的这20个用户删除。
3.在U盘上建立文件系统,并进行安装。
4.配置网络信息,浏览校园网信息。
5.配置mail环境,发送和接收邮件。
3、实验结果分析: 一:创建threadtest.c,利用Linux系统使用 #include <pthread.h> #include <stdio.h> void *create(void *arg){ printf("new thread created ..."); } int main(int argc,char *argv[]) { pthread_t tidp; int error; error=pthread_create(&tidp,NULL,create,NULL); if(error !=0) { printf("pthread_create is not created..."); return -1; } printf("pthread_create is created..."); } 因为pthread库不是Linux系统的库,所以在编译的时候要加上-lpthread,否则编译通不过。 二:创建文件thread11.c,利用pthread库创建一个新线程,并打印出主线程和新线程的线程ID和进程ID pthread_self函数的作用是获取线程ID,其作用对应进程中 getpid() 函数。验证thread11.c 程序 #include <stdio.h> #include <pthread.h> #include <unistd.h> void *create(void *arg){ printf("new thread...\n"); printf("thread_tid == %u\n",(unsigned int )pthread_self()); printf("thread_pid is %d\n",getpid()); return (void *)0; } int main(int arg,char *argv[]){ pthread_t tid; int error; printf("Main thread is starting...\n"); error = pthread_create(&tid,NULL,create,NULL); if(error!=0){ printf("thread is not created..."); return -1; } printf("main pid is %d\n ",getpid()); sleep(1); return 0; } 三:创建文件thread3.c,利用pthread库创建一个新线程,并使用全局变量var来演示线程之间的数据共享和竞争条件。 #include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h> int var = 100; void *tfn(void *arg) { var = 200; printf("thread\n"); return NULL; } int main(void) { printf("At first var = %d\n", var); pthread_t tid; pthread_create(&tid, NULL, tfn, NULL); sleep(1); printf("after pthread_create, var = %d\n", var); return 0; } 四:创建文件thread4.c,利用pthread库创建一个新线程,并使用一个全局字符串 pthread_join的用法,验证如下程序thread4.c #include<stdio.h> #include<stdlib.h> #include<string.h> void *thread_function(void *arg); char message[] = "Hello World"; int main() { int res; pthread_t a_thread; void *thread_result; res = pthread_create(&a_thread, NULL, thread_function, (void *)message); if (res != 0) { perror("Thread creation failed!"); exit(EXIT_FAILURE); } printf("Waiting for thread to finish...\n"); res = pthread_join(a_thread, &thread_result); if (res != 0) { perror("Thread join failed!/n"); exit(EXIT_FAILURE); } printf("Thread joined, it returned %s\n", (char *)thread_result); printf("Message is now %s\n", message); exit(EXIT_FAILURE); } void *thread_function(void *arg) { printf("thread_function is running. Argument was %s\n", (char *)arg); sleep(3); strcpy(message, "Bye!"); pthread_exit("Thank you for your CPU time!"); } 五:创建文件thread5.c,利用pthread库创建两个新线程,并让它们分别打印出传递给它们的字符串。 更多的pthread_join的例子,预测它的运行结果thread5.c。 #include <stdio.h> #include <stdlib.h> #include <pthread.h> void *print_message_function( void *ptr ); main() { pthread_t thread1, thread2; char *message1 = "Thread 1"; char *message2 = "Thread 2"; int iret1, iret2; /* Create independent threads each of which will execute function */ iret1 = pthread_create( &thread1, NULL, print_message_function, (void*) message1); iret2 = pthread_create( &thread2, NULL, print_message_function, (void*) message2); /* Wait till threads are complete before main continues. Unless we */ /* wait we run the risk of executing an exit which will terminate */ /* the process and all threads before the threads have completed. */ pthread_join( thread1, NULL); pthread_join( thread2, NULL); printf("Thread 1 returns: %d\n",iret1); printf("Thread 2 returns: %d\n",iret2); exit(0); } void *print_message_function( void *ptr ) { char *message; message = (char *) ptr; printf("%s \n", message); } 六创建文件thread6.c,创建三个线程 A、B 和 C,并设置了一个全局的互斥锁和条件变量。每个线程在打印之前都会等待条件变量,主线程负责按顺序通知这些条件变量,从而控制打印顺序为ABCABC……。 编程thread6.c,创建3个线程。假如三个线程的ID分别为A,B,C。每个线程把自己的ID在屏幕上打印10遍,要求输出结果必须按照ABC的顺序显示,例如ABCABC…..。 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #define NUM_THREADS 3 pthread_mutex_t mutex; pthread_cond_t cond; int current_thread = 0; void *thread_function(void *arg) { char *thread_id = (char *)arg; int i; for (i = 0; i < 10; i++) { pthread_mutex_lock(&mutex); while (current_thread != atoi(thread_id)) { pthread_cond_wait(&cond, &mutex); } printf("%s", thread_id); fflush(stdout); current_thread = (current_thread + 1) % NUM_THREADS; pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mutex); } return NULL; } int main() { pthread_t threads[NUM_THREADS]; char *thread_ids[] = {"0", "1", "2"}; int i; pthread_mutex_init(&mutex, NULL); pthread_cond_init(&cond, NULL); for (i = 0; i < NUM_THREADS; i++) { pthread_create(&threads[i], NULL, thread_function, thread_ids[i]); } for (i = 0; i < NUM_THREADS; i++) { pthread_join(threads[i], NULL); } pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); return 0; } |
3、实验结果分析: 一:分别以普通用户和 root 身份登录,看能否建立新用户账号。 以普通用户身份登录: 普通用户通常没有权限直接添加新用户账号到系统。尝试使用如 useradd 或 adduser 命令时,会收到权限错误,例如: useradd newuser -bash: useradd: 权限不够 如果尝试使用 sudo 命令(前提是普通用户已被赋予执行特定命令的sudo权限),则可能成功创建新用户账号: sudo useradd newuser 以 root 用户身份登录: 作为 root 用户,你拥有系统中最高的权限,可以执行任何操作,包括添加新用户账号。使用 useradd 或 adduser 命令将不会有权限问题: useradd newuser 你还可以设置密码,为新用户配置其他选项等。 二:为新用户(如 Zhang)建立账号和工作组,并进行相应配置。以该用户身份登录,修改密码等。最后删除该用户。 编写shell脚本,批量创建和删除用户。编写一个shell脚本myadduser.sh,完成下列功能,创建30个用户stu001,stu002,..,stu030,初始密码设置分别为001,002,…,030。 编写一个批量删除用户的脚本mydeluser.sh,完成以下功能,把用户名stu001,…,stu020的这20个用户删除。 三:在U盘上建立文件系统,并进行安装。 四:配置网络信息,浏览校园网信息。 五:配置mail环境,发送和接收邮件。 sudo apt install evolution |