日志标签 ‘C编程’

IPC under Linux-Shared Memory

2010年8月27日

还记得在上篇文章中我们说到的N个子进程独自访问自己的临界区吗,那么今天要说的共享内存就可以实现多个进程都去访问一块内存et2 如果你对我们前面所说的几种IPC方式都了解,那么关于共享内存的基本知识你就有似曾相识的感觉。我们快速入门吧 e91

1.如何创建一块共享内存?

其实列出函数原型,你应该会知道每个参数的含义。

int shmget(key_t key,size_t size,int shmflg);

这里要说明的是size的大小,如果创建一块新的共享内存区,那么size必须大于0;如果size为0,那此时shmget函数的功能就是打开一个已存在的共享内存区(当然,key代表的内存区是否存在那是二话)。size为负,那么调用出错,返回-1。另外shmflg的取值以及用法都与信号量相同,可参考前面的文章。

当shmget调用成功后,返回整形id。

2.共享内存创建好了,进程如何使用?

我们创建好了内存块,但是创建进程此时和这块内存还没联系。所以,我们应该使用shmat函数将其附加到进程的地址空间(具体位于进程的堆栈段)。

void* shmat(int shmid,const void* shmaddr,int shmflg);

一般我们将shmaddr置为NULL,因为此时内存会自动分配一段何时的内存块。并且最终shmat函数将返回这个内存块的首地址。也许你对此处的void类型比较不解,其实void* shmaddr这样的定义方式其实是为了灵活使用:首先shmaddr是一个指针类型(内存地址),但其具体是指向何种类型的指针,在此并不具体说明。因为当你要使用这个指针时,可以用任何类型强制转换。

与此函数对应的是shmdt函数,它会让调用进程与shmaddr指向的共享内存脱离。

3.对共享内存的控制?

int shmctl(int shmid,int cmd,struct shmid_ds *buf);

cmd可取值:IPC_RMID,IPC_SET,IPC_STAT分别对应:删除共享内存区,设置共享内存区的shmid_ds结构,获取shmid_ds结构。这里不再赘述具体使用方法,可参考man手册。

介绍完这些,我们的重点就是要学会应用共享内存。《linuxC编程实战》一书中的例10-17非常经典,可以模拟进程之间的同步。该例还特别的定义了一个头文件shm.h,此头文件中加入了许多对信号量以及共享内存的操作函数。现在我们具体来分析,哪些语句实现进程的互斥,哪些语句实现的是进程的同步。

首先,不管是读进程,还是写进程,PV操作之间的代码都属于临界区。临界区的代码都是对共享内存去进行操作,所以应该加上PV操作,避免读写进程交互对共享内存进行访问。所以读写程序中的PV操作实现的是进程的互斥。

我们还可以发现,读写进程代码中都有waitsem(semid,0)语句。以写进程为例,每次写内存时,都要查看信号量(semid信号量集中的0号信号量)是否为1。如果为1,说明此时没有读进程访问共享内存;否则,说明有读进程在访问,此时写进程本应该阻塞,但是这里我们用sleep(1)来模拟进程的阻塞,而且不断的去测试信号量是否为1。

如果写进程成功进入临界区,那么它将写数据至共享内存。可能你对于strcpy(shmaddr,wbuf);这样的语句感到困惑:怎么能将wbuf赋值给共享内存?!其实仔细想想,这没有什么不合理。shmaddr是一个指针,wbuf是一个字符串数组的首字符的首地址,那么strcpy函数所做的就是很平常的事了。同时,我们也可以这么想:一般我们都是提前用malloc来分配内存空间,再让某个指针指向这片内存空间,而现在,上面的shmget以及shat以及完成了类似malloc的工作。

其实我们有这样的想法很自然,毕竟刚开始学习,通过实践可以从感性上认识这些。

那么很显然,waitsem(semid,0)语句实现的便是读写进程的同步问题——协调读写进程对共享内存的访问。

//写进程部分代码
	while(1)
	{
                waitsem(semid,0);
		p(semid,0);
		printf("writer:");
		fgets(wbuf,1024,stdin);
		wbuf[strlen(wbuf)-1]='\0';
		strcpy(shmaddr,wbuf);
		my_sleep(5);
		v(semid,0);
		my_sleep(3);
	}
