Linux Chapter6 Exercise

本科时期Linux 程序设计的实验文档整理。

一.实验内容

T1

1. 区别exec函数族使用方法的区别。

(1)练习使用execvp函数**,用于根目录文件查找,查找文件名为:tes****t.txt的文件。**

(2)练习使用execlp函数**,用于列出根目录下所文件和文件夹及其文件的类型使用权限****,文件的拥有者,创建时间和文件名称。**

(3)练习使用execl函数,用于列出根目录下所的文件和文件夹**。**

实验步骤:

在exec( )族中有6个函数可用来建立子进程,它们是execl,execv,execle,execve,execlp,execvp,函数中第5,6个字符l,v,e,p表示函数中的参数分别用列表传递方式、字符指针数组传递方式、可指定环境变量及路径自动搜索功能。

T1(1)

这道题的思路是用execvp去查找执行find,然后传参给find去查找test.txt文件。

运行代码:

1
2
3
4
5
6
int main()
{
    char *argv[]={(char*)"find", (char*)"-name",(char*)"*.txt",NULL};
    cout<<execvp(argv[0], argv)<<endl;
    return 0;
}

int execvp(const char file, const char argv[]);**

函数说明:

execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数args传给该欲执行的文件。

PATH是Linux和类Unix操作系统中的环境变量,它指定存储可执行程序的所有bin和sbin目录*。 当用户在终端上执行任何命令时,它会通过PATH变量来响应用户执行的命令,并向shell发送请求以搜索可执行文件。 超级用户通常还具有/sbin和/usr/sbin条目,以便于系统管理命令的执行。*

注意事项:

  • (1) 第一个参数是要运行的文件,会在环境变量PATH中查找file,并执行.

  • (2) 第二个参数,是一个参数列表,如同在shell中调用程序一样,参数列表为0,1,2,3……因此,test.txt作为第0个参数,需要重复一遍。

  • (3)  argv列表最后一个必须是 NULL。

  • (4) 失败会返回-1, 成功无返回值,但是,失败会在当前进程运行,执行成功后,直接结束当前进程,可以在子进程中运行。

测试结果:

最开始的时候我对这道题的理解有点偏差,执行的是“test.txt”,结果不管有没有这个文件,返回值为-1,说明执行test.txt的过程中出现了错误。

图片

后来转变了思路,改成调用bin里面的find。

图片

新建文件便于查找。

图片

find在查找成功后会显示查找的这个文件的名字:

图片

T1(2)

下面是我代码中核心的一句。

1
execlp("ls","ls","-al",NULL);

实验的时候查了一下execlp函数的相关信息:
int execlp(const char * file,const char * arg,……);

函数说明:

execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名, 找到后便执行该文件, 然后将第二个以后的参数当做该文件的argv[0]、argv[1]……, 最后一个参数必须用空指针(NULL)作结束。

返回值:

如果执行成功则函数不会返回, 执行失败则直接返回-1, 失败原因存于errno 中。

测试结果:

当前文件夹下的文件:

图片

根目录/下的文件:

图片

T1(3)

参考老师给的PPT上execl的调用格式使用函数:

execl("/bin/ps", “ps”, “-o”, “pid,ppid,pgrp,session,tpgid,comm”, NULL);

1
execl("/bin/ls","ls","/","-al",NULL);

**int execl(const char path, const char arg, …);

测试结果:

根目录/下的文件:

图片


2、 编写一个后台检查邮件的程序,这个程序每隔一个指定的时间会去检查邮箱,如果发现有邮件了,会不断的通过机箱上小喇叭发出声音报警,Linux的默认邮箱地址是/var/spool/mail/用户的登陆名。

T2

实验步骤:

先简单描述一下我的实验思路:

(1)init_daemon()函数创建守护进程。

(2)间隔时间执行检测代码可以用while循环配合sleep函数实现。

(3)shell程序实现:检测邮箱路径中的对应邮件文件大小,文件不为空则说明有新邮件未读,控制后台发出提示音。

下面详细记录实验过程。

1.ubuntu mail安装与配置

我的ubuntu之前都还没有安装mail,所以首先需要安装mail:

