一 、实验目的:
1.掌握文件以及缓冲文件系统、文件指针的概念;
2.学会使用文件打开、关闭、读、写等文件操作函数;
3.学会用缓冲文件系统对文件进行简单的操作;
4.非缓冲文件的操作。
二、实验设备:
1.硬件 PC机
2.软件 VMware Workstation、Linux
三、实验内容:
编写程序并上机调试运行。
1.新建一个源文件如:f1.c编程实现:打开a.txt文本文件,读取其中内容,将其中小写字母转换为大写字母后,复制到b.txt新建文件中。
#include
#include
int main(){
char ch;
FILE *fp1,*fp2;
if((fp1=fopen("a.txt","r"))==NULL){
printf("file open error!\n");
exit(0);
}
if((fp2=fopen("b.txt","w"))==NULL){
printf("file open error!\n");
exit(0);
}
while((ch=fgetc(fp1))!=EOF){
if(isalpha(ch) && islower(ch))
ch=ch-0x20;
fputc(ch,fp2);
}
fclose(fp1);
fclose(fp2);
return 0;
}
2.新建一个源文件如:f2.c编程实现:程序打开某个文本文件,然后把此文件中的小写字母转换为大写字母其他字符不变,其中文件名作为命令行参数。
#include
#include
#include
#include
#include
#define PERMS 0666
#define DUMMY 0
#define BUFSIZE 1024
int main(int argc,char *argv[]){
int fd,num,i,num2;
char iobuffer[BUFSIZE];
if(argc!=2){
printf("file error!\n");
return 1;
}
if((fd=open(*(argv+1),O_RDWR,PERMS))==-1){
printf("Source file open error!\n");
return 2;
}
num=read(fd,iobuffer,BUFSIZE);
for(i=0;i=97&&iobuffer[i]<=122)
iobuffer[i]=iobuffer[i]-32;
}
lseek(fd,0,SEEK_SET);
num2=write(fd,iobuffer,num);
if(num2!=num){
printf("write error!\n");
return 3;
}
close(fd);
return 0;
}
3.新建一个源文件如:f3.c编程实现:程序从键盘接收多个字符,直到输入#字符,输入结束,然后,程序把这些输入的字符写入文件 file.txt中。
#include
#include
int main(){
FILE *fp;
char ch;
if((fp=fopen("file.txt","w"))==NULL){
printf("cannot open this file\n");
exit(0);
}
else{
printf("file open successful--writeonly\n");
}
printf("Input character from keybord and end input with charater'#'\n");
while(1){
ch=getchar();
//填空
//填空
}
fputc(0x0d,fp);
fputc(0x0a,fp);
if(fclose(fp)==0){
printf("file write successful and closed\n");
}
if((fp=fopen("file.txt","r"))==NULL){
printf("cannot open this file\n");
exit(0);
}
else{
printf("file open successful--readonly\n");
}
printf("Input character from keybord and end input with charater'#'\n");
while(1){
ch=fgetc(fp);
if(ch!=EOF)
printf("%c",ch);
else
break;
}
if(fclose(fp)==0){
printf("file read sucessful and closed\n");
}
return 0;
}
4.编写一个程序f4.c,实现文件改名操作。
#include
int main(int argc,char **argv){
if(argc<3){
printf("Usage: %s old_name new_name\n",argv[0]);
return 0;
}
printf("%s=>%s",argv[1],argv[2]);
if(rename(argv[1],argv[2])<0)
printf("error!\n");
else
printf("ok!\n");
return 0;
}
编写一个程序f5.c,实现改变文件拥有者。
#include
#include
main(){
chown("a.txt",0,0);
}
编写一个程序f6.c,实现修改文件的访问权限
#include
#include
main(){
chmod("a.txt",S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
}
编写一个程序f7.c,读取一个目录中的内容。(修改代码,执行时读取指定的某个目录)
#include
struct dirent *readdir(DIR *dir);
struct dirent {
long d_ino;//文件Inode号
off_td_off;//文件在目录文件中位置偏移
unsigned short int d_reclen;//文件名长
unsigned char d_type;//文件类型
char d_name[256];//文件名
};
程序代码:
#include
#include
#include
main(){
DIR * dir;
struct dirent * ptr;
int i;
dir =opendir("/etc/rc.d");
while((ptr = readdir(dir))!=NULL) {
printf("d_name: %s\n",ptr->d_name);
}
closedir(dir);
}
编写一个程序f8.c,读取一个目录中的内容及其子目录中的内容。(修改代码,执行时读取指定的某个目录)
# include
# include
# include
# include
# include
# include
int re_readdir(char name[]){
char str[1000] = {0};
strcpy(str, name);
printf("%s\n", str);
DIR *dir = opendir(str);
if(NULL == dir){
perror("opendir");
return -1;
}
struct dirent *ent = readdir(dir);
while(NULL != ent){
strcpy(str,name);
printf("name = %s: type = %d\n", ent->d_name, ent->d_type);
if(4 == ent->d_type && strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..")){
re_readdir(strcat(strcat(strcat(str,"/"), ent->d_name),"/"));
}
ent = readdir(dir);
}
closedir(dir);
return 0;
}
int main(void){
re_readdir("/boot");
return 0;
}
编写一个程序f9.c,将文件ttt的文件权限变成所有者,同组的人有读写权限,其他用户只有读的权限。
#include
#include
#include /* for chmod */
#include /* for chmod */
int main(){
system("touch ttt; ls -l |grep ttt");
chmod("./ttt",S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
system("ls -l |grep ttt");
return 0;
}
编写一个程序f10.c,获取文件的相关信息。
Linux stat函数:
表头文件:
#include
#include
#include
#include
#include
int main(){
struct stat buf;
stat("/etc/hosts", &buf);
printf("/etc/hosts file size = %d, last update time is %s\n", buf.st_size,ctime(&buf.st_mtime));
return 0;
}
struct stat {
dev_t st_dev; //文件使用的设备号
ino_t st_ino; //节点
mode_t st_mode; //文件的类型和存取的权限
nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; //文件字节数(文件大小)
unsigned long st_blksize; //块大小(文件系统的I/O 缓冲区大小)
unsigned long st_blocks; //块数
time_t st_atime; //最后一次访问时间
time_t st_mtime; //最后一次修改时间
time_t st_ctime; //最后一次改变时间(指属性)
};
先前所描述的st_mode 则定义了下列数种情况:
S_IFSOCK 0140000 socket
S_IFLNK 0120000 符号连接
S_IFREG 0100000 一般文件
S_IFDIR 0040000 目录
S_IFCHR 0020000 字符
S_IFIFO 0010000 先进先出
S_IRUSR(S_IREAD) 00400 文件所有者具可读取权限
S_IWUSR(S_IWRITE)00200 文件所有者具可写入权限
S_IXUSR(S_IEXEC) 00100 文件所有者具可执行权限
S_IRGRP 00040 用户组具可读取权限
S_IWGRP 00020 用户组具可写入权限
S_IXGRP 00010 用户组具可执行权限
S_IROTH 00004 其他用户具可读取权限
S_IWOTH 00002 其他用户具可写入权限
S_IXOTH 00001 其他用户具可执行权限
上述的文件类型在POSIX中定义了检查这些类型的宏定义:
S_ISLNK (st_mode) 判断是否为符号连接
S_ISREG (st_mode) 是否为一般文件
S_ISDIR (st_mode) 是否为目录
S_ISCHR (st_mode) 是否为字符文件
S_ISBLK (s3e) 是否为先进先出
S_ISSOCK (st_mode) 是否为socket
编写一个程序f11.c,创建一个文件。
#include
#include
#include
#include
int main(void){
int fd = creat("creat.txt",S_IRUSR|S_IWUSR|S_IXUSR);
system("ls creat.txt -l");
return EXIT_SUCCESS;
}
编写一个程序f12.c,创建3个软连接,一个指向正常的文件,一个指向自己,一个指向不存在的文件。
格式:int symlink(const char *oldpath, const char *newpath);
代码:
#include
#include
#include
int main(){
if(symlink("read","read_link") == -1){
perror("read symlink ");
exit(1);
}
if(symlink("invalid","in_link") == -1){
perror("invalid symlink ");
exit(1);
}
if(symlink("self_link","self_link") == -1){
perror("self symlink ");
exit(1);
}
system("ls *link -l");
return 0;
}
编写一个程序f13.c,创建一个空的文件,然后创建一个指向它的硬连接,接着向链接文件中写入数据。
格式:int link(const char *oldpath, const char *newpath);
代码:
#include
#include
#include
#include
#include
#include
#include
int main(){
system("touch read");
link("read","read_link");
int fd = open("read_link",O_WRONLY);
char *str = "hello world!\n";
int ret = write(fd,str,strlen(str));
if(ret == -1){
perror("write ");
exit(1);
}
system("cat read; rm read; ls read_link -l");
return 0;
}
上述代码的执行结果,说明:
实验目的:
利用Linux提供的系统调用设计程序,加深对进程概念的理解。体会系统进程调度的方法和效果。
实验内容: 编写使用系统调用的C 语言关于进程的程序,并分析运行结果。
一、用户标识(UID)和有效用户标识(EUID)
使用getuid函数和geteuid函数来获取当前进程的用户标识和有效用户标识
程序p1.c
#include
#include
#include
#include
int main(void){
FILE *fp;
char ch;
printf("Current process UID:%ld\n",(long)getuid());
printf("Current process EUID:%ld\n",(long)geteuid());
fp=fopen("abc.txt","r");
while ((ch=fgetc(fp))!= EOF)
putchar(ch);
return 0;
}
由root用户在/home/um1/文件夹下建立文件abc.txt,并写入一些内容,保存,设置该文件权限为400,查看Linux用户,并分别以um1用户运行和root用户运行上面程序。
二、fork函数
通过fork函数并判断函数返回值,根据返回值来判断是在父进程还是子进程。
程序p2.c
#include
#include
#include
int main(void){
pid_t pid;
if((pid=fork())<0){
printf("Cannot create the new process\n");
return 1;
}
else if(pid==0){
printf("In the child process!\n");
return 0;
}
else{
printf("In the parent process!\n");
return 0;
}
}
调用fork函数后,其后代码会被父子进程分别执行。
程序p3.c
#include
#include
#include
int main(void)
{
fork();
printf("Will be executed twice\n");
return 0;
}
程序p0.c
#include
#include
#include
int main(void)
{
int i;
for(i=0; i<3; i++){
fork();
printf("-%d\n",getpid());
}
return 0;
}
三、vfork函数
程序p4.c
#include
#include
#include
int g_var=0;
int main(void)
{
pid_t pid;
int var=1;
printf("process id:%d\n",(long)getpid());
printf("before execute the fork system call,g_var=%d var=%d\n",g_var,var);
if((pid=fork())<0)
{
printf("Cannot create a new process");
return 1;
}
else if(pid==0)
{
g_var++;
var++;
printf("process id1:%d,g_var=%d var=%d\n",(long)getpid(),g_var,var);
_exit(0);
}
printf("process id2:%d,g_var=%d var=%d\n",(long)getpid(),g_var,var);
return 0;
}
由上可知, 。
修改上面程序,将fork函数的语句进行替换,使用vfork函数,将代码保存并运行:
程序p5.c
#include
#include
#include
int g_var=0;
int main(void)
{
pid_t pid;
int var=1;
printf("process id:%d\n",(long)getpid());
printf("before execute the fork system call,g_var=%d var=%d\n",g_var,var);
if((pid=vfork())<0)
{
printf("Cannot create a new process");
return 1;
}
else if(pid==0)
{
g_var++;
var++;
printf("process id1:%d,g_var=%d var=%d\n",(long)getpid(),g_var,var);
_exit(0);
}
printf("process id2:%d,g_var=%d var=%d\n",(long)getpid(),g_var,var);
return 0;
}
由上可知, 。
四、exec函数族
execvp函数支持参数列表,使用参数列表将使程序获得更大的灵活性,程序通过读取agrv中的参数,实现对输入的shell命令的执行。
程序p6.c
#include
#include
int main(int argc,char *argv[])
{
if(argc<2)
{
printf("Usage: %s path\n",argv[0]);
return 1;
}
execlp("/bin/ls","ls",argv[1],(char *)NULL);
return 0;
}
程序的功能是调用shell命令中的“ls”,例如在linux终端:
(1)只输入一位main函数参数:“p6”
[root@BC test]# ./p6
其中path为文件或者目录的路径,而“./p6”则比拟为“ls”
(2)输入一个文件或目录的路径
[root@BC test]# ./p6 /home/bc
(3)此程序与“ls”区别是:要显示当前目录的内容,“ls”只输入“ls”然后回车即可;而此程序需要输入“./p6 ./”,即要加上“./”当前目录路径。
五、exit 函数
使用atexit注册了一个在进程退出时的处理函数,该处理函数只是显示一段文字信息。Main函数退出时将调用exit函数,这样进程就会在退出时自动调用atexit注册的函数。
程序p7.c
#include
#include
#include
void do_at_exit(void)
{
printf("You can see the output when the progrom terminates\n");
}
int main()
{
int flag;
flag=atexit(do_at_exit);
if(flag!=0)
{
printf("Cannot set exit function\n");
return EXIT_FAILURE;
}
exit(EXIT_SUCCESS);
printf("OK"); /*检验exit()直接调用atexit注册函数退出*/
}
运行结果:
[root@BC test]# ./p7
由上面程序可知, atexit注册函数成功返回值为0,所以flag的值为0,exit()直接调用atexit注册函数退出
_exit函数
下面的程序和上面的除了退出函数不同,其他都一样。不同的是使用_exit函数退出时,不会执行atexit中注册的处理函数。
程序p8.c
#include
#include
#include
void do_at_exit(void)
{
printf("You can see the output when the progrom terminates\n");
}
int main()
{
int flag;
flag=atexit(do_at_exit);
if(flag!=0)
{
printf("Cannot set exit function\n");
return EXIT_FAILURE;
}
_exit(EXIT_SUCCESS);
}
运行结果:
[root@BC test]# ./p8
执行_exit函数与exit函数,内核都会将打开的文件关闭,将使用的内存地址空间释放,但前者不同是不处理打开的流缓冲区。
六、kill 函数发送信号
直接用kill函数给进程发送结束信号或是让进程自动退出。
程序p9.c
#include
#include
#include
#include
int main(int argc,char*argv[])
{
int h; /*定义一个变量,便于观察if语句的判断*/
pid_t pid;
int exit_code;
pid=getpid();
printf("%d\n",pid);
srand((unsigned)pid);
/*以pid产生随机数种子,默认随机数种子是1,如每次seed设相同值,*/
/*rand()所产生的随机数值每次就会一样*/
exit_code=(int)(rand()%256); /*exit_code的十六进制值不超过ff*/
sleep(1);
h=atoi(*(argv+1))%2;
/*atoi()把字符转换为整型,参数为字母时返回0,h的值为0或1*/
printf("%d\n ",h);
if(h) /*h=1时,执行语句*/
{
printf("the child process id:%d recive signal SIGKILL\n",pid);
kill(pid,9);
}
else
{
printf("the child process id:%d normally exit_code is %0x\n",pid,exit_code);
exit(exit_code);
}
}
运行结果:
[root@BC test]# ./p9 4
the child process id:2536 normally exit_code is 25
[root@BC test]# ./p9 5
the child process id:2537 recive signal SIGKILL
Killed
[root@BC test]# ./p9 a
the child process id:**** normally exit_code is c1
由上可知,程序输入的第二参数argv[1],
。
下面程序通过fork函数创建子程序,并调用execl函数执行上面的程序。为方便了解程序运行的情况,在父进程中显示了创建的子进程的进程号。在while循环中调用wait函数,跳出条件是wait函数返回值小于0,或wait_pid为-1。这种情况下,所有的子进程都已经完全退出。
程序p10.c
#include
#include
#include
#include
int main(int argc,char*argv[])
{
pid_t pid,wait_pid;
int status;
int i;
if(argc<4)
{
printf("Usage:%s para1 para2 para3\n",argv[0]);
return 1;
}
for(i=1;i<4;i++)
if((pid=fork())==0)
execl("./p10","p10",argv[i],NULL);
else
printf("create the child process id:%d\n",pid);
while((wait_pid=wait(&status))&&wait_pid!=-1)
printf("process id:%d exit,exit_code is %0x\n",wait_pid,status);
return 0;
}
运行结果:
(1)[root@BC test]# ./p10 dfdf dfd
(2)[root@BC test]# ./p10 1 2 a
分析运行结果,说明执行流程:
七、waitpid函数
使用waitpid等待SIGSTOP,SIGCONT和SIGKILL这三种信号。Waitpid第三参数为0时,父进程阻塞,为WNOHANG时,如果pid指定的子进程没有结束,则waitpid()函数立即返回0,而不是阻塞在这个函数上等待;如果结束了,则返回该子进程的进程号。
为WUNTRACED时,如果子进程进入暂停状态,则马上返回。。
程序p11.c
#include
#include
#include
#include
int main(void)
{
pid_t pid,wait_pid;
int status;
pid=fork();
if(pid==-1)
{
perror("Cannot creat new process");
exit(1);
}
else
if(pid==0)
{
printf("child process id:%d\n",(long)getpid());
alarm(1); // 给一个时钟信号,终止子进程
pause();
puts("pause"); // 检查_exit()有没有执行
_exit(0);
}
else{
do
{
wait_pid=waitpid(pid,&status,0); // 此时pid>0,等待子进程结束
if(wait_pid==-1){
perror("cannot using waitpid function\n");
exit(1);
}
if(WIFEXITED(status)) //子进程正常终止,则返回真
printf("child process exites,status=%d\n",WEXITSTATUS(status));
if(WIFSIGNALED(status)) //子进程由信号终止,则返回真
printf("child process is killed by signal %d\n",WTERMSIG(status));
if(WIFSTOPPED(status)) //信号导致子进程停止,则返回真
printf("child process is stopped by signal %d\n",WSTOPSIG(status));
//if(WIFCONTINUED(status)) //信号导致子进程继续执行,则返回真
// printf("child process resume running...\n");
}
while(!WIFEXITED(status)&&!WIFSIGNALED(status));
//信号或正常终止,则跳出循环
exit(0);
}
}
运行结果:[root@BC test]# ./p11
(1)如果去掉第十九行与二十行代码
…
//alarm(1);
//pause();
…
运行结果:[root@BC test]# ./p11
使用alarm函数定时,然后通过pause()等待alarm函数的信号,pause函数使调用进程挂起直至捕捉到这个信号。对比执行结果,说明
alarm()函数-给闹钟定义处理函数。(定时1次)
程序p111.c
#include
#include
#include
#include
void handler()
{
printf("hello\n");
}
void main()
{
int i;
signal(SIGALRM, handler);
alarm(3);
for(i = 1; i < 7; i++)
{
printf("sleep %d ...\n", i);
sleep(1);
}
}
setitimer函数-定义精确定时。(定时多次)
程序p112.c
#include //printf()
#include //pause()
#include //signal()
#include //memset()
#include //struct itimerval, setitimer()
static int count = 0;
void printMes(int signo)
{
printf("Get a SIGALRM, %d counts!\n", ++count);
}
int main()
{
int res = 0;
struct itimerval tick;
signal(SIGALRM, printMes);
memset(&tick, 0, sizeof(tick));
//Timeout to run first time
tick.it_value.tv_sec = 1;
tick.it_value.tv_usec = 0;
//After first, the Interval time for clock
tick.it_interval.tv_sec = 1;
tick.it_interval.tv_usec = 0;
if(setitimer(ITIMER_REAL, &tick, NULL) < 0)
printf("Set timer failed!\n");
while(1)
{
pause();
}
return 0;
}
其中,which为定时器类型,3中类型定时器如下:
ITIMER_REAL : 以系统真实的时间来计算,不管它是否在运行,计时到,它送出SIGALRM信号。
ITIMER_VIRTUAL : 以该进程在用户态下运行时时间来计算,它送出SIGVTALRM信号。
ITIMER_PROF : 以该进程在用户态下和内核态下运行时时间来计算,它送出SIGPROF信号。
第二个参数指定间隔时间,第三个参数用来返回上一次定时器的间隔时间,如果不关心该值可设为NULL。
it_interval指定间隔时间,it_value指定初始定时时间。如果只指定it_value,就是实现一次定时;如果同时指定 it_interval,则超时后,系统会重新初始化it_value为it_interval,实现重复定时;两者都清零,则会清除定时器。
tv_sec提供秒级精度,tv_usec提供微秒级精度,以值大的为先,注意1s = 1000000us。
如果是以setitimer提供的定时器来休眠,只需阻塞等待定时器信号就可以了。
setitimer()调用成功返回0,否则返回-1。
八、信号signal函数
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signal函数
作用1:站在应用程序的角度,注册一个信号处理函数
作用2:忽略信号,设置信号默认处理
参数:
–signal是一个带signum和handler两个参数的函数,准备捕捉或屏蔽的信号由参数signum给出,接收到指定信号时将要调用的函数有handler给出。
–handler这个函数必须有一个int类型的参数(即接收到的信号代码),它本身的类型是void
–handler也可以是下面两个特殊值:① SIG_IGN 屏蔽该信号② SIG_DFL 恢复默认行为
p12.c
//忽略,屏蔽信号
#include
#include
#include
#include
#include
#include
#include
int main(int arg, char *args[])
{
pid_t pid=fork();
if(pid==-1)
{
printf("fork() failed! error message:%s\n",strerror(errno));
return -1;
}
//注册信号,屏蔽SIGCHLD信号,子进程退出,将不会给父进程发送信号
signal(SIGCHLD,SIG_IGN);
if(pid>0)
{
printf("father is runing !\n");
sleep(10);
}
if(pid==0)
{
printf("i am child!\n");
exit(0);
}
printf("game over!\n");
return 0;
}
p13.c
//恢复信号
#include
#include
#include
#include
#include
#include
#include
void catch_signal(int sign)
{
switch (sign)
{
case SIGINT:
printf("ctrl + C is executed!\n");
//exit(0);
break;
}
}
int main(int arg, char *args[])
{
//注册终端中断信号
signal(SIGINT, catch_signal);
char tempc = 0;
while ((tempc = getchar()) != 'a')
{
printf("tempc=%d\n", tempc);
//sleep()
}
//恢复信号
signal(SIGINT, SIG_DFL);
while (1)
{
pause();
}
printf("game over!\n");
return 0;
}
p14.c
//sleep 函数
#include
#include
#include
#include
#include
#include
#include
#include
void catch_signal(int sign)
{
switch(sign)
{
case SIGINT:
printf("accept signal!\n");
break;
default:
break;
}
}
int main(int arg,char *args[])
{
//注册信号
if(signal(SIGINT,catch_signal)==SIG_ERR)
{
perror("signal error");
return-1;
}
int num=0;
num=sleep(100);
//手动执行 ctrl+C 进程被唤醒
if(num>0)
{
printf("sleep()函数被打算睡眠,醒过来了!\n");
}
//说明:sleep()函数是可中断睡眠
printf("新的测试!\n");
//再次设计sleep()函数,让其sleep需要的时间
num=15;
do{
num=sleep(num);
printf("被唤醒了,但是还要继续睡眠!剩余时间%d\n",num);
}while(num);
printf("game over!\n");
return 0;
}