Archive for the ‘Linux下C编程’ category

软链接和硬链接

20 9 月, 2010

硬链接类似与一个指向文件的指针(但是与文件描述符不同),比如我们通过下面命令:

edsionte@edsionte-laptop:~$ touch file1
edsionte@edsionte-laptop:~$ ln file1 file1hdlink
edsionte@edsionte-laptop:~$ ls -l file1 file1hdlink
-rw-r--r-- 2 edsionte edsionte 0 2010-09-20 22:56 file1
-rw-r--r-- 2 edsionte edsionte 0 2010-09-20 22:56 file1hdlink
edsionte@edsionte-laptop:~$ rm file1
edsionte@edsionte-laptop:~$ ls -l file1hdlink
-rw-r--r-- 1 edsionte edsionte 0 2010-09-20 22:56 file1hdlink

通过ln命令我们为file1文件创建了一个硬链接file1hdlink。通过ls -li 命令我们也可以发现,这两个文件的索引节点,属性以及大小均均是相同的,因此我们可以得出这样的结论:file1和file1hdlink同时指向一个文件(类似指针),它们只是同一个文件的两个不同名字而已。此时也就没有file1是源文件,而file1hdlink是硬链接这样的概念了,两者的地位相同。当我们删除其中一个文件时,就会发现连接数减少了一个。当某个文件的链接数为0时,这个文件便会被删除。

软链接也叫符号链接(symbol link),它相当于windows下快捷方式。与硬链接不同的是,软链接本身就是一类文件(链接文件),因此软链接本身的索引结点和其链接的文件的索引结点是不同的。比如通过下面的命令:

edsionte@edsionte-laptop:~$ ln -s file2 file2symlink
edsionte@edsionte-laptop:~$ ls -li file2 file2symlink
51217 -rw-r--r-- 1 edsionte edsionte 7 2010-09-20 23:18 file2
51214 lrwxrwxrwx 1 edsionte edsionte 5 2010-09-20 23:17 file2symlink -> file2
edsionte@edsionte-laptop:~$ cat file2symlink
hello!
edsionte@edsionte-laptop:~$ rm file2
edsionte@edsionte-laptop:~$ cat file2symlink
cat: file2symlink: 没有那个文件或目录

这里我们也可以发现file2的链接数为1。这里的两个文件并不是平等的关系,从文件属性也可以发现file2是普通文件,file2symlink是一个链接文件。每次系统访问软连接文件时,就会自动去访问它所链接的那个源文件。当源文件本身被删除后,这个链接文件也就失效了,尽管它还存在,但是已经不能访问到源文件了。

文件操作学习笔记(2)

7 9 月, 2010

8.熟悉了文件的一些基本操作,那么就要来理解文件的属性。比如前面我们做过cp命令的实现,前面我们所说的那些函数可以完成文件内容的复制,如果想获得某个文件的属性就要用到stat函数。

stat函数族可以将文件属性存储于struct stat类型的结构体中。需要注意的是这个结构体中的几个常用字段,比如st_ino是文件的索引节点编号;st_mode是文件的类型和访问权限,我们在open或chmod这个文件时候都会用到。

特别强调的是这个结构体中有三个关于时间的字段:st_atime表示文件最后一次被访问的时间;st_mtime表示文件最后一次被修改的时间,这里的修改特别指文件内容的修改;st_ctime也表示文件被修改的时间,但是更强调的是文件权限、所有者、所属组的修改时间。当然内容修改时,这个字段也会相应更新。

而且通过前面对ls命令以及cp命令的实现,我们可以发现,关于st_mode我们还有一些列的判定宏。比如S_ISLINK(st_mode),S_ISDIR(st_mode)等等。除此之外,我们还可以通过系统提供的几个访问权限宏与st_mode的与运算,可以具体的分解st_mode,比如:

if((st_mode&S_IROTH)==1)
{
      printf("the file can be read by others\n!");
}

9.如何设置文件属性,这里涉及到一些常用函数。比如要修改文件的存取权限,就用chmod/fchmod函数;如果要修改文件的用户id和组id,那么就用chown组函数来进行相应修改。注意,如果只想修改两者中的一个,那么将两外一个的值指定为-1即可。

10.rename函数用来修改文件名或文件的位置,如果newpath所指定的文件已经存在,那么源文件会被删除。如果想删除一个文件,那么就直接调用remove函数即可。此函数既可以删除一个普通文件,也可是删除一个目录文件。这个函数其实是封装了unlink函数和rmdir函数。如果想创建一个目录则可以使用mkdir函数。

