课上实践补交
题目二要求:
学习使用stat(1),并用C语言实现
提交学习stat(1)的截图
man -k ,grep -r的使用
伪代码
产品代码 mystate.c,提交码云链接
测试代码,mystat 与stat(1)对比,提交截图
问题探索与解决
首先学习stat(1)指令:使用指令man 1 stat
使用指令man -k stat
使用指令man -k stat | grep 2
伪代码:就是获取stat结构然后打印。
产品代码:
#include#include #include #include #include int main(int argc, char *argv[]) { struct stat sb; if (argc != 2) { fprintf(stderr, "Usage: %s \n", argv[0]); exit(EXIT_FAILURE); } if (stat(argv[1], &sb) == -1) { perror("stat"); exit(EXIT_FAILURE); } printf("File type: "); switch (sb.st_mode & S_IFMT) { case S_IFBLK: printf("block device\n"); break; case S_IFCHR: printf("character device\n"); break; case S_IFDIR: printf("directory\n"); break; case S_IFIFO: printf("FIFO/pipe\n"); break; case S_IFLNK: printf("symlink\n"); break; case S_IFREG: printf("regular file\n"); break; case S_IFSOCK: printf("socket\n"); break; default: printf("unknown?\n"); break; } printf("I-node number: %ld\n", (long) sb.st_ino); printf("Mode: %lo (octal)\n", (unsigned long) sb.st_mode); printf("Link count: %ld\n", (long) sb.st_nlink); printf("Ownership: UID=%ld GID=%ld\n", (long) sb.st_uid, (long) sb.st_gid); printf("Preferred I/O block size: %ld bytes\n", (long) sb.st_blksize); printf("File size: %lld bytes\n", (long long) sb.st_size); printf("Blocks allocated: %lld\n", (long long) sb.st_blocks); printf("Last status change: %s", ctime(&sb.st_ctime)); printf("Last file access: %s", ctime(&sb.st_atime)); printf("Last file modification: %s", ctime(&sb.st_mtime)); exit(EXIT_SUCCESS); }
stat指令与mystat测试比较:
2017-2018-1 20155302 课下实践IPC
共享内存
共享内存允许两个或多个进程共享一定的存储区,因为不需要拷贝数据,所以这是最快的一种IPC。
共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,由IPC为进程创建的一个特殊地址范围,它将出现在该进程的地址空间(这里的地址空间具体是哪个地方?)中。其他进程可以将同一段共享内存连接到自己的地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是malloc分配的一样。如果一个进程向共享内存中写入了数据,所做的改动将立刻被其他进程看到。 共享内存是IPC最快捷的方式,因为共享内存方式的通信没有中间过程,而管道、消息队列等方式则是需要将数据通过中间机制进行转换。共享内存方式直接将某段内存段进行映射,多个进程间的共享内存是同一块的物理空间,仅仅映射到各进程的地址不同而已,因此不需要进行复制,可以直接使用此段空间。#include#include (1)创建或访问共享内存 * int shmget(key_t key,size_t size,int shmflg);(2)附加共享内存到进程的地址空间 * void *shmat(int shmid,const void *shmaddr,int shmflg);//shmaddr通常为NULL,由系统选择共享内存附加的地址;shmflg可以为SHM_RDONLY(3)从进程的地址空间分离共享内存 * int shmdt(const void *shmaddr); //shmaddr是shmat()函数的返回值(4)控制共享内存 * int shmctl(int shmid,int cmd,struct shmid_ds *buf); * struct shmid_ds{ struct ipc_perm shm_perm; … }; cmd的常用取值有:(a)IPC_STAT获取当前共享内存的shmid_ds结构并保存在buf中(2)IPC_SET使用buf中的值设置当前共享内存的shmid_ds结构(3)IPC_RMID删除当前共享内存
代码实例:
建立共享内存并写入数据的程序#include#include #include #include #include #include void get_buf(char *buf){ int i=0; while((buf[i]=getchar())!='\n'&&i<1024) i++;}int main(void){ int shmid; shmid=shmget(IPC_PRIVATE,sizeof(char)*1024,IPC_CREAT|0666); if(shmid==-1) { perror("shmget"); } char *buf; if((int)(buf=shmat(shmid,NULL,0))==-1) { perror("shmat"); exit(1); } get_buf(buf); printf("%d\n",shmid); return 0;}
读取数据的程序
#include#include #include #include int main(int argc,char **argv){ int shmid; shmid=atoi(argv[1]); char *buf; if((int)(buf=shmat(shmid,NULL,0))==-1) { perror("shmat"); exit(1); } printf("%s\n",buf); shmdt(buf); return 0;}
管道
管道的特点:
1、管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道; 2、只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。比如fork或exec创建的新进程,在使用exec创建新进程时,需要将管道的文件描述符作为参数传递给exec创建的新进程。当父进程与使用fork创建的子进程直接通信时,发送数据的进程关闭读端,接受数据的进程关闭写端。 3、单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。 4、数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。(1)在两个程序之间传递数据的最简单的方法是使用popen()和pclose()函数: #includeFILE *popen(const char *command, const char *open_mode); int pclose(FILE *stream);popen()函数首先调用一个shell,然后把command作为参数传递给shell。这样每次调用popen()函数都需要启动两个进程;但是由于在Linux中,所有的参数扩展(parameter expansion)都是由shell执行的,这样command中包含的所有参数扩展都可以在command程序启动之前完成。(2)pipe()函数: #include int pipe(int pipefd[2]);popen()函数只能返回一个管道描述符,并且返回的是文件流(file stream),可以使用函数fread()和fwrite()来访问。pipe()函数可以返回两个管道描述符:pipefd[0]和pipefd[1],任何写入pipefd[1]的数据都可以从pipefd[0]读回;pipe()函数返回的是文件描述符(file descriptor),因此只能使用底层的read()和write()系统调用来访问。pipe()函数通常用来实现父子进程之间的通信。(3)命名管道:FIFO #include #include int mkfifo(const char *fifo_name, mode_t mode);
代码实例:
read端
#include#include #include #include #include #include #define PATH "./fifo" #define SIZE 128 int main() { umask(0); if (mkfifo (PATH,0666|S_IFIFO) == -1) { perror ("mkefifo error"); exit(0); } int fd = open (PATH,O_RDONLY); if (fd<0) { printf("open fd is error\n"); return 0; } char Buf[SIZE]; while(1){ ssize_t s = read(fd,Buf,sizeof(Buf)); if (s<0) { perror("read error"); exit(1); } else if (s == 0) { printf("client quit! i shoud quit!\n"); break; } else { Buf[s] = '\0'; printf("client# %s ",Buf); fflush(stdout); } } close (fd); return 3; }
write端
#include#include #include #include #include #include #include #include #define PATH "./fifo" #define SIZE 128 int main() { int fd = open(PATH,O_WRONLY); if (fd < 0) { perror("open error"); exit(0); } char Buf[SIZE]; while(1) { printf("please Enter#:"); fflush(stdout); ssize_t s = read(0,Buf,sizeof(Buf)); if (s<0) { perror("read is failed"); exit(1); } else if(s==0) { printf("read is closed!"); return 1; } else{ Buf[s]= '\0'; write(fd,Buf,strlen(Buf)); } } return 0; }
FIFO(命名管道)
管道和命名管道的区别:
对于命名管道FIFO来说,IO操作和普通管道IO操作基本一样,但是两者有一个主要的区别,在命名管道中,管道可以是事先已经创建好的,比如我们在命令行下执行
mkfifo myfifo
就是创建一个命名通道,我们必须用open函数来显示地建立连接到管道的通道,而在管道中,管道已经在主进程里创建好了,然后在fork时直接复制相关数据或者是用exec创建的新进程时把管道的文件描述符当参数传递进去。 一般来说FIFO和PIPE一样总是处于阻塞状态。也就是说如果命名管道FIFO打开时设置了读权限,则读进程将一直阻塞,一直到其他进程打开该FIFO并向管道写入数据。这个阻塞动作反过来也是成立的。如果不希望命名管道操作的时候发生阻塞,可以在open的时候使用O_NONBLOCK
标志,以关闭默认的阻塞操作。 FIFO可以说是管道的推广,克服了管道无名字的限制,使得无亲缘关系的进程同样可以采用先进先出的通信机制进行通信。
管道和FIFO的数据是字节流,应用程序之间必须事先确定特定的传输"协议",采用传播具有特定意义的消息。 要灵活应用管道及FIFO,理解它们的读写规则是关键。代码实例:
接收消息:
#include#include #include #include #include #include #include #include #define FIFO "/tmp/fifo.temp1" #define MAXLINE 1024 int main(void) { int fifo, fd; char buf[MAXLINE]; int len; fd_set set; struct timeval tv; int i = 0; unlink(FIFO); //如果FIFO存在,就先删除 if ((fifo = mkfifo(FIFO, O_RDWR)) < 0) //产生一个有名管道 { printf("mkfifo error: %s/n", strerror(errno)); return(0); } if ((fd = open(FIFO, O_RDWR)) < 0) //读写打开有名管道 { printf("open error: %s/n", strerror(errno)); return(0); } FD_ZERO(&set); FD_SET(fd, &set); tv.tv_sec = 5; tv.tv_usec = 0; //超时设置,超过5秒没有信息,就打印超时 while (1) { FD_SET(fd, &set); if ((i = select(fd + 1, &set, NULL, NULL, &tv)) > 0)//检测管道是否信息 { printf("receive data/n"); if (FD_ISSET(fd, &set)) { len = read(fd, buf, MAXLINE);//读取信息 buf[len] = '/0'; printf("buf = %s/n", buf); tv.tv_sec = atoi(buf); tv.tv_usec = 0; } } else if (i == 0) { tv.tv_sec = 5; tv.tv_usec = 0; printf("chaoshi/n"); } else printf("error/n"); } unlink(FIFO); //删除有名管道 return(0); }
发消息:
#include#include #include #include #include #include #define FIFO "/tmp/fifo.temp1" #define MAXLINE 1024 int main(void) { int fifo; char buf[MAXLINE]; int len; int i = 0; strcpy(buf, "10"); if ((fifo = open(FIFO, O_RDWR)) < 0) //读写打开有名管道 { printf("mkfifo error: %s/n", strerror(errno)); return(0); } while (i < 10) { sprintf(buf, "%d", i + 1); len = write(fifo, buf, strlen(buf)); //写入信息到管道中 printf("send len = %d/n", len); sleep(i); i++; } return(0); }
信号
信号是Unix/Linux系统在一定条件下生成的事件。信号是一种异步通信机制,进程不需要执行任何操作来等待信号的到达。信号异步通知接收信号的进程发生了某个事件,然后操作系统将会中断接收到信号的进程的执行,转而去执行相应的信号处理程序。
(1)注册信号处理函数 #include/*typedef void (*sighandler_t)(int); sighandler_t signal(int signum,sighandler_t handler);*/ * void (*signal(int signum, void (*handler)(int)))(int); //SIG_IGN && SIG_DFL * int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);(2)发送信号 #include * int kill(pid_t pid,int sig); //#include * int raise(int sig); //kill(getpid(),sig); * unsigned int alarm(unsigned int seconds); //(#include ) seconds秒后,向进程本身发送SIGALRM信号。(3)信号集 信号集被定义为:typedef struct {unsigned long sig[_NSIG_WORDS];} sigset_t; * int sigaddset(sigset_t *set,int sig); * int sigemptyset(sigset_t *set);
信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。
代码实例:
#include#include void int_handler(int signum){ printf("\nSIGINT signal handler.\n"); printf("exit.\n"); exit(-1);}int main(){ signal(SIGINT, int_handler); printf("int_handler set for SIGINT\n"); while(1) { printf("go to sleep.\n"); sleep(60); } return 0;}
消息队列
消息队列是内核地址空间中的内部链表,通过linux内核在各个进程直接传递内容,消息顺序地发送到消息队列中,并以几种不同的方式从队列中获得,每个消息队列可以用IPC标识符唯一地进行识别。内核中的消息队列是通过IPC的标识符来区别,不同的消息队列直接是相互独立的。每个消息队列中的消息,又构成一个独立的链表。
消息队列克服了信号承载信息量少,管道只能承载无格式字符流。消息队列保存在内核中,是一个由消息组成的链表。
#include#include #include (1)创建或访问消息队列* int msgget(key_t key,int msgflg);(2)操作消息队列 * int msgsnd(int msqid,const void *msg,size_t nbytes,int msgflg);msg指向的结构体必须以一个long int成员开头,作为msgrcv()的消息类型,必须大于0。nbytes指的是msg指向结构体的大小,但不包括long int部分的大小 * ssize_t msgrcv(int msqid,void *msg,size_t nbytes,long msgtype,int msgflg);如果msgtype是0,就返回消息队列中的第一个消息;如果是正整数,就返回队列中的第一个该类型的消息;如果是负数,就返回队列中具有最小值的第一个消息,并且该最小值要小于等于msgtype的绝对值。(3)控制消息队列 * int msgctl(int msqid,int cmd,struct msqid_ds *buf); * struct msqid_ds{ struct ipc_perm msg_perm; … };
代码实例:
#include#include #include #include #define MAX_LINE 80 #define MY_MQ_ID 1233 /*消息结构体的一般形式如下: typedef struct { long type; //用于存放消息代码,必须位于首位 char message[ LENGHT+1 ]; }MSG_TYPE_T; */ typedef struct { long type; float fval; unsigned int uival; char strval[ MAX_LINE+1 ]; }MY_TYPE_T; int main( ) { int msgid,ret; //create the message queue with the id MY_MQ_ID msgid=msgget( MY_MQ_ID,0666|IPC_CREAT ); if( msgid>=0 ) printf( "Created a Message Queue,message queue identifier is %d\n",msgid ); //modify the size of message queue struct msqid_ds buf; ret=msgctl( msgid,IPC_STAT,&buf ); printf( "The origianl size of queue is %d\n",buf.msg_qbytes ); buf.msg_qbytes=4096; ret=msgctl( msgid,IPC_SET,&buf ); if( ret==0 ) printf( "Size sucessfully changed for queue,message queue identifier is %d\n",msgid ); //send a message MY_TYPE_T myMessage; myMessage.type=1L; //消息的类型,msgrcv会用到 myMessage.fval=128.256; myMessage.uival=512; strncpy( myMessage.strval,"This is a test.\n",MAX_LINE ); ret=msgsnd( msgid,( struct msgbuf* )&myMessage,sizeof( MY_TYPE_T ),0 ); //0是消息旗标 if( ret!=-1 ) printf( "Message send successfully.\n" ); //read a message MY_TYPE_T recMessage; ret=msgrcv( msgid,( struct msgbuf* )&recMessage,sizeof(MY_TYPE_T),1,0 );//这个地方Message Type要和欲接受的消息类型相同 if( ret!=-1 ) { printf( "\nRead a message from the queue\n" ); printf( "Message Type:%ld\n",recMessage.type ); printf( "Float value:%f\n",recMessage.fval ); printf( "Uint value:%d\n",recMessage.uival ); printf( "String value:%s\n",recMessage.strval ); } //destroy a message queue ret=msgctl( msgid,IPC_RMID,NULL ); if( ret!=-1 ) printf( "Message queue %d sucessfully removed.\n",msgid ); return 0; }