1、创建用户,以自己的姓名缩写创建用户名,并设置密码; useradd mingzi
passwd mingzi 敲回车输入密码;
2、显示当前系统进程列表;结束gnome-screensaver并显示效果; LL 显示当前文件,LS -L 一行输出一个文件; kill -9 +进程号 杀死一个进程; 3、查看磁盘分区情况; fdisk -l;
4、切换当前用户为David并携带环境变量,并查看当前目录和文件信息;
su - David 切换用户,并携带环境变量。 pwd 查看当前目录; ll 查看文件信息
5、在David目录下创建多级目录 hello/my,并查看当前目录路径; mkdir -p ./hello/my创建多级目录;
6、在my目录下继续创建子目录why,设置目录访问权限777; mkdir -m 777 ./why 7、使用cat将键盘输入的字符传到单个文件abc中,依此方法创建另一个文件def; 将abc的内容覆盖def; 将abc的内容追加到def; 将def的内容显示在shell中; 将def的内容按行号显示; 将def的内容逆序显示;
cat >abc 回车123456 ctrl +z 创建文件abc 并向里面输入123456内容;
more abc 查看文件里面的内容;
cat abc>def用 abc 里面的内容覆盖def里面的内容; cat abc>>def将abc 里面的内容追加到def中; cat< def 在屏幕上输出def里面的内容; cat -n def 带行号的显示def里面的内容; tac def 逆序显示;
8、将def文件拷贝到上级目录;并重命名为d; 将d文件剪切回my目录; 将my目录拷贝到hello目录中; 将hello目录中拷贝的my目录强制删除;
cp def ../将def文件复制到上一级目录;mv def d 重命名为d; mv -i ./home/David/hello/d /home/David/hello/my 移动,剪回; cp -rf 复制 ;rm -rf my 删除;
1
9、将abc的文件所有者和用户组都改为自己姓名对应的组名; 将abc的文件所有者权限设置为r--,其他权限设置为r-wr--; chown xingming abc 修改文件所有者和组名 chgrp xingming abc 改变文件的组所有权
chmod 464 abc 第一位是创建者,第二位是同组,第三位是其他组 (见书上28页)
10、使用grep和管道只显示gnome-terminal进程;
11、在/在使用find按照文件名查找abc文件; 使用locate快速查找abc文件;
find -name abc locate abc
12、将abc文件压缩,然后解压; tar -zcvf abc.tar.gz abc 压缩 tar -zxvf abc.tar.gz abc 压缩
13、将David目录进行打包;然后解压linux-2.6.18-tar.bz2文件(在第2章页面中可下载) 如果解压*.tar.gz用什么命令; tar -jcvf linux-2.6.18-tar.bz2 linux 压缩 tar -jxvf linux-2.6.18-tar.bz2 linux 解压
回答问题:
1、解释X-window是什么?
X-window是一个图形化得显示界面,在linux里面以一个图形化显示软件存在。
2、普通用户和超级用户区别何在?
普通用户与超级用户的区别在对文件的操作权限上,超级用户可以给普通用户开通相应的操作权限,如果超级用户不给普通用户相应的权限,普通用户则无法执行相应功能。
2
3、解释chmod命令的作用,并说明目录访问权限776是什么意思?
chmod是更改拥有者对文件的执行权限的命令,7的意思是r+w+x=7,其中r=4,w=2,x=1,776代表着,拥有者及拥有者所在的小组内成员对问价拥有读写执行的权限,而小组外的成员对文件仅能进行读写,不能进习执行权限。
4、解释ln的作用以及包括的类型;
它的功能是为某一个文件在另外一个位置建立一个同步的链接.当我们需要在不同的目录,用到相同的文件时,我们不需要在每一个需要的目录下都放一个必须相同的文件,我们只要在某个固定的目录,放上该文件,然后在 其它的目录下用ln命令链接它就可以,不必重复的占用磁盘空间
5、解释什么是用户登录模式;
用户登录模式是root账户下的子账户登录,往往我们用root账户来修改系统,用用户账户来使用系统,即root拥有无限大的权利,可以杀死自己,而用户账户则可以在一定约束下使用系统。 6、解释如何修改系统服务自启动的级别?
如果需要自启动某些服务,只需使用chkconfig 服务名 on即可,若想关闭,将on改为off在默认情况下,chkconfig会自启动2345这四个级别,如果想自定义可以加上--level选项我们先将sshd服务的所有启动级别关闭,然后使用--level选项启动自定义级别。
3
第三章 1.vi
(1)在“/root”目录下建一个名为“vi”的目录。
(2)进入“vi”目录。
(3)将文件“/etc/inittab”复制到“vi”目录下。
(4)使用 vi 打开“vi”目录下的 inittab。 vi inittab;
(5)设定行号,指出设定 initdefault(类似于“id:5:initdefault” 的所在行号。)
在特权模式下输入: set nu;set nonu,取消行号; / initdefault 在光标之后查找名为initdefault 的字符串; ?initdefault 在光标之前。(在第十八行找到字符串) (6)将光标移到该行。 18 G 光标移动到十八行 (7)复制该行内容。
插入模式下 1yy o esc模式下 p 粘贴 nyy 复制光标所在行的向下的n行;
4
(8)将光标移到最后一行行首。 ESC 直接输入G最后一行行首 ESC 直接输入gg移动到文件开头 (9)粘贴复制行的内容。
(10)撤消第 9 步的动作。 ESC (u恢复前一个动作)
(11)将光标移动到最后一行的行尾。 特权模式下使用G $光标所在行的尾部;
(13)光标移到“si::sysinit:/etc/rc.d/rc.sysinit 行号G (14)删除该行。(dd) (15)存盘但不退出。 :w (16)将光标移到首行。gg
(19)向下查找字符串“0:wait”。(/0:wait) (20)再向上查找字符串“halt”。(?halt) (21)强制退出 vi,不存盘 :q!
2.用gdb调试程序的Bug
(1)在工作目录上新建文件 greet.c,并用 vi 启动:vi greet.c。 (2)在 vi 中输入以上代码。
(3)在 vi 中保存并退出,使用命令“:wq”。 (4)用 gcc 编译:gcc -g greet.c -o greet。
5
(5)运行 greet,使用命令“./greet” 输出为: The original string is Embedded Linux The string afterward is
(6)启动 gdb 调试:gdb greet。 (7)查看源代码,使用命令“l”。
(8)在 20 行(for 循环处)设置断点,使用命令“b 20”。 (10)查看断点设置情况,使用命令“info b”。
(11)运行代码,使用命令“r”。(运行到了第一个断点处) (12)单步运行代码,使用命令“n”。(运行完一次for循环) s进入函数体单步运行
(13)查看暂停点变量值,使用命令p string2[size - i] 有可能是 ASCII值 (15)继续程序的运行,使用命令“c”。 (16)程序在 printf 前停止运行, 此时依次查看 string2[0]、string2[1]…, 发现 string[0]没有被正确赋值,而后面的赋值都是正确的,这时,定位程序第 31 行,发现程序运行结果错误的原因在于“size-1” 于 i 只能增到“size-1” 这样 string2[0]就永远不能被赋值而保持 NULL,故不能输出任何结果。 (17)退出 gdb,使用命令“q”。
(18)重新编辑 greet.c,把其中的“string2[size - i] = string1[i]”改为“string2[size –i - 1] = string1[i];”即可。
(19)使用 gcc 重新编译:gcc -g greet.c -o greet。 (20)查看运行结果:./greet
The original string is Embedded Linux The string afterward is xuniL deddedbmE 这时,输出结果正确。
3. 编写包含多文件的 makefile
(1)用 vi 打开上述两个代码文件“hello.c”和“hello.h”。 (2)在 shell 命令行中用 gcc 尝试编译,使用命令:\" gcc hello.c –o hello” 并运行,./hello 可执行文件查看结果。
(3)删除此次编译的可执行文件:rm hello。
(4)用 vi 编辑 makefile,如下所示: hello:hello.c hello.h
(一定是一个制表位的空档) gcc hello.c -o hello (5)退出保存,在 shell 中键入:make,查看结果。
(6)再次用 vi 打开 makefile,用变量进行替换,如下所示: OBJS :=hello.o CC :=gcc
hello:$(OBJS) $(CC) $^ -o $@
(7)退出保存,在 shell 中键入 make,查看结果。
6
(8)用 vi 编辑 makefile1,如下所示: hello:hello.o
gcc hello.o -o hello hello.o:hello.c hello.h gcc -c hello.c -o hello.o
(9)退出保存,在 shell 中键入:make -f makefile1,查看结果。
(10)再次用 vi 编辑 makefile1,如下所示: OBJS1 :=hello.o
OBJS2 :=hello.c hello.h CC :=gcc
hello:$(OBJS1) $(CC) $^ -o $@ $(OBJS1):$(OBJS2) $(CC) -c $< -o $@
在这里请注意区别“$^”和“$<”。
(11)退出保存,在 shell 中键入 make -f makefile1,查看结果。
4. 使用 autotools 生成包含多文件的 makefile
(1)在原目录下新建文件夹 auto。
(2)将上例的两个代码文件“hello.c”和“hello.h”复制到该目录下。 (3)使用 autoscan 生成 configure.scan。
(4)编辑 configure.scan,修改相关内容,并将其重命名为
7
configure.in。
(5)使用 aclocal 生成 aclocal.m4。
(6)使用 autoconf 生成 configure。
(7)使用 autoheader 生成 config.h.in。
(8)编辑 makefile.am。
(9)使用 automake 生成 makefile.in。
8
(10)使用 configure 生成 makefile。
(11)使用 make 生成 hello 可执行文件,并在当前目录下运行 hello 查看结果。
(12)使用 make install 将 hello 安装到系统目录下,并运行,查看结果。
(13)使用 make dist 生成 hello 压缩包。
9
(14)解压 hello 压缩包。 (15)进入解压目录。
(16)在该目录下安装 hello 软件。
5. 根据已知三个源文件编写静态库和动态库,并调试运行出正确结果; 静态库
10
动态库
11
错
误
:
错误提示,找不到动态库文件libadd.so。程序在运行时,会在/san/ku 和/ku
等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。我们将文件 libadd.so复制到目录/ku 中,再试试。
6. 有一个计算阶乘的源程序,可以运行出结果,但是结果不对,请使用gdb找出错误并改正错误。
12
运行结果: 下面进行调试:
错误.
分别在第6,7,9行设置了断点:
然后进行单步调试,发现问题出现在断点9处出现问题,J的值出现错误:
修改结果:
13
7. 实现汉诺塔游戏,要求:汉诺塔的层数由键盘输入,最少3层,程序执行结果要把移动盘子的步骤显示出来。(gcc和gdb综合应用)
14
第六章:linux下文件I/O编程
1、根据视频改写copy_file.c,以只写方式打开目标文件,权限742;将源文件读写指针移动到距离文件开头5KB处,每次读写2KB,并用自己的话描述读写函数功能;
15
16
2、fcntl()给文件上锁需要执行几次,为什么?回答fcntl()实例之后的思考题(如果在一个终端上运行设置读取锁的程序,则在另一个终端上运行设置写入锁的程序,会有什么结果?),并将hello文件同时加读锁和写锁。
一共两次,第一次执行fcntl函数,在上锁前必须要进行判断是否能够上锁,当能够上锁再执行上锁的功能,返回值是-1,说明未上锁,第二次执行fcntl函数时会返回值为0,上锁成功。
在另一个终端上可以进行正常写入锁,且与读取锁的进程号相等。
17
可以通过对一个文件的部分进行读锁,对另一部分进行写锁,从而达到对文件的同时上锁和写锁。
3、关于multiplex_select.c源程序问题: (1)什么是I/O多路复用,用自己的话描述? 让多个设备可以同时工作,从而提高效率。 (2)如何判断文件描述符在fd_set中? 通过FD_ISSET语句判断
(3)如何让程序执行case 0: 分支语句?
一定时间内,不对文件进行任何写操作,在固定监控时间内没有接收到写入相关文件里数据。
(4)文件描述符集inset的备份:tmp_inset,其在本程序当中的作用是什么?
通过改变其值,.存放检测到的文件描述符,从而判断输入数据的通道但每一次都要重新赋值,否则以后将不会监听到所有文件。 (5)select()函数到底是做什么用的?
用来监测键盘通过哪个通道输入数据,有数据输入的会被他监控
18
到,会把写入的数据写到目标程序上。 4、标准I/O函数和系统调用函数有什么区别? 标准I/O函数是带缓存的,系统调用函数是不带缓存的.
5、有关生产者程序:./producer 1 20,代表每隔一秒生产一个字符,总共生产20个字符;消费者程序./customer 5代表消费5个字符;
19
在执行生产者程序生产第10个字符时,在另一个终端同时执行消费者程序,请问消费者为什么能消费abcde5个字符?因为生产者生产字符时是给myfifo文件上写锁的,而写锁是排他锁,它锁住时,其他进程是不能加锁的。所以在生产者还未生产完字符时,消费者应该消费不了字符,为什么实际执行时能消费字符???
因为默认循环生产10个字符,当生产到第十个字符时,会对其解锁,所以当执行./customer 5时,会将一开始的生产的10个字符,进行消费。
借助GDB调试工具,仿照视频的演示,搞懂生产者消费者程序执行的流程,并用自己的话描述两个源程序执行的关键步骤。
描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者. sprintf(buff, \"%c\将'a'+counter(counter是字符偏移量,这里是0)按字符格式输入到buff字符数组中*/
counter = (counter + 1) % sign_count; //将字符偏移量后移,下一个向
20
buff字符数组输入的是'b'*/
sign_start = ALPHABET_START; /*将'a'赋值给sign_start,字符常量可以赋值给整型变量,其值是'a'对应的ASCII吗,即97;*/ sign_count = COUNT_OF_ALPHABET;/*将26赋值给sign_count */ case DIGIT:/* 数字字符 */ sign_start = DIGIT_START; sign_count = COUNT_OF_DIGIT;
lock_set(fd, F_WRLCK); /* 上写锁,此时其他进程号对该fd无读写权 if ((size = write(fd, buff, strlen(buff))) < 0)/* 从buff字符数组取strlen(buff)个字符输入到fd文件中;strlen是取字符数组的长度*/ printf(\"Producer: write error\\n\"); lock_set(fd, F_UNLCK); /* 解锁 * if ((fd = open(myfifo, O_RDONLY)) < 0)
printf(\"Function customing error\\n\") printf(\"Enjoy:\");
lseek(fd, SEEK_SET, 0);/*文件读写指针定位在文件开头*/ while (counter < need)
while ((read(fd, &buff, 1) == 1) && (counter < need)) /*从fd对应的文件中读1个字符到buff字符变量中*/
fputc(buff, stdout); /* 消费就是在屏幕上简单的将字符变量buff
打印出来 */
21
第七章进程控制开发
(1)、以下是对execlp进行调试之后显示的调试信息,根据此信息回答问题 [Detaching after fork from child process 14692. (Try `set detach-on-fork off'.)]
UID PID PPID C STIME TTY TIME CMD root 14562 1 0 22:12 ? 00:00:00 gnome-terminal
root 14565 14562 0 22:12 ? 00:00:00 gnome-pty-helper
root 14566 14562 0 22:12 pts/1 00:00:00 bash root 14675 14566 0 22:15 pts/1 00:00:00 gdb execlp
root 14676 14675 0 22:15 pts/1 00:00:00 /root/7/execlp
root 14692 14676 0 22:15 pts/1 00:00:00 ps -ef
辅助说明:gnome-terminal就是终端进程,bash是shell命令的版本类型,多数Linux发行版本采用bash作为其命令版本;
以下是对execlp进行调试之后显示的调试信息,根据此信息回答问题
执行父进程的进程号是多少? 14676 14675 14566 14562 1
执行子进程的进程号是多少? 14692 14676 14675 14566 14565 14562
(2)、使用gdb调试waitpid(),如果首先执行的是父进程,会出现什么情况? 如果想要调试子进程应该如何做??? [root@localhost 7]# ./waitpid The child process has not exited The child process has not exited The child process has not exited The child process has not exited The child process has not exited hello world
The father process quit because the child process quit. The return of waitpid() is the child process PID : 4921
如果先执行父进程,且指定子进程为非调试状态,则在屏幕上一直显示The child process has not exited 这时候就需要强制退出,如果指定子进程为调试状态,使用C进行调试,会在屏幕上显示5行The child process has not exited,才能获得子进程的pid。
如果先调试子进程,并且子进程为调试状态,使用单步调试s,waitpid会一直监测pc的
22
值,且会在屏幕上一直输出The child process has not exited ,直到单步调试到sleep(5)时,会先显示The child process has not exited,再显示将要调试的exit(0),当执行exit(0)后,程序会正常退出。 如果先调试子进程,且指定父进程为非调试状态,使用单步调试,直到执行exit(0)后,程序就会正常退出,这一轮调试结束,在执行s后,从if(pc<0>)始以默认方式调试。
(3)、已知multi_proc可执行程序的一个可能的执行结果,如下所示: [root@localhost ~]# ./multi_proc In child1: execute 'ls -l'
In child2: sleep for 5 seconds and then exit In father process: 总计 96
-rw------- 1 root root 1155 2014-09-26 anaconda-ks.cfg drwxr-xr-x 2 root root 4096 2014-09-26 Desktop -rw-r--r-- 1 root root 35507 2014-09-26 install.log
-rw-r--r-- 1 root root 4399 2014-09-26 install.log.syslog drwxrwxrwx 10 root root 4096 02-26 08:27 linux -rwxr-xr-x 1 root root 5760 03-16 12:42 multi_proc drwxr-xr-x 3 root root 4096 01-05 08:57 vmtools Get child1 exit code
The child2 process has not exited! The child2 process has not exited! The child2 process has not exited! The child2 process has not exited! The child2 process has not exited! Get child2 exit code [root@localhost ~]#
假设CPU是单核,即某时刻只有一个进程在CPU上运行,试说明这些结果是如何运行出来的?
是否可以设置进程间的执行顺序,如果可以应该怎么做?
在42行方法内有一个sleep(5),当函数内出现sleep(5)之后,系统会自动将此进程调至就绪状态。然后继续执行以下的程序,在执行 child = waitpid(child2, NULL, WNOHANG );此语句的时候即是程序对目前子进程-->sleep(5)的监视,如果子进程在程序里面那么waitpid()函数会返回一个0,如果子函数已经运算完毕,那么waitpid()会返回子进程号。 所以当sleep()函数还在运行的时候child是0,那么就会执行printf(\"The child2 process has not exited!\\n\");然后进入sleep(1)函数。这时候时间片对于CPU来说是空的,所以 继续状态的进程会自动调度进CPU运行,这时候会执行sleep(5)方法。当周而复始当sleep(5)运行完毕的时候继而运行exit(0),退出当前程序。那么waitpid()检测到子进程退出的情况 所以会把子进程的pid值返回给child变量。然后做do--while()循环,继而执行下方语句。 /**************************************************************************/
需要先执行waitpid(),这个时候如果这个时候waitpid()返回值是0则代表子进程尚未推出。子进程运行结束,那么waitpid()函数会返回child_pid。
fork()函数运行之后,这个时候会先运行子进程的sleep(5),因为子进程已经开始睡眠,所以这个时候将子进程调至就绪状态,然后其他程序抢注进来,所以这个时候是运行
23
waitipid()函数的,而这个时候会出现waitpid()函数里面的sleep(1),所以这个时候会发现,这个时候,sleep(1)一执行原本就绪状态的子进程的sleep(5)会抢注进入CPU,当时间片轮转sleep(1)一旦一到时间便会继续监测子进程退出没有,如果没有的便会输出,所以他会在子进程的5秒时间里面父进程的waitpid()在时间片的调度下轮转5次监测5次,所以也输出5次。
(4)、编写一个守护进程,用root用户编译运行后,将用户切换到david用户,执行cd /root; 屏幕提示: [david@localhost ~]$ cd /root
-bash: cd: /root: 权限不够 系统日志文件(/var/log/messages)显示:
Mar 16 14:24:45 localhost The wrong information of change directory[4403]: Ordinary user can't change directory to /root!
将此守护进程开发出来。 答:代码如下所示,“*”之间部分为修改过的代码!!截图在代码下面。 int main(void) {
pid_t child1,child2; int i,sid;
/*创建子进程1*/ child1 = fork(); if (child1 == 1) {
perror(\"child1 fork\");
/*perror() 用来将上一个函数发生错误的原因显示到标准输出设备上; child1 fork是提示:右边是具体出错的原因;*/ exit(1); }
else if (child1 > 0) {
exit(0); }
openlog(\"daemon_proc_info\LOG_PID, LOG_DAEMON);/* openlog()函数用于打开系统日志服务的一个连接*/
/* \"demon_syslog\":通常为程序名称 */ /* LOG_PID:每条消息中包含进程的PID*/ /* LOG_DAEMON:其他系统守护进程*/
/*以下几步是编写守护进程的常规步骤*/ setsid(); chdir(\"/\"); umask(0);
for(i = 0; i < getdtablesize(); i++) {
close(i); }
24
/*创建子进程2*/ child2 = fork(); if (child2 == 1) {
perror(\"child2 fork\"); exit(1); }
else if (child2 == 0) {
\");
}
/*在日志中写入字符串*/
syslog(LOG_INFO, \" child2 will sleep for 10s \"); sleep(10);
syslog(LOG_INFO, \" child2 is going to exit! \"); exit(0); } else {
waitpid(child2, NULL, 0);
syslog(LOG_INFO, \" child1 noticed that child2 has exited \"); /*关闭日志服务*/ closelog(); while(1) {
if((sid=execlp(\"cd\ {
syslog(LOG_INFO,\"%s\\n\user can't change directory to /root! exit(1); }
sleep(10); } }
25
(5)、为什么程序运行时最总会显示4行代码,用gdb调试时会显示5行代码
在gdb调试中,没有敲入set命令,系统默认调试父进程,而子进程不受影响,继续执行,先调试父进程,输出The child process has not exited后会休眠1秒钟,子进程继续执行,屏幕就会输出The child process is runing,因为父进程已休眠1秒,所以子进程在休眠4秒,程序执行非常快,睡眠时间占绝大部分。
在执行时,子进程和父进程会争夺cpu的使用权,在视屏中,子进程先抢到cpu的使用权,故先会输出The child process is runing,在输出5行The child process has not exited
26
第八章进程间通信
1、有关pipe()函数的问题:如果将waitpid(pc,NULL,0);注释掉,程序执行会发生什么结果,为什么?
1.先会打印父进程里的内容Parent wrote 17 bytes : 'Pipe Test Program',父进程结束。
然后打印子进程里的内容[root@localhost pipe]# 117 bytes read from the pipe is 'Pipe Test Program',子进程结束。
因为父进程休眠的时间要小于子进程休眠的时间,所以父进程先执行,当子进程还未休眠结束是,父进程已经结束,随着子进程休眠结束,打印子进程内的信息。 2、
在tmp里面创建名为myfifo的管道文件。mknode myfifo p
3、如果在int ret=alarm(5);上面再加一条语句休眠10秒,会出现什么结果? 不影响运行结果
如果注释掉 pause();会打印底下的语句I have been waken up吗?为什么? 不影响运行结果 会打印
因为设置定时器5秒钟时间,到点后悔直接发SIGALARM信号,但在5秒到来前,不加pause的话,程序会继续向下执行。对于alarm()函数就起不到任何效果。
27
28
.(1)因为调试子进程,但对父进程没有任何影响,所以在执行子进程的if时,父进程继续执行。
(2)当信号量执行V操作时,会将sem_v()会返回一个值给变量,此时被阻塞的父进程会立即执行P,并且父进程直接结束(跳出主方法),对于子进程来说,会继续执行并打印相关语句,跳出当前的方法,但主方法已经跳出,因此子方法不能跳出光标悬停。
6、共享内存是最为高效的进程间通信方式,请写出其实现进程间通信的步骤; 共享内存是最为高效的进程间通信方式,请写出其实现进程间通信的步骤;
因为共享内存是多个进程共用一个内存,不用对相关信息进行复制等操作,直接从内存中读数据。
第一步:创建共享内存,使用shmget()函数,从内存中获得一段共享内存。 第二步:映射共享内存,把创建的共享内存映射到具体的进程空间。
7、在消息队列中,消息发送端进程和消息接收端进程之间为什么不需要额外实现进程间同步的机制?
因为队列的特点是先进先出,信息是从固定端读出,从另一个固定端写入,程序是顺序执行,因此在发送和接收端不需要同步机制。
29
第九章:
因为在同一时刻只能有一个线程掌握某个互斥锁,虽然线程0在休眠,但是它仍然掌握互斥锁。因此其它线程在线程0未解锁前,其它线程都不能抢占CPU资源。
2、改写thread_mutex.c程序,使得线程的执行顺序为1,2,0,如视频中所演示的那样。 sem_post(&sem[THREAD_NUMBER - 2]);/*将2号信号量的值改为1*/
for (no = 1 ; no <=3; no++) {
res = pthread_join(thread[no%3], &thrd_ret);
30
if (!res)
{ printf(\"Thread %d joined\\n\else
{ printf(\"Thread %d join failed\\n\}
sem_post(&sem[(no +1) % 3]);
/*第一次循环if else执行过后,2号信号量释放资源,立即将1号信号量的值改为1;然后进入第2次循环等待1号线程的结束;*/
}
初始状态3个空单元个数个数 2,1,2,1,2,3,2,1,2
不会,因为即使它抢到CPU资源,full初始值为0,没有可以消费的资源,因此消费者线程只能阻塞。
31
第十章:
1、请描述基于socket套接字的通讯机制,包含服务端和客户端;
socket,即套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。也因为这样,套接字明确地将客户端和服务器区分开来。 客户端:
首先服务器应用程序用系统调用socket来创建一个套接安,它是系统分配给该服务器进程的类似文件描述符的资源,它不能与其他的进程共享。
接下来,服务器进程会给套接字起个名字,我们使用系统调用bind来给套接字命名。然后服务器进程就开始等待客户连接到这个套接字。
然后,系统调用listen来创建一个队列并将其用于存放来自客户的进入连接。
最后,服务器通过系统调用accept来接受客户的连接。它会创建一个与原有的命名套接不同的新套接字,这个套接字只用于与这个特定客户端进行通信,而命名套接字(即原先的套接字)则被保留下来继续处理来自其他客户的连接。 服务端:
基于socket的客户端比服务器端简单,同样,客户应用程序首先调用socket来创建一个未命名的套接字,然后将服务器的命名套接字作为一个地址来调用connect与服务器建立连接。 一旦连接建立,我们就可以像使用底层的文件描述符那样用套接字来实现双向数据的通信。 2、如何实现网络高级编程?
本节给出了两种解决I/O 多路复用的解决方法,fcntl 和select。可以看到,由于在Linux中把socket也作为一种特殊文件描述符,这给用户的处理带来了很大的方便. 1.fcntl 在文件已经共享的情况下如何操作,也就是当多个用户共同使用、操作一个文件的情,这时,Linux通常采用的方法是给文件上锁,来避免共享的资源产生竞争的状态。 文件锁包括建议性锁和强制性锁。 在Linux中,实现文件上锁的函数有lock和fcntl,其中lock用于对文件施加建议性锁,而fcntl 不仅可以施加建议性锁,还可以施加强制锁。同时,fcntl还能对文件的某一记录进行上锁,也就是记录锁。 记录锁又可分为读取锁和写入锁,其中读取锁又称为共享锁,它能够使多个进程都能在文件的同一部分建立读取锁。而写入锁又称为排斥锁。在任何只对于文件只能有一个write lock存在,read lock和wrtie lock不能共存。
2.select 使用fcntl 函数虽然可以实现非阻塞I/O 或信号驱动I/O,但在实际使用时往往会对资源是否准备完毕进行循环测试,这样就大大增加了不必要的CPU资源。在这里可以使用select函数来解决这个问题,同时,使用select函数还可以设置等待的时间,可以说功能更加强大。
3、编写基于socket通信的客户端和服务端,要求客户端可以给服务器发消息,服务器收到后可以给客户端回消息。 (提示:需要用到多线程)
32
33
多线程实现 需求:
1、实现服务器与客户端的通信,服务器可接收多个客户端。 2、服务器发送消息时,所有客户端都可收到。 3、客户端发送信息时,只有服务器可收到。
4、服务器发送“bye”时,包括服务器程序及所有客户端程序都结束,客户端发送“bye”时,本客户端结束(当然
,服务器对应的线程也会结束)。
5、服务器要在输出客户端消息时,要连同对应客户端的IP一起输出。 对于上述需求,对应序号的解决方法如下:
1. 选用TCP(UDP也可)进行通信,主进程监听并创建与客户端对应的线程 2. 分为main(),发送信息线程处理函数,接受信息线程处理函数。 具体:
只要有一个接受客户端,就要创建服务器发送信息线程,为了编写的方便,也可没有接受客户端就创建服务器发送
信息线程,只要服务器端有输入,就要向每个客户端发送,这就要求服务器发送线程能对主进程创建的所有客户端
socket文件描述符,进行操作(写操作)。所以全局变量需包括所有socket描述符(也可在main中定义而传地址)
。但是,如何判断某个socket文件描述符是否关闭、可用,就要在初始时,将socket文件描述符置为0或负数,并且
当对应的客户端线程关闭前,需要close(socket)后,置socket为0或负数。当服务器端读取到字符串后,就遍历整
个socke数组,判断是否大于0,大于就发送信息到此socket描述符。
34
3. 正常解决
4. 在客户端、服务器端发送完信息后,都将此信息与”bye”比较,是“bye“就退出。 5. 在服务器accept一个客户端后,需要将得到的客户端地址IP记录,并传递给将要创建的与此客户端交互的线程
6. 由2、5可得到,传递给服务器接受信息线程的参数为:socket文件描述符,IP //server.c
#include #include #define PORT 5000 #define A(x) (struct sockaddr*)(&x) #define ACCEPT_NUM 5 pthread_t m_id;//send massage's pthread_id pthread_t id[ACCEPT_NUM];//thread num struct thread_arg{ int fd; //指向同一个文件描述符 char ip[100]; }; struct thread_arg arg[ACCEPT_NUM];//向recv_func传递的参数类型 char server_massage[1024]={0}; //int thread_num=0;//已经存在的线程数 //recv_ massage void* recv_func(void* p) { int i=(int)p; char buf[1000]; int readn=0; for(;;){ memset(buf,0,sizeof(buf)); readn=read(arg[i].fd,buf,sizeof(buf)-1);//读取客户端来的信息 if(readn<=0) break;//出错或者对方关闭就退出 buf[readn] = '\\0'; printf(\"clientip-%s : %s\\n\if(strncmp(buf,\"bye\} 35 close(arg[i].fd); arg[i].fd=0;//说明对应描述符已经关闭,易于send_massage线程的判断 } void* send_massage(void*para){ int nread=-1; int i=0; while((nread=read(0,server_massage,sizeof(server_massage)))){ server_massage[nread]='\\0'; for(i=0;i write(arg[i].fd,server_massage,strlen(server_massage)+1); } } if(strncmp(server_massage,\"bye\{ exit(0); } } } int main() { int server_sock; server_sock = socket(AF_INET, SOCK_STREAM, 0);//建立套接字 struct sockaddr_in si; si.sin_family = AF_INET;//跟前面的一致 si.sin_addr.s_addr = INADDR_ANY;//0,本机的任何ip si.sin_port = htons(PORT); if(bind(server_sock, A(si), sizeof(si))<0){ printf(\"%s\\n\} pthread_create(&m_id,NULL,send_massage,NULL); listen(server_sock, 5); int j=0; for(j=0;j arg[j].fd = accept(server_sock,A(si),&len);//等待客户端连接 if(arg[j].fd<0) { perror(\"accept failed\"); 36 return -1; } //成功就返回新套接字 //welcome friend from inet_ntop(si.sin_family,&si.sin_addr,ip,sizeof(ip)); printf(\"%s连接主机成功\\n\ sprintf(buf,\"welcome friend from <%s:%d>\\n\ip,ntohs(si.sin_port)); write(arg[j].fd,buf,strlen(buf)); memcpy(arg[j].ip,ip,100); pthread_create(&id[j],NULL,recv_func,(void*)j); // thread_num++; } pthread_join(m_id,NULL); } //client //client #include #define PORT 5000 // The port which is communicate with server #define LENGTH 1024 // Buffer length void* send_massage(void *); void* recv_massage(void* ); int main(int argc, char *argv[]) { int sockfd; // Socket file descriptor int num; // Counter of received bytes int recv_pid; int send_pid; struct sockaddr_in remote_addr; // Host address information 37 if (argc != 2) { printf (\"Usage: client SERVER_IP (ex: ./client 172.0.0.1).\\n\"); return (0); } if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { printf(\"ERROR: Failed to obtain Socket Descriptor!\\n\"); return (0); } remote_addr.sin_family = AF_INET; // Protocol Family remote_addr.sin_port = htons(PORT); // Port number inet_pton(AF_INET, argv[1], &remote_addr.sin_addr); // Net Address if (connect(sockfd, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr)) == -1) { printf (\"ERROR: Failed to connect to the host!\\n\"); return (0); } else { printf (\"OK: Have connected to the %s\\n\ } pthread_create(&send_pid,NULL,send_massage,(void*)sockfd); pthread_create(&recv_pid,NULL,recv_massage,(void*)sockfd); pthread_join(send_pid,NULL); pthread_join(recv_pid,NULL); return (0); } //thread_to recv massaga void* recv_massage(void* arg) { char revbuf[LENGTH]={0}; // Receive buffer int sockfd=(int )arg; int num; memcpy(revbuf,\"hello\ while (strncmp(revbuf,\"bye\ // Check remoter command { memset(revbuf,0,LENGTH); 38 num = read(sockfd, revbuf, LENGTH); if(num<=0){ exit(0); } revbuf[num]='\\0'; printf(\"server: %s \\n\ } exit(0); } //thread to send massage void* send_massage(void *arg) { int fd=(int)arg; char sendbuf[LENGTH]={0}; int nread=0; while(1){ printf(\"please input some word:\\n\"); //scanf(\"%s\ nread=read(0,sendbuf,LENGTH); if(nread<=0) { exit(0); } sendbuf[nread]='\\0'; write(fd,sendbuf,strlen(sendbuf)+1); if(strncmp(sendbuf,\"bye\{ exit(0); } } } 39 因篇幅问题不能全部显示,请点此查看更多更全内容