11.如何获得一个目录下的所有文件名称?下面这个例子是一个经典模型。

DIR *dir;
struct dirent *ptr;

if((dir=opendir(path))==NULL)
{
	perror("opendir");
	return -1;
}
while((ptr=readdir(dir))!=NULL)
{
	printf("file name:%s\n",ptr->d_name);
}
/*do something*/
closedir(dir);

首先通过opendir打开一个目录,并返回一个DIR*的目录流dir,这类似于文件操作中的文件描述符(这里你可以理解为目录描述符)。然后将这个dir作为readdir函数的参数,使用一个while循环就可以依次获得该目录下的所有文件信息,存储于struct dirent类型的结构体中。

那么,如何根据上面的基本模型去实现其他的功能?你可以这么想:既然现在已经得到了每个文件的名称,那么想完成单个文件的操作就容易多了。

文件操作学习笔记(1)

2 9 月, 2010

学习linuxC语言有一阵子了,这里最重要最基础的部分当属文件操作部分了,所以很有必要在学习一段时间后总结一下相关知识。

1.linux文件系统最突出的一个特点就是对用户访问权限的控制,即采用ugo的访问控制形式。

2.在linuxC语言中所涉及的文件输入输出函数,比如open,creat,read等,均不是标准C下的库函数,因此用上述函数所编写的程序不方便移植。但是这些函数比标准C下所对应的输入输出函数速度要快的多,因为它们直接条用操作系统。

提到这些函数,我们就不能不提到文件描述符。这也是LinuxC与标准C在文件操作部分的一大不同点。我们可以回想在标准C下我们通常打开文件,返回的是一个FILE类型的指针;而在LinuxC下我们则返回一个整形变量——文件描述符。

3.open函数可以打开最多有三个参数。第二个参数是打开文件的方式,只能取O_RDONLY,O_WRONLY,O_RDWR中的一个。通常上面的参数还会与其他一些标志进行货运算。这里只说两种最典型的情况。

O_CREAT|O_EXCL|O_WRONLY:这种情况只适合打开一个不存在的文件;如果要打开的文件存在,那么则会返回错误标志。并且此时会用到第三个参数:存取权限。这里应该注意文件的存取权限和打开方式是两个不同的概念。具体可参看这里的文章。

O_CREAT|O_TRUNC|O_WRONLY:这种情况即适合打开一个已存在的文件,又适合打开一个不存在的文件。如果要打开的文件存在,那么会自动会清空原文件中的数据,即文件长度为0。但是文件的属性不变,也就是说第三个参数此时失效。

上面我们说到了第三个参数mode,它用来说明文件的存取权限。但是实际上存取权限是mode与umask的进行(mode&~umask)运算后的来的结果。这一点还可以参见这里的文章。

4.creat函数也可以打开或创建一个文件。与open不同的是,creat函数既可以打开一个存在的文件,又可以打开一个不存在的文件。因此才creat函数相当于这样使用open函数:open(const char* pathname,(O_CREAT|O_TRUNC|O_WRONLY),mode_t mode)。不过应该注意的,creat函数只能以只写方式打开或创建一个文件。

5.读写函数使用起来比较简单,成功调用会返回实际读(或写)的字节数。一般在使用这两个函数的同时会用if语句对返回值加以判断,以确定调用是否正常。比如具体代码可参考如下:

if((ret=read(fd,read_buf,len))<0)
{
     my_error("read",__LINE__);
}

这里并没有严格规定返回值必须等于读(或写)的字节数,因为有可能当前文件的读写指针与文件末尾的偏移量小于要读(或写)的字节数(通俗的说就是不够读或写),这种情况在不严格要求的情况下也算正常读取。

6.我们并不是希望每次都从文件头开始读或写,那么这里就要涉及到文件的读写指针的移动了。lseek函数的作用是将文件描述符为fd的文件从whence处开始移动offset个字节。不管参数如何搭配,lseek成功调用后,都会返回当前的读写位置,也就是距离文件开头的字节数。根据各个参数的含义,就会又下面的几种常用方法:

将文件读写指针移动到文件开始:lseek(fd,0,SEEK_SET);

将文件读写指针移动到文件结尾:lseek(fd,0,SEEK_END);

获取文件读写指针当前的位置:ret=lseek(fd,0,SEEK_CUR);