//读进程部分代码
	while(1)
	{
                waitsem(semid,0);
		p(semid,0);
		printf("reader:");
		printf("%s\n",shmaddr);
		my_sleep(5);
		v(semid,0);
		my_sleep(3);
	}

理解了这个程序要干什么,那么我们再来想想它为什么要这么做:读写进程中为何要加入sleep函数?那我们来做几个测试吧。在测试之前,建议大家适当的修改waitsem函数和my_sleep函数,它可以让我们更好的感知当前进程的“死活”。

int waitsem(int semid,int index)
{
	while(semctl(semid,index,GETVAL,0)==0)
	{
		sleep(1);
		printf("I am waiting ..\n");
	}
	return 1;
}
void my_sleep(int i)
{
	while(i--)
	{
		printf("writer is sleeping..\n");//写进程中的my_sleep做相应改动
		sleep(1);
	}
}

这样增加了一些提示语,再分别在两个终端运行读写进程,可以发现第一个my_sleep函数的作用就是故意延长读(写)进程在临界区的访问时间,以使得写(读)进程阻塞。而第二个my_sleep函数则是故意阻塞写(读)进程,让读(写)进程有时间去读共享内存。好了,既然现在掌握了信号量和共享内存,那么我们就可以试着去完成一些简单的同步互斥的题目了,继续加油吧 e49 e49 e49

信号量集实现进程互斥

2010年8月26日

本文为大家呈现的代码可以实现进程的互斥。下面代码省去了pv操作的的具体定义以及union semun的定义(可参考前文)。本程序前半部分是一些初始化工作:生成key,创建信号量集,设置信号量初值。接下来的代码依次生成N个子进程,具体数目由运行时的参数决定。

现在我们要关注的是,这几个子进程如何互斥的访问临界区?这是本问要说明的重点。请先看本程序加PV操作和不加PV操作的结果:

edsionte@edsionte-laptop:~/code/IPC$ ./pv 3
===process 2164 enter the critical section===
===process:2164 is accessing=================
===process 2164 leave the critical section===
===process 2165 enter the critical section===
===process:2165 is accessing=================
===process 2165 leave the critical section===
===process 2166 enter the critical section===
===process:2166 is accessing=================
===process 2166 leave the critical section===
//不加PV
===process 2175 enter the critical section===
===process 2176 enter the critical section===
===process 2177 enter the critical section===
===process:2177 is accessing=================
===process:2176 is accessing=================
===process:2175 is accessing=================
===process 2176 leave the critical section===
===process 2177 leave the critical section===
===process 2175 leave the critical section===

可以看到加入pv操作后,子进程在访问临界区时都不受子其他进程的影响。

可是仔细想一下,这个程序真的是在演示多个进程互斥访问同一个临界区吗?当然不是,其实从程序中的fork函数就可以发现。因为fork后的子进程代码段,数据段等都是父进程的副本,因此下面的程序根本不存在所谓的多个进程同时访问一个临界区。其实就是每个进程在唱自己的独角戏(各进程访问属于自己的临界区,一对一)而已。

但是,另一个事实又摆在我们面前:如果不加PV,那么很明显每个进程访问临界区又是交互运行的。其实我们可以这样想,对于单个子进程A而言,即便没有其他进程要竞争A进程要访问的那个临界区,那么A进程要也要装作周围有很多进程那样——在访问临界区的时候不受其他进程的干扰——只不过A进程现在只是独自一人去遵守那个规则而已。我们可以在V操作后再加一句话:

			sleep(2);
			printf("===process %d is doing another thing=======\n",getpid());

再次运行,你可以发现这句话并不总是紧挨在临界区之后就被执行的,因为出了临界区,这几个子进程就是普通的交互运行而已。

所以,虽然这并不是我们以前熟悉的那个多进程互斥使用一个临界区的场景,但也可以体现进程对临界区的互斥访问。这样说来可能会有点绕,但是仔细想想也应该会理解。

那么如何实现多个进程互斥访问一个临界区?你也许会想到vfork函数,可是比较糟糕的是vfork后,父进程在子进程退出前总是阻塞,这样并不适合我们这里依次生成多个子进程的情况。如果还感到困惑,那么也没关系,不妨在学习了共享内存之后,再来理解本文。用共享内存才可以实现多个进程都访问一个内存区

