西邮Linux兴趣小组10级暑期学习周总结
第二周(7月22日至7月28日)
第四组
组长:李攀
小组成员:韩伟,王情,周媛媛,杨东明,张京鹏
本周任务
《鸟哥的Linux私房菜》
第六章的内容重点在文件权限,熟悉掌握linux中几种文件权限代表的意义,区分用户,用户组,其它用户对某一目录的存取权限,能够熟练的按照要求设置相应的文件权限,更改文件的属主。在本章节中常用的命令:chmod,chown,chgrp。
第七章,在 本章节中,要能熟悉的应用管理文件与目录的几个命令,如:cd,ls,cp,mv,rm等都是很常用的命令, 这些命令的某些参数也得熟悉掌握,接下来就是文件目录的默认权限与隐藏权限,区别这一小部分的权限与第六章中文件权限的使用方法及使用时机,熟悉文件权限的数字计算方式。
《Linux C 编程实战》
第六章:文件操作了解Linux下对文件的一些操作函数,为实现自己的ls [-al, etc]准备,最后要求实现。
1.李攀
一,心得体会
经过十几天的学习,对linux系统的系统结构和文件系统有了一定的了解,对于一些系统函数的调用有了一定的认识,对数据结构堆栈和队列一了初步的了解。接下来就需要我们多动手操作,尽可能的熟悉我们所学的东西。
二,学习进度
《linuxc编程实战》学到第六章文件的操作;
《鸟个的私房菜》学到了第10章
《数据结构》学到了第三章
三,遇到的问题
在使用gdb调试程序的时候,我们常常需要对main函数的参数进行输入,那么应该怎么输入呢?
首先读入文件,然后在输入setargs +要输入的命令;然后输入的命令就会自动的存入argv中,argc也会相应的增加。
dup和dup2详解:
dup和dup2也是两个非常有用的调用,它们的作用都是用来复制一个文件的描述符。
它们经常用来重定向进程的stdin、stdout和stderr。
这两个函数的原形如下:
#include
int dup( int oldfd );
int dup2( int oldfd, inttargetfd )
利用函数dup,我们可以复制一个描述符。传给该函数一个既有的描述符,它就会返回一个新的描述符,
这个新的描述符是传给它的描述符的拷贝。这意味着,这两个描述符共享同一个数据结构。例如,
如果我们对一个文件描述符执行lseek操作,得到的第一个文件的位置和第二个是一样的。
下面是用来说明dup函数使用方法的代码片段:
intfd1, fd2;
...
fd2 = dup( fd1 );
需要注意的是,我们可以在调用fork之前建立一个描述符,这与调用dup建立描述符的效果是一样的,子进程也同样会收到一个复制出来的描述符。
dup2函数跟dup函数相似,但dup2函数允许调用者规定一个有效描述符和目标描述符的id。dup2函数成功返回时,
目标描述符(dup2函数的第二个参数)将变成源描述符(dup2函数的第一个参数)的复制品,换句话说,
两个文件描述符现在都指向同一个文件,并且是函数第一个参数指向的文件。下面我们用一段代码加以说明:
intoldfd;
oldfd = open("app_log", (O_RDWR | O_CREATE), 0644);
dup2( oldfd, 1 );
close( oldfd );
本例中,我们打开了一个新文件,称为“app_log”,并收到一个文件描述符,该描述符叫做fd1。我们调用dup2函数,
参数为oldfd和1,这会导致用我们新打开的文件描述符替换掉由1代表的文件描述符(即stdout,因为标准输出文件的id为1)。
任何写到stdout的东西,现在都将改为写入名为“app_log”的文件中。
需要注意的是,dup2函数在复制了oldfd之后,会立即将其关闭,但不会关掉新近打开的文件描述符,因为文件描述符1现在也指向它。
下面我们介绍一个更加深入的示例代码。回忆一下本文前面讲的命令行管道,在那里,我们将ls–1命令的标准输出作为标准输入
连接到wc–l命令。接下来,我们就用一个C程序来加以说明这个过程的实现。代码如下面的示例代码3所示。
在示例代码3中,首先在第9行代码中建立一个管道,然后将应用程序分成两个进程:一个子进程(第13–16行)和一个父进程(第20–23行)。接下来,在子进程中首先关闭stdout描述符(第13行),然后提供了ls–1命令功能,
不过它不是写到stdout(第13行),而是写到我们建立的管道的输入端,这是通过dup函数来完成重定向的。在第14行,使用dup2函数把stdout重定向到管道(pfds[1])。之后,马上关掉管道的输入端。然后,使用execlp函数把子进程的
映像替换为命令ls–1的进程映像,一旦该命令执行,它的任何输出都将发给管道的输入端。
现在来研究一下管道的接收端。从代码中可以看出,管道的接收端是由父进程来担当的。首先关闭stdin描述符(第20行),因为我们不会从机器的键盘等标准设备文件来接收数据的输入,而是从其它程序的输出中接收数据。然后,再一次用到dup2函数(第21行),让stdin变成管道的输出端,这是通过让文件描述符0(即常规的stdin)等于pfds[0]来实现的。关闭管道的stdout端(pfds[1]),因为在这里用不到它。最后,使用execlp函数把父进程的映像替换为命令wc-1的进程映像,命令wc-1把管道的内容作为它的输入(第23行)。
示例代码3:利用C实现命令的流水线操作的代码
1: #include
2: #include
3: #include
4:
5: int main()
6: ...{
7: int pfds[2];
8:
9: if ( pipe(pfds) == 0 ) ...{ //建立一个管道
10:
11: if ( fork() == 0 ) ...{ //子进程
12:
13: close(1); //关闭stdout描述符
14: dup2( pfds[1], 1 ); //把stdout重定向到管道(pfds[1])
15: close( pfds[0] ); //关掉管道的输入端
16: execlp( "ls", "ls", "-1", NULL );//把子进程的映像替换为命令ls–1的进程映像
17:
18: } else ...{ //父进程
19:
20: close(0); //关闭stdin描述符
21: dup2( pfds[0], 0 ); //让stdin变成管道的输出端
22: close( pfds[1] ); //关闭管道的stdout端(pfds[1])
23: execlp( "wc", "wc", "-l", NULL );//把父进程的映像替换为命令wc-1的进程映像
24:
25: }
26:
27: }
28:
29: return 0;
30: }
在该程序中,需要格外关注的是,我们的子进程把它的输出重定向的管道的输入,然后,父进程将它的输入重定向到管道的输出。
这在实际的应用程序开发中是非常有用的一种技术。
1.文件描述符在内核中数据结构
在具体说dup/dup2之前, 我认为有必要先了解一下文件描述符在内核中的形态。
一个进程在此存在期间,会有一些文件被打开,从而会返回一些文件描述符,从shell
中运行一个进程,默认会有3个文件描述符存在(0、1、2),0与进程的标准输入相关联,
1与进程的标准输出相关联,2与进程的标准错误输出相关联,一个进程当前有哪些打开
的文件描述符可以通过/proc/进程ID/fd目录查看。 下图可以清楚的说明问题:
进程表项
————————————————
fd标志 文件指针
_____________________
fd0:|________|____________|------------> 文件表
fd1:|________|____________|
fd 2:|________|____________|
fd3:|________|____________|
| ....... |
|_____________________|
图1
文件表中包含:文件状态标志、当前文件偏移量、v节点指针,这些不是本文讨论的重点,我们只需要知道每个打开的文件描述符(fd标志)在进程表中都有自己的文件表项,由文件指针指向。
2.dup/dup2函数
APUE和man文档都用一句话简明的说出了这两个函数的作用:复制一个现存的文件描述符。
#include
int dup(int oldfd);
int dup2(int oldfd, intnewfd);
从图1来分析这个过程,当调用dup函数时,内核在进程中创建一个新的文件描述符,此
描述符是当前可用文件描述符的最小数值,这个文件描述符指向oldfd所拥有的文件表项。
进程表项
————————————————
fd标志 文件指针
_____________________
fd0:|________|____________| ______
fd 1:|________|____________|----------------> | |
fd 2:|________|____________| |文件表|
fd3:|________|____________|----------------> |______|
| ....... |
|_____________________|
图2:调用dup后的示意图
如图2所示,假如oldfd的值为1,当前文件描述符的最小值为3, 那么新描述符3指向描述符1所拥有的文件表项。
dup2和dup的区别就是可以用newfd参数指定新描述符的数值,如果newfd已经打开,则先将其关闭。如果newfd等于oldfd,则dup2返回newfd,而不关闭它。dup2函数返回的新文件描述符同样与参数oldfd共享同一文件表项。
APUE用另外一个种方法说明了这个问题:
实际上,调用dup(oldfd);
等效与
fcntl(oldfd,F_DUPFD, 0)
而调用dup2(oldfd,newfd);
等效与
close(oldfd);
fcntl(oldfd, F_DUPFD, newfd);
3.CGI中dup2
写过CGI程序的人都清楚,当浏览器使用post方法提交表单数据时,CGI读数据是从标准输入stdin,写数据是写到标准输出stdout(c语言利用printf函数)。按照我们正常的理解,printf的输出应该在终端显示,原来CGI程序使用dup2函数将STDOUT_FINLENO(这个宏在unitstd.h定义,为1)这个文件描述符重定向到了连接套接字。dup2(connfd,STDOUT_FILENO); /*实际情况还涉及到了管道,不是本文的重点*/如第一节所说, 一个进程默认的文件描述符1(STDOUT_FILENO)是和标准输出stdout相关联的,对于内核而言,所有打开的文件都通过文件描述符引用,而内核并不知道流的存在(比如stdin、stdout),所以printf函数输出到stdout的数据最后都写到了文件描述符1里面。至于文件描述符0、1、2与标准输入、标准输出、标准错误输出相关联,这只是shell以及很多应用程序的惯例,而与内核无关。
用下面的流图可以说明问题:(ps:虽然不是流图关系,但是还是有助于理解)
printf->stdout ->STDOUT_FILENO(1) ->终端(tty)
printf最后的输出到了终端设备,文件描述符1指向当前的终端可以这么理解:
STDOUT_FILENO= open("/dev/tty", O_RDWR);
使用dup2之后STDOUT_FILENO不再指向终端设备, 而是指向connfd,所以printf的
输出最后写到了connfd。是不是很优美?:)
4.如何在CGI程序的fork子进程中还原STDOUT_FILENO
如果你能看到这里,感谢你的耐心, 我知道很多人可能感觉有点复杂, 其实
复杂的问题就是一个个小问题的集合。所以弄清楚每个小问题就OK了,第三节中
说道,STDOUT_FILENO被重定向到了connfd套接字, 有时候我们可能想在CGI程序
中调用后台脚本执行,而这些脚本中难免会有一些输入输出, 我们知道fork之后,子进程继承了父进程的所有文件描述符,所以这些脚本的输入输出并不会如我们愿输出到终端设备,而是和connfd想关联了,这个显然会扰乱网页的输出。那么如何恢复STDOUT_FILENO和终端关联呢?
方法1:在dup2之前保存原有的文件描述符,然后恢复。
代码实现如下:
savefd= dup(STDOUT_FILENO); /*savefd此时指向终端*/
dup2(connfd,STDOUT_FILENO); /*STDOUT_FILENO(1) 被重新指向connfd*/
...../*处理一些事情*/
dup2(savefd,STDOUT_FILENO); /*STDOUT_FILENO(1) 恢复指向savefd*/
很遗憾CGI程序无法使用这种方法, 因为dup2这些不是在CGI程序中完成的,而是在
webserver中实现的,修改webserver并不是个好主意。
方法2:追本溯源,打开当前终端恢复STDOUT_FILENO。
分析第三节的流图, STDOUT_FILENO是如何和终端关联的? 我们重头做一遍不就行了, 代码实现如下:
ttyfd= open("/dev/tty", O_RDWR);
dup2(ttyfd,STDOUT_FILENO);
close(ttyfd);
/dev/tty是程序运行所在的终端, 这个应该通过一种方法获得。实践证明这种方法是可行的,但是我总感觉有些不妥,不知道为什么,可能一些潜在的问题还没出现。
2.韩伟
一.学习情况总结:
这周学习进度稍微慢了点,也学的很杂,主要了解了《linuxC编程实战》第六章文件操作,比较难理解;通过这张的学习,了解了文件类型,访问,属性操作等,但还么完全掌握;对于文件锁还不是很了解,我会继续努力巩固运用它,掌握它,对于ls,虽然编出来,但还不是自己的,因为夹杂着例题的思想!编出来觉得和例题差不多!但我下来会编写一个属于自己思想的ls;至于《鸟哥的私房菜》看到第七章,了解了一些基本知识;另外,通过这周虚拟机的练习和鸟哥的学习,对系统安装和磁盘分区有了一定的了解,下来我会经过不断练习巩固的。
二.学习进度
《linuxC编程》第六章刚学完;《鸟哥》第七章刚学完,第五章还没看。
三.学习中遇到的问题
1.在学文件这张中发现出错处理特别多,但并没有发现函数调用的过程,而程序还能运行出正确结果! ????
2.getthe privilege of the file eg.-rwxr--r--
for(n= 8;n >= 0;n--)
{
if(buf.st_mode& (1<
{
switch(n%3)
{
case2:printf("r");break;
case1:printf("w");break; case0:printf("x");break;
{
printf("-");
}
}
这段程序求文件所有者,所属组,其他人权限!觉得有点经典,和大家分享下!!算法太巧妙了!!
四。心得体会
要多加练习,熟能生巧,不懂就查资料,就请教别人!
3.王情
学习心得:
通过这一周的学习,对linux有了更深的了解,认识了它更多的功能,而且也对其内部文件,目录等有了一定认识。此外文件操作这部分有些复杂,需要多花时间去研究理解。
进过这一周我知道了自己学习需要更深入更细致一点,也需要更多的思考与实践。
学习进度:
《鸟哥的linux私房菜》第六章 第七章
《linux c编程实战》 第六章
尚未解决的问题:
1.whereis 不能查找/home目录里的任何文件。
2.SUID SGID特殊权限,当档案具有了SUID或SGID权限,并且执行者也有x权限时,执行者仍不具有拥有者的权限,例如:执行者只有执行权利,而拥有者具有读写执行三种权利,并且此档案有SUID权限,但执行者仍不能对其进行读写操作。
3.什么是单人维护模式?
4.文件操作中一些函数有些不理解。
4.周媛媛
问题:
1.ID与IP的区别
2.whereis,locate,find 之间的区别。(为什么有这个文件却找不出来?)
例:有一个文件,名字为123.c,分别用whereis, locate , find 进行查找
$:whereis 123.c
123
$:locate123.c (不会出现有关此文件的东西)
$:find123.c
123.c
3.二维数组与二级指针有关系吗?是什么关系?
4.C编程实战例6-13后半部分不清楚。
5.6.3节内容看不懂,就跳过了。
6.lseek(intfd,off_t offset,int whence)中的位移数(offset),指针位置(whence)不理解。
5.杨东明
一、心得体会。
在第二周的学习中,主要接触了linux系统的一些东西,包括开机的启动流程,
对文件系统的简单了解和操作,文件压缩与打包,以及xWINDOWS的一些东西。感觉c语言基础比较弱,就回过头看一些c基础的东西。感觉只是按书本流程学习有时会很晕,可以先找一些联系比较大的内容先行学习。
二、问题
1.对硬连接只是知道是怎么回事,但具体在实际中的应用是什么
2.vim很强大,偶然发现它可以将一些只有读权限的文件强制修改并保存?还有的文件..什么权限都没有,但即使不能看见文件内容也能对文件强制修改?
上网查了下,发现个更夸张的:
6.张京鹏
一.本周的学习内容:
1.鸟哥第6,7章
2.Linux实战编程的6,7章
3.数据结构的图这一章的学习
二.心得体会:
在学习Linux实战编程的这一周内,渐渐的感到了难度的上升,同时也看到了Linux是怎么用C语言完成了那些命令的编写,通过实际的编写才能真正的掌握那些知识。
而在学习数据结构的过程中,发现在看懂的同时也要上手去练,现在感觉已经会应用栈和队列了,而递归,尽管已经掌握了它的思想,但是感觉很难把它写出来,我现在手头有一道关于递归的题,如果有哪位同学感觉递归学的还不错的话,还请指导一下。
在学习的过程中,也会产生一些思考,比如:权限的设置为什么是4,2,1呢?为什么一定要是这3个值呢?后来在学习实战编程的时候有点明白了,因为在程序中通常是对它们的宏进行'|'或着‘&’的运算,而1,2,4在二进制中只占一位,而且不会与其他数字产生混淆,这就是我的理解。
三.未解决的问题:
1.判断两个数是否为互质数有几种方法,其中最高效的方法是什么?
2.找一个数的素数因子,最高效的算法是什么?(如18,只有2,3,而6或9因为可以分解出2或3,所以不算)
3.在实战编程中的ls程序中的冒泡排序法那,为什么还要在后面加'\0'呢?
小组周总结:
在这周的学习中,小组成员主要还是按照进度进行学习,也进行了一些拓展学习,同时感觉这周的学习有点疲惫,这也是整体兴趣小组出现的问题,希望大家能够适时的放松,以兴趣为主,不要太过劳累。在讨论中发现,在学习了实战编程以后,对最后的那个ls的理解还有较大问题,下面是小组成员的学习报告,对一些比较精彩的部分和未解决的部分标为红体,希望大家看的时候注意一下。