lseek允许将文件指针设置到文件结束符之后,但这并不改变文件的大小。如果继续对EOF之后的位置写数据,那么之前的EOF之处与之后写入的数据之间将存在一段“空隙”。如果用read读取这段间隔的数据,那么可以发现数据为0,其实这段空隙中填充的是’\0’。比如用lseek函数从文件末尾移动10个字节,再继续写入新数据,那么旧数据和新数据之间将有10个’\0’。

我们都知道字符串均以’\0’结尾,所以我们直接输出读取文件的那个字符串是不行的,因为它只会输出空隙前的数据(旧数据)。因此我们得一个字符一个字符的输出,这样才能将新旧数据都输出。但这样仍然不能输出空隙中的数据,不过用od -c filename命令就可以查看文件中的每一个字符,包括空隙中的’\0’。

7.初次学习dup函数可能不能真实体会其用途。不过涉及到输入输出重定位时,它的威力就显示了出来。比较特别的是,dup函数每次都返回系统内尚未使用的最小文件描述符。在前面我们一起分析管道应用的时候,有一个主程序监控子程序的例子。由于子程序exec后,以前那些数据(比如管道的描述符)将荡然无存,所以我们需要用dup进行一些重定位。具体来看:

	switch(pid)
	{
		case -1:
			perror("fork failed");
			exit(1);
		case 0:
			close(0);
                        dup(fd[0]);
			execve("ctrlprocess",(void*)argv,environ);
			exit(0);
		default:
			close(fd[0]);
			write(fd[1],argv[1],strlen(argv[1]));
			break;

	}

可以看到父进程负责向管道写数据。而子进程脱离父进程前先将标准输入端关闭,然后再对管道的输出端dup一个文件描述符,接着就可以执行新的程序ctrlprocess了。而新的子进程只需在标准输入端读数据就可以了。

也许看到这里你还有些不明白,其实这里就用到了上文中说的返回最小的文件描述符。关闭标准输入端,那么当前系统内最小的可用文件描述符当然就是0了!而这还不是重点,当我们将管道的输出端定位到标准输入后,即便子进程去执行新的进程(原有数据段被废弃),那么至少标准输入这个文件描述符是不变的。那么子进程在标准输入里读,也就相当于在管道的读端读取数据。

而理解了上述原理,那么close(0);和dup(fd[0]);可以用一条语句完成:dup2(fd[0],0);因为0文件描述符正在使用,因此dup2函数会先关闭标准输入,然后再将管道的输出端赋值到标准输入,即此时0和fd[0]都同时指向管道。

生产者与消费者模型的实现

29 8 月, 2010

学习了信号量以及共享内存后,我们就可以实现进程的同步与互斥了。说到这里,最经典的例子莫过于生产者和消费者模型。下面就和大家一起分析,如何一步步实现这个经典模型。完整代码可以在这里下载

下面程序,实现的是多个生产者和多个消费者对N个缓冲区(N个货架)进行访问的例子。现在先想想我们以前的伪代码是怎么写的?是不是这样:

//生产者:
	while(1)
	{
		p(semid,1);
		sleep(3);
		p(semid,0);
		//producer is producing a product
		goods=rand()%10;//product a goods
		shmaddr[indexaddr[0]]=goods;//The goods is placed on a shelf
		printf("producer:%d produces a product[%d]:%d\n",getpid(),indexaddr[0],goods);
		indexaddr[0]=(indexaddr[0]+1)%10;
		v(semid,0);
		sleep(3);
		v(semid,2);
	}
//消费者:
	while(1)
	{
		p(semid,2);
		sleep(1);
		p(semid,0);
		//consumer is consuming a product
		goods=shmaddr[indexaddr[1]];//The goods on the shelf is taken down
		printf("consumer:%d consumes a product[%d]:%d\n",getpid(),indexaddr[1],goods);
		indexaddr[1]=(indexaddr[1]+1)%num;
		v(semid,0);
		sleep(1);
		v(semid,1);
	}

可能上面的代码你有些眼熟,又有些困惑,因为它和课本上的代码不完全一样,其实上面的代码就是伪代码的linuxC语言具体实现。我们从上面的代码中慢慢寻找伪代码的踪迹:p(semid,0)和v(semid,0)的作用是让进程互斥访问临界区。临界区中包含的数据indexaddr[0],indexaddr[1],以及shmaddr数组分别对应伪代码中的in,out,buffer。p(semid,1)和v(semid,2)以及p(semid,2)和v(semid,1)实现的是同步作用。