int main(int argc,char** argv)
{
	int proj_id;
	int semid;
	union semun arg;
	pid_t pid;
	key_t key;
	int num;
	int i,j;

	if(argc!=2)
	{
		printf("error:%s num\n",argv[0]);
		return -1;
	}

	num=atoi(argv[1]);

	//create key
	proj_id=2;
	if((key=ftok(".",proj_id))==-1)
	{
		printf("generating IPC key failed\n");
		return -1;
	}

	//create a semaphore set
	if((semid=semget(key,1,IPC_CREAT|0666))==-1)
	{
		printf("creating semaphore set failed\n");
		return -1;
	}

	arg.val=1;
	if(semctl(semid,0,SETVAL,arg)==-1)
	{
		printf("set semval failed\n");
		return -1;
	}

	for(i=0;i<\num;i++)
	{
		pid=fork();
		if(pid<0)
		{
			printf("creating new process failed\n");
			return -1;
		}
		else if(pid==0)
		{
			if((semid=semget(key,1,0))==-1)
			{
				printf("geting semid failed in the child process\n");
				return -1;
			}

			p(semid,0);
			printf("===process %d enter the critical section===\n",getpid());
           		sleep(1);
			printf("===process:%d is accessing=================\n",getpid());
			sleep(1);
			printf("===process %d leave the critical section===\n",getpid());
			sleep(1);
			v(semid,0);

			return 0;
		}
	}

	for(i=0;i<\num;i++)
	{
		wait(NULL);
	}

	if(semctl(semid,0,IPC_RMID,0)==-1)
	{
		printf("remove the sem set failed\n");
		return -1;
	}

	return 0;
}

shell管道重定向实现

2010年8月24日

如果你实现过my_shell.c,那么对管道重定向应该有印象。但是本文中所述的管道重定向,将采用最近我们学习的管道相关系统调用函数来实现。随后,也将和大家一起再去回顾当初my_shell.c中是如何实现管道重定向的

对于管道符号,这里只做简单的说明:管道符前命令的输出作为管道符后命令的输入。对于一般命令而言,输入均来自标准输入,而输出则至标准输出。但是为了实现管道重定向,我们先创建管道,然后将管道写端重定向到标准输出,将管道读端重定向到标准输入。当然这两次重定向分别在不同的进程中完成。具体代码实现可参考下面的代码片段。

	if(pipe(fd)==-1)
	{
		printf("my_pipe:can't create a pipe\n");
		exit(1);
	}

	pid=fork();
	if(pid==0)
	{
		close(fd[0]);
		dup2(fd[1],STDOUT_FILENO);
		close(fd[1]);

		if(execlp(argv[1],argv[1],NULL)==0)
		{
			printf("my_pipe:can't execute the first command in the child process\n");
			exit(1);
		}
	}
	else if(pid>\0)
	{
		close(fd[1]);
		dup2(fd[0],STDIN_FILENO);
		close(fd[0]);

		if(execlp(argv[2],argv[2],NULL)==0)
		{
			printf("my_pipe:can't execute the second command in the parent process\n");
			exit(1);
		}
	}
	else
	{
		printf("my_pipe:can't create a new process\n");
		exit(1);
	}

上述代码所实现的管道重定向只支持没有参数的命令,比如./my_pipe ls wc(相当于命令ls | wc)。如果想要使用命令参数,可以在次基础上继续完善。

可以发现,管道是作为前后两个命令的数据缓冲区,各命令的相关输入或输出只是“私下”操作。既然理解了上述代码,那么my_shell.c中的管道的实现代码也就一目了然了。

首先,创建一个子进程,然后在子进程中创建子子进程(pid2=fork())。其实无非是让这两个子进程去分别执行管道符前后的命令。这里应该注意的是此段代码是如何“模拟”管道的。我们可以发现,其实并没有真正的pipe一个管道,而是创建了一个实实在在tempfile文件而已,最后使用完毕再悄悄删除次文件。

再看具体是如何重定向的,在子子进程中,以写方式打开”管道”,将tempfile定向到标准输出。在子进程中,以读方式打开”管道”,将tempfile定向到标准输入。