(安装参考链接:https://blog.csdn.net/zpf336/article/details/89295474

1
2
3
sudo apt-get install sendmail  
sudo apt-get install sendmail-cf
sudo apt-get install mailutils

安装过程中弹出配置config,按住Tab然后Enter进入下一步,选择Internet Site,其他的配置我没有做更多的修改:
图片

测试sendmail是否安装成功,以及使用mail查看自己的邮箱是否有新的邮件:

以下两项如果测试都没有问题,说明第一步安装已经成功了。

图片

图片

sendmail 默认只会为本机用户发送邮件,只有把它扩展到整个Internet,才会成为真正的邮件服务器。下面开始进行配置,打开sendmail的配置宏文件:/etc/mail/sendmail.mc,修改如下行为0.0.0.0.1。

图片

0.0.0.0表名可以连接任何服务器

重新生成配置文件:

1
2
3
cd /etc/mail  
mv sendmail.cf sendmail.cf_bk      //备份  
m4 sendmail.mc > sendmail.cf   //root执行

试着给本机上的用户发送一封邮件:
图片

1
echo "This is the message body" | mail -s "hello, this is a test!" username@ubuntu

使用mail查看受到的邮件,发现刚刚发送的邮件已经在邮箱里面了:
图片

图片

图片

2.ubuntu shell 调用电脑提示音

查了一下资料,发现调用提示音直接用echo就可以实现: echo -e “\a” -e选项用于处理特殊字符,\a让系统发出警告声。

1
echo -e "\a"

或者是使用spd-say 让系统读出指定的语句:

1
spd-say "New Email!"

(不能用中文,否则就会读chinese letter,chinese letter,chinese letter… )
一开始选用了常用的蜂鸣声作为收到邮件的提示音,然后测试发现后台执行shell的时候不能听见蜂鸣声,最后还是靠spd-say "New Email!"作为提示。

3.收到新邮件调用电脑提示音

先前比较困扰我的是:在调用系统电脑提示音时,怎么判断邮件是新的邮件?

如下图所示,在mail的标注中,R是已读邮件,N才是新的邮件:

图片

当我用mail命令把受到的所有邮件都读了一遍以后,发现已读邮件都会被存到用户目录下的mbox中,对应的/var/mail/usr中的邮件大小会重置为0.

(mail命令参数说明:https://blog.csdn.net/weixin_33967071/article/details/89784271

图片

图片

图片

而当新的邮件没有被阅读时,受到的邮件的大小是不为0 的,所以只要在shell中检测对应的文件是否为空,如果不为空的话,说明有新邮件未读取,发出提示音即可。

图片

1
2
3
4
5
6
#检查邮箱中是否有新邮件,存在新邮件则发出提示音
mailsize=`ls -l /var/spool/mail/vv | awk '{print $5}'`
if [ $mailsize -ne 0 ]; then #邮箱中存在未读取的新邮件
    echo -e "\a"
spd-say "New Email!"
fi

编辑完shell文件以后一定一定要设置为可执行!不然execlp就会报错permission denied!
图片

4.守护进程执行shell

守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。

例6.9的init已经实现了守护进程的创建,基于例6.9而做出的主要修改写在while函数中:

1
2
3
4
5
6
7
8
9
10
11
//每间隔一段时间检查是否有新邮件,如果有则发出提示音
sleep(200);
if((fp=fopen("linux6T2.log","a+"))>=0)
{
t=time(0);
fprintf(fp,"邮箱检测守护程序还在运行,时间是:%s",asctime(localtime(&t)));
fclose(fp);
}
char *argv[]={(char*)"/home/vv/chap6/linux6T2/checkmail.sh",NULL};
execvp(argv[0], argv);
perror("Execvp Fails ") ;

测试结果:

最后放上测试结果日志文件:

图片

当邮箱里面有新的未读邮件时,每隔一段时间后台就会提示一遍“New Email”。

ps:如果需要测试,注意修改一下checkmail.sh文件和linux6T2.C里的文件路径。


3、 编写一个程序,要求运行后成为守护进程,每隔5分钟修改一次本机的Ip地址。所有的IP 地址放在一个文本文件中。每隔5分钟,随机读取一个,Linux中可以用“ioctl”和“sysctl”函数实现,也可以调用系统命令“ifconfig”,例如“ifconfig eth0 192.168.0.20 netmask 255.255.255.0”.可以考虑使用例6.9的init.c .

T3

实验步骤:

先简单描述一下我的实验思路:

看了一下示例代码6.9中的内容,很明显init_daemon()函数的作用就是创建守护进程,这一部分的源代码可以直接使用,所以说现在需要实现的就是间隔一段时间去修改网络IP地址。

1
init_daemon(); // 将进程声明为守护进程

(1)间隔时间执行代码可以用while循环配合sleep函数实现。

(2)修改IP地址我查到了很多种方法,最后还是选择了最简洁的“ifconfig eth0 192.168.0.20 netmask 255.255.255.0”,正好刚刚学习的execl族能在c++中调用shell文件,除此之外使用“system(“bash shell_file”)”也是ok的。

下面是while循环中实现日志记录与shell执行的核心代码。

1
2
3
4
5
6
7
8
9
10
11
//每间隔5min读取IP,修改一次当前IP地址
sleep(10);
if((fp=fopen("linux6T3.log","a+"))>=0)
{
t =time(0);
fprintf(fp,"守护进程还在运行,时间是:%s",asctime(localtime(&t)));
fclose(fp);
}
//char *argv[]={(char*)"/home/vv/chap6/linux6T3/changeIp.sh",NULL};
//execvp(argv[0], argv);
//perror("Execvp Fails ");

system(“bash /home/vv/chap6/linux6T3/changeIp.sh”);

1.创建守护进程:

创建守护进程可以参照例6.9的init,其中我查到了一些比较重要的函数的作用解释。

fork的作用

使用fork函数创建进程时,新的进程叫子进程,原来调用fork函数的进程则称为父进程。

子进程会复制父进程的数据和堆栈空间,并继承父进程的用户代码、组代码、环境变量、已经打开的文件代码、工作目录及资源限制等,但是子进程和父进程使用不同的内存空间。因此,很多时候用fork函数创建进程称为“复制进程”。

se****tid的理解

setsid函数用于创建了一个新的会话,并担任该会话组的组长。调用setsid有3个作用:让进程摆脱原会话的控制,让进程摆脱原进程组的控制和让进程摆脱原控制终端的控制。

在调用fork函数时,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但原先的会话期、进程组、控制终端等并没改变,因此,还不是真正意义上独立开来,而setsid函数能够使进程完全独立出来,从而脱离所有其他进程的控制。

u****mask的作用

默认情况下的umask值是022(可以用umask命令查看),此时建立的文件默认权限是644(6-0,6-2,6-2),建立的目录的默认 权限是755(7-0,7-2,7-2),可以用ls -l验证。umask的用途是为了控制默认权限,不要使默认的文件和目录具有全权而设的。

2.修改IP地址

ifconfig查看设备无线网卡的名字:

图片

( 看到网上的教程都是什么也不说直接让改eth0,eth0确实是很多电脑的默认第一网卡名称,可是我根本没有一块网卡叫这个名字,具体问题还是要具体分析。)

我在Terminal中试着改了一下网卡的IP地址,改完发现果然上不了网了:

1
 sudo ifconfig wlo1 192.168.0.20 netmask 255.255.255.0

而且发现按照原来的IP地址改回去也上不了网,需要再重新进行连接才能上网…
于是编辑了一个shell文件,在shell文件里面完成对IP地址文件的随机一行读取以及切换IP地址的功能,编辑完毕以后我单独测试了一下shell文件,然后发现ifconfig中的inet的IP地址确实被修改了。

执行shell时遇到了各种权限问题,最后就直接sudo开超级管理员权限了。

1
sudo ifconfig wlo1 $ip netmask 255.255.255.0

图片

执行ifconfig发现wlo1的网络地址确实被修改了:

图片

3.C中执行shell

这一步和T1中基本相同,不过我在调用execlp时遇到了permission denied的error,解决这个错误的方法是将要执行的shell文件设置为“可执行”。

需要注意的是在把sh放到守护进程运行前,最好能提前测试一下单独用execlp执行sh有没有问题,不然如果有问题没有发现的话,在测试运行过程中再找问题就困难很多。我之前就浪费了很多时间在上面,运行一次多一个守护进程,最后看日志也是一头雾水。

测试结果:

为了方便测试,我把修改IP地址的时间间隔改成了10ms,下面是执行成功以后的日志文件,日志文件会在/tmp目录中生成,记录的是当前修改的IP地址和当前时间点。

我把测试时候生成的log文件copy了一份到压缩包里面。

图片

图片

ps:如果老师要测试一下的话,注意修改一下changeIp.sh文件和linux6T3.C文件里的路径。


二.实验总结

1.warning: ISO C++ forbids converting a string constant to ‘char’ [-Wwrite-strings]*

图片

报错分析:

这是因为在赋值操作的时候,等号两边的变量类型不一样,那么编译器会进行一种叫做 隐式转换(implicit conversion) 的操作来使得变量可以被赋值。

上面的表达式中,等号右边的字符串是一个不变常量,在c++中叫做string literal,type是const char *,而p则是一个char指针。如果强行赋值就是将右边的常量强制类型转换成一个指针,结果就是我们在修改一个const常量。

编译运行的结果会因编译器和操作系统共同决定,有的编译器会通过,有的会抛异常,就算过了也可能因为操作系统的敏感性而被杀掉。

解决方案:

1
char *argv[]={(char*)"test.txt", 0};

2.Execvp Fails : Permission denied

报错分析:

在这次实验中,我遇到这种错误的原因是Execvp要执行的文件没有执行权限。

解决方案:

给要执行的shell文件赋予执行权限。

ps:错误虽小,解决起来也很简单,但是要引起重视。

开始我的代码里面没有perror("Execvp Fails "),根本不知道为什么execvp会执行失败,考虑到各种可能(比如路径比如传参)都没能解决问题,浪费了大量的时间。

三.实验感想

先做了实验的第三题然后又做完了第二题,第三题做的很艰难,出现了各种各样让我迷惑的情况,好在最后都静下心来解决了。第二题相对而言就做的很快了。

这次实验总体来讲难度适中,在没有理解看明白一些进程相关函数之前写起来还是听困难的…调试起来也很麻烦(比如说修改IP地址那道题,我运行的过程中它就开始改IP地址,观察日志文件发现有不对的地方想查一下又发现自己IP被改了上不了网…)但是在弄明白原理以后,基本上做起来就比较顺畅了。