并且,在生产者中,生产者生产了一个货物(goods=rand()%10;),然后将这个货物放上货架(shmaddr[indexaddr[0]]=goods;)。在消费者中,消费和从货架上取下货物(goods=shmaddr[indexaddr[1]];)。

好了,现在再看一边上面的代码,我想你的思路就清晰了。

了解了核心代码,并不能算就完成了生产者和消费者模型,因为生产者和消费者核心代码前还得做一些些准备工作,具体要准备些什么,我们具体来分析。

首先申请一块共享内存,这块共享内存用于存放生产者所生产的货物。同时我们可以看到这块共享内存大小为10字节。这里需要注意,每个生产着或消费者运行后,都要去试着分配这样的一块共享内存。如果在当前进程运行前已经有某个进程已经创建了这块共享内存,那么这个进程就不再创建(此时createshm会返回-1并且错误代码为EEXIST),只是打开这块共享内存。创建后,再将这块共享内存添加到当前进程的地址空间。

	num=10;
	//create a shared memory as goods buffer
	if((shmid_goods=createshm(".",'s',num))==-1)
	{
		if(errno==EEXIST)
		{
			if((shmid_goods=openshm(".",'s'))==-1)
			{
				exit(1);
			}
		}
		else
		{
			perror("create shared memory failed\n");
		        exit(1);
		}
	}
	//attach the shared memory to the current process
	if((shmaddr=shmat(shmid_goods,(char*)0,0))==(char*)-1)
	{
		perror("attach shared memory error\n");
		exit(1);
	}

接下来还要再申请一块共享内存,用于存放两个整形变量in和out(其实就是申请一个含有2个整形变量的数组而已)。他们记录的是生产和消费货物时“货架”的索引。与上面情况相同,如果已经有其他进程创建了此块共享内存,那么当前进程只是打开它而已。

注意这里对两个整形变量的初始化时的值均为0。

	//create a shared memory as index
	if((shmid_index=createshm(".",'z',8))==-1)
	{
		if(errno==EEXIST)
		{
			if((shmid_index=openshm(".",'z'))==-1)
			{
				exit(1);
			}
		}
		else
		{
			perror("create shared memory failed\n");
		        exit(1);
		}
	}
	else
	{
		is_noexist=1;
	}
	//attach the shared memory to the current process
	if((indexaddr=shmat(shmid_index,(int*)0,0))==(int*)-1)
	{
		perror("attach shared memory error\n");
		exit(1);
	}
	if(is_noexist)
	{
		indexaddr[0]=0;
		indexaddr[1]=0;
	}

接下来就是创建一个信号量集,这个信号量集中包含三个信号量。第一个信号量实现的互斥作用,即进程对临界区的互斥访问。剩下两个均实现的是同步作用,协调生产者和消费者的合理运行,即货架上没有空位时候生产者不再生产,货架上无商品时消费者不再消费。

注意下面对每个信号量的赋值情况。互斥信号量当然初值为1。而同步信号量两者之和不能大于num的值。

	//create a semaphore set including 3 semaphores
	if((semid=createsem(".",'t',3,0))==-1)
	{
		if(errno==EEXIST)
		{
			if((semid=opensem(".",'t'))==-1)
			{
				exit(1);
			}
		}
		else
		{
			perror("semget error:");
			exit(1);
		}
	}
	else
	{
		union semun arg;
	        //seting value for mutex semaphore
         	arg.val=1;
         	if(semctl(semid,0,SETVAL,arg)==-1)
         	{
			perror("setting semaphore value failed\n");
	        	return -1;
            	}
          	//set value for synchronous semaphore
	        arg.val=num;
            	//the num means that the producer can continue to produce num products
          	if(semctl(semid,1,SETVAL,arg)==-1)
            	{
			perror("setting semaphore value failed\n");
	         	return -1;
         	}
           	//the last semaphore's value is default
           	//the default value '0' means that the consumer is not use any product now
      	}

基本上这样,就算完成了生产者和消费者的前期工作。我们可以看到,在核心代码中,我们只需要“装模作样”的将代码“各就各位”即可,当然这需要你理解生产者消费者这个基本模型。而在下面的准备代码中,则需要我们理解关于信号量和共享内存的一些基本函数。

最后再说说使用,建议先运行一个生产者和一个消费者,观察两者是如何协调工作的。然后再只运行一个生产者或一个消费者,看其是否会阻塞。了解了以上情况后,你就可以同时运行多个生产者和消费者了。

实现cp命令(8)

10 8 月, 2010

问题不断的文件权限!

在实现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);
	}

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