看到这里,你应该已经发现,这里所用到的原理其实和一开始我们所述的方法是一致的。

			if(pid==0)//child_process
			{
				int pid2;
				int status2;
				int fd2;

				if((pid2=fork())<\0)
				{
					printf("fork2 error\n");
					return;
				}
				else if(pid2==0)//child_child_process
				{
					if(!(find_command(arg[0])))
					{
						printf("%s: command not found\n",arg[0]);
						exit(0);
					}
					fd2=open("/tmp/tempfile",O_WRONLY|O_CREAT|O_TRUNC,0644);
					dup2(fd2,1);
					execvp(arg[0],arg);
					exit(0);
				}

				if(waitpid(pid2,&status2,0)==-1)//waiting for child_child_process
				{
					printf("wait for child_child process error\n");
				}

				if(!(find_command(argnext[0])))
				{
					printf("%s:command not found\n",argnext[0]);
					exit(0);
				}

				fd2=open("/tmp/tempfile",O_RDONLY);
				dup2(fd2,0);
				execvp(argnext[0],argnext);

				if(remove("/tmp/tempfile"))
				{
					printf("remove error\n");
				}
				exit(0);
			}

			

实现cp命令(8)

2010年8月10日

问题不断的文件权限!

在实现cp命令(6)(以下简称cp(6))中我们加入了-p选项,即使用cp命令时加入-p才会将源文件的属性复制给目的文件,否则只拷贝文件中的内容。但是我们看看下面cp的运行结果。

cp(6)中不是说只有加-p才会复制源文件的属性吗?怎么cao.c和源文件的属性一模一样?

gues@huangwei-desktop:~/code/shell_command$ ls -l ch222.c
-rwxr--r-- 1 gues gues  5327 2010-08-09 20:45 ch222.c
gues@huangwei-desktop:~/code/shell_command$ cp ch222.c cao.c
gues@huangwei-desktop:~/code/shell_command$ ls -l cao.c
-rwxr--r-- 1 gues gues 5327 2010-08-09 20:54 cao.c

为什么此时目的文件的属性又是644(cp(6)中说644是新建文件的默认属性)?

gues@huangwei-desktop:~/code/shell_command$ ls -l changemode.c
-rw-rw-r-- 1 gues gues  5327 2010-08-09 10:31 changemode.c
gues@huangwei-desktop:~/code/shell_command$ cp changemode.c wo.c
gues@huangwei-desktop:~/code/shell_command$ ls -l wo.c
-rw-r--r-- 1 gues gues 5327 2010-08-09 20:56 wo.c

起初,当我发现上述结果后,脑中有数个为什么。为什么复制文件的时候,文件属性会这么多变?不断出问题?其实,是因为我们忽略了文件屏蔽字:umask。在新建文件或者目录的时候,新文件的实际存取权限是mode&~umask。比如用open创建(或打开)文件,那mode就是open函数的第三个参数。既然这样,那我们来检验一下上述命令。通过输入umask命令,我们可以看到当前屏蔽字为022。与上述源文件进行mode&~umask运算后,刚好和目的文件的属性一致。那么现在我们终于找到问题的原因所在了。

好了,让我们忘掉cp(6)中所说的一切,重新整理思路:

在使用cp命令的时候,当目的文件不存在时,会新建与源文件同名的新文件。这个新文件的实际权限由:mode&~umask运算后的结果来决定。此时的mode为源文件的权限。而当目的文件存在时,则会保持原目的文件的属性,除非在cp命令中加入-p选项。

好了,我们现在搞清楚cp命令在不加-p选项时候的文件权限问题,至于代码修改,只需修改将open中的第三个参数修改成源文件的权限即可。具体代码实现如下:

	//open the dest file
	if((fdwt=open(dest_path,O_CREAT|O_TRUNC|O_RDWR,src_buf.st_mode))==-1)
	{
		perror("open_destfile");
		exit(1);
	}

实现cp命令(7)

2010年8月9日

一开始,我们先想这样的问题:

我们要执行命:cp -r newdir dir/ 。而在dir/下已经存在了newdir/目录。那么执行了此条命令,会产生什么结果?

my_cp的结果是删除目的newdir目录,将源目录复制到dir/下。如果你对cp足够了解(不了解?trrrrrrrrry),这么做显然是不完美的。cp命令的结果是:不完全的覆盖。具体是,1.对newdir/和dir/newdir/中都存在的文件file,newdir中的file将会覆盖dir/newdir/中的file;2.对于newdir/中存在而dir/newdir中不存在的文件,直接创建新文件进行拷贝;3.对于newdir/中不存在而dir/newdir中存在的文件,此次操作对这个文件没有影响。注意,上述中的覆盖或拷贝只涉及到文件内容,至于文件权限的拷贝,则需添加-p选项(关于此选项可参见这里的文章)。

本文就是要对my_cp进行修改,让其功能和cp命令相同。当前my_cp对上述情况的处理就是直接删除dir/newdir这个原文件夹,然后又新建dir/newdir。当然此时新建的newdir中的文件是和源newdir相同的。

我们的修改方案其实很简单!我们先检查目的目录是否存在;存在时,我们再提取源路径中的最低级目录(存储于lowestdir中)。将其与目的目录连接,再存于temp_dest_path变量中。我们再检查temp_dest_path中的路径是否存在,存在说明我们不能完全的覆盖(如上述举例)!那么我们现在就可以确定temp_dest_path就是我们接下来要用到的目的目录,我们j将其复制到dest_path中即可。如果temp_dest_path不存在,那么直接在目的目录下新建lowestdir这个目录,我们用到mkdir这个函数。

对于上述开始的举例,我们首先提取lowestdir=”newdir”;然后temp_dest_path=”dir/newdir”;从已知中,我们知道temp_dest_path中的路径是存在的,因此我们只是将它里面存储的路径复制到dest_path中即可。
以上所述内容的具体实现详见下面的代码:

			//extract the lowest directory of src path
			int i,k=0;
			char lowestdir[PATH_MAX+1];
			char temp_dest_path[PATH_MAX+1]={'\0'};
			struct stat temp_dest_buf;
			for(i=strlen(src_path)-1-1;i>\0;i--)
			{
				if(src_path[i]=='/')
				{
					i=i+1;
					break;
				}
			}
			for(;i<\strlen(src_path);i++)
			{
				lowestdir[k++]=src_path[i];
			}
			lowestdir[k]='\0';
			strncpy(temp_dest_path,dest_path,strlen(dest_path));
			strncat(temp_dest_path,lowestdir,strlen(lowestdir));

			if(stat(temp_dest_path,&temp_dest_buf)==0)
			{
				strncpy(dest_path,temp_dest_path,strlen(temp_dest_path));
			}
			else
			{
				if(mkdir(temp_dest_path,S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)==-1)
	                  	{
					printf("my_cp:create the directory \"%s\" error.\n",dest_path);
	 		                return ;
	                      	}
				strncpy(dest_path,temp_dest_path,strlen(temp_dest_path));
			}

我们再来看一下修改后的运行结果:

gues@huangwei-desktop:~/code/shell_command$ ls newdir/ -l
总用量 12
drwxr-xr-x 2 gues gues 4096 2010-08-08 21:21 littedir
-rw-r--r-- 1 gues gues  221 2010-08-09 17:05 my_ls.c
-rw-r--r-- 1 gues gues   15 2010-08-09 16:13 srchasthisfile
gues@huangwei-desktop:~/code/shell_command$ ls dir/newdir/ -l
总用量 12
-rw-r--r-- 1 gues gues   15 2010-08-09 17:45 desthasthisfile
-rw-r--r-- 1 gues gues  221 2010-08-09 17:45 hello.c
drwxr-xr-x 2 gues gues 4096 2010-08-09 17:45 littedir
gues@huangwei-desktop:~/code/shell_command$ cp newdir/ -r dir/
gues@huangwei-desktop:~/code/shell_command$ ls dir/newdir/ -l
总用量 20
-rw-r--r-- 1 gues gues   15 2010-08-09 17:45 desthasthisfile
-rw-r--r-- 1 gues gues  221 2010-08-09 17:45 hello.c
drwxr-xr-x 2 gues gues 4096 2010-08-09 17:45 littedir
-rw-r--r-- 1 gues gues  221 2010-08-09 17:47 my_ls.c
-rw-r--r-- 1 gues gues   15 2010-08-09 17:47 srchasthisfile

ok。修改完毕。

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key