关于stm32的USB学习笔记之usbcore.c

#include <stm32f10x_lib.h>
#include "usbreg.h"
#include "usbcore.h"
#include "usbuser.h"
#include "usbcfg.h"
#include "usb.h"
#include "usb_hw.h"
#include "usbdesc.h"
#include "hid.h"
#include "hiduser.h"


#define	_DEBUG_
#include "debug.h"

#pragma diag_suppress 111,1441

//用来指示USB设备的状态
WORD	USB_DeviceStatus;
//用来存储设备的地址
BYTE	USB_DeviceAddress;
//用来存储USB当前使用的设备的配置
BYTE	USB_Configuration;
//此配置使用端点
DWORD USB_EndPointMask;
//用于标志此端点是否已经被停止0~15依次代表15个端点
DWORD USB_EndPointHalt;
//此配置使用的接口数
BYTE  USB_NumInterfaces;
//每个接口可用的当前接口可替换值
BYTE  USB_AltSetting[USB_IF_NUM];


/*用于临时存储控制传输时的数据包*/
USB_SETUP_PACKET SetupPacket;

/*用于向主机发送数据的EP0的数据结构*/
USB_EP_DATA EP0Data;
/*EP0Buf用于向USB发送数据时用的缓冲区*/
BYTE	EP0Buf[USB_MAX_PACKET0];

/*功能:复位USB的一些数据标志
 *参数:无
 *返回值:无
 */
void	USB_ResetCore(void)
{
	//默认为总线供电,因为我们的USB现在不都是插在电脑上才能工作的么&^_^
	USB_DeviceStatus 	=	USB_POWER;	
	//在枚举之初地址当然是0
	USB_DeviceAddress	=	0;
	//配置描述符的标识从1开始,这里也先置为0
	USB_Configuration	=	0;
	//目前使用的是端点0
	USB_EndPointMask	= 	0x00010001;
	//没有停止的端点
	USB_EndPointHalt	=	0x00000000;
}

/*功能:建立阶段,读取建立数据包
 *参数:无
 *返回值:无
 */
void USB_SetupStage(void)
{
	 USB_ReadEP(0x00,(BYTE*)&SetupPacket);
}
/*功能:建立阶段,In握手包
 *参数:无
 *返回值:无
 */
void USB_StatusInStage(void)
{
	 USB_WriteEP(0x80,NULL,0);
}
/*功能:建立阶段,Out握手包
 *参数:无
 *返回值:无
 */
void USB_StatusOutStage(void)
{
	 USB_ReadEP(0x00,EP0Buf);
}
/*功能:数据In阶段
 *参数:无
 *返回值:无
 */
void USB_DataInStage(void)
{
	DWORD	cnt;
	//先计算引次要发送多少数据
	if(EP0Data.Count > USB_MAX_PACKET0)
		cnt	= USB_MAX_PACKET0;
	else
		cnt	= EP0Data.Count;
	//这里写端点却主机读,则将Dir位置1
   	cnt	= USB_WriteEP(0x80,EP0Data.pData,cnt);
	EP0Data.pData	+=	cnt;
	EP0Data.Count	-=	cnt;
}
/*功能:数据Out阶段
 *参数:无
 *返回值:无
 */
void USB_DataOutStage(void)
{
	 DWORD	cnt;

	 cnt	=	USB_ReadEP(0x00,EP0Data.pData);
	 EP0Data.pData+=cnt;
	 EP0Data.Count-=cnt;
}
/*功能:获取USB设备的状态
 *参数:无 
 *返回值:TRUE	--->成功
 *		 FALSE	--->错误
 */
__inline BOOL	USB_GetStatus(void)
{
	DWORD	n,m;
	
	switch(SetupPacket.bmRequestType.BM.Recipient)
	{
		//接收端是设备
		case REQUEST_TO_DEVICE:
			//返回设备状态给他
			EP0Data.pData = (BYTE *)&USB_DeviceStatus;
			//将状态信息发送给主机
			USB_DataInStage();
			break;
		//接收端是接口
		case REQUEST_TO_INTERFACE:
			/*配置描述符的标识从1开始,并且请求的接口号不能大于接口的数目,因为接口数目从0开始
			 *因为我们接口描述符中的接口号是一个字节所以这里wIndex中的数据中人低字节有效
			 */
			if((USB_Configuration !=0)&&(SetupPacket.wIndex.WB.L < USB_NumInterfaces))
			{	
				//清0两个字节,因为根据USB协议此处必须返回0
				*((__packed WORD *)EP0Buf) = 0;	
				EP0Data.pData = EP0Buf;
				//发送给主机
				USB_DataInStage();
			}
			//此接口出现了错误
			else
				return	FALSE;
			break;
		case REQUEST_TO_ENDPOINT:
			//端点号高1位0方向,低4位为端点号
			n	= SetupPacket.wIndex.WB.L & 0x8f;
			//m的高16位代表in端点的标志,低16位代表out端点的标志
			m	= (n&0x80)?(1<<16 )<< (n&0x0f) :(1<<n);
			//如果配置标识不为0,或配置标识为0,但是是端点0时,才算正常
			if((USB_Configuration !=0)||((n&0x0f)==0)&&(USB_EndPointMask & m))
			{
				//查看此端点是否已经停用
				*((__packed WORD *)EP0Buf) = (USB_EndPointHalt &m )?1:0;
				EP0Data.pData = EP0Buf;
				//将数据发送给主机
				USB_DataInStage();
			}
			//说明配置描述符出了问题
			else
				return	FALSE;
			break;
		default:
			return	FALSE;
	}	
  	return TRUE;
}
/*功能:设置/清除USB设备的特征
 *参数:sc
 			0----->清除
			1----->设置
 *返回值:TRUE	--->成功
 *		 FALSE	--->错误
 */
__inline BOOL USB_SetClrFeature(DWORD	sc)
{
	DWORD	n,m;
	switch(SetupPacket.bmRequestType.BM.Recipient)
	{
		//接收端是设备,则清除或设置设备的特性
		case REQUEST_TO_DEVICE:
			if(SetupPacket.wValue.W == USB_FEATURE_REMOTE_WAKEUP)
			{
				if(sc)
				{
					printf("设置设备远程唤醒特性rn");
					//设置USB状态为使能远程唤醒
					USB_DeviceStatus |= USB_GETSTATUS_REMOTE_WAKEUP;
					/*stm32硬件本身就支持远程唤醒,这里就不用设置了
					 *当然,软件支不支持在于对中断屏蔽位的设置
					 */
				}	
				else
				{
					printf("清除设备远程唤醒特性rn");
					USB_DeviceStatus &= ~USB_GETSTATUS_REMOTE_WAKEUP;
					/*stm32硬件本身就支持远程唤醒,这里就不用设置了
					 *当然,软件支不支持在于对中断屏蔽位的设置
					 */
				}
			}
			//不支持的请求
			else
			{
				printf("不支持的请求rn");
				return FALSE;
			}
			break;
		//接收端是接口
		case REQUEST_TO_INTERFACE:
			//接口不支持清除特性
			printf("清楚设置接口特征不被USB2.0支持rn");
			return 	FALSE;
		//接收端是端点
		case REQUEST_TO_ENDPOINT:
			//取得端点号
			n = SetupPacket.wIndex.WB.L & 0x8F;
//			printf("请求的端点是:");
//			printhex(n);
//			printf("USB_Configuration:");
//			printhex(USB_Configuration);
//			printf("USB_EndPointMask");
//			printhex(USB_EndPointMask);
//			printf("rn");
			//将端点转化成与EndPointMask一样的格式
			m =	(n & 0x80)?((1<<16) << (n&0x0f)) : (1<<n);
		    /*根据USB协议来说,
			 *地址状态下只有端点0才能使用,
			 *配置状态下可用
			 *不知道他这句是不是bug
			 *原文件引用:
			 *This request is valid when the device is in the Address state;
			 *references to interfaces or to endpoints other than endpoint zero 
			 *shall cause the device to respond with a Request Error
			 */
			 /*	此处认为只用配置状态下的非0端点才能清楚特征 ,至于地址状态下没有处理
			  *
			  */
			if((USB_Configuration !=0) && ((n&0x0f)!=0) && (USB_EndPointMask & m))
			{
				//设置/清楚 某个端点stall状态
				if(SetupPacket.wValue.W == USB_FEATURE_ENDPOINT_STALL)
				{
					//设置
					if(sc)
					{
						printf("设置端点远程唤醒特性rn");
						//设置请求的端点为stall
						USB_SetStallEP(n);
						//更新USB的状态位
						USB_EndPointHalt |= m;	
					}
					//清楚
					else
					{
						printf("清楚端点远程唤醒特性rn");
						USB_ClrStallEP(n);
						USB_EndPointHalt &= ~m;
					}

				}
				//未定义的请求
				else
				{
					printf("不正确的端点请求rn");
					return FALSE;
				}
			}
			//不正确的请求时机
			else
			{
				printf("不正确的请求时机rn");
				return	FALSE;
			}
			break;
		//未定义的接收端类型
		default:
				printf("未定义的接收端类型rn");
				printf("接收端为:");
				printhex(SetupPacket.bmRequestType.BM.Recipient);
				printf("rn");
				return	FALSE;
			
	}
	return TRUE;
}
/*功能:用于向主机发送描述符
 *参数:无 
 *返回值:TRUE	--->成功
 *		 FALSE	--->发生了错误
 */
__inline BOOL	USB_GetDescriptor()
{
	BYTE 	* pD;
	DWORD	len,n;

	switch (SetupPacket.bmRequestType.BM.Recipient)
	{
		//发给设备的请求
		 case REQUEST_TO_DEVICE:
		 	switch(SetupPacket.wValue.WB.H)
			{
				//获取设备描述符
				case USB_DEVICE_DESCRIPTOR_TYPE:
				  	printf("获取设备描述符rn");
					EP0Data.pData = (BYTE *)USB_DeviceDescriptor;
					len	= USB_DEVICE_DESC_SIZE;
					break;
				//获取配置描述符
				case USB_CONFIGURATION_DESCRIPTOR_TYPE:
				  	printf("获取USB配置描述符rn");
					pD = (BYTE *)USB_ConfigDescriptor;
				/* 引用USB协议:The range of values used for a
				 * descriptor index is from 0 to one less than 
				 * the number of descriptors of that type implemented by the device
				 * 配置描述符中的标识是从1开始计的,但是,请求中的值是标识值为配置描述符
				 */
				 /*由于可能有几个配置描述符所以可能需要描述一下指针*/
					for(n=0;n!= SetupPacket.wValue.WB.L;n++)
					{
						if(((USB_CONFIGURATION_DESCRIPTOR *)pD)-> bLength != 0)
						{
							pD+=((USB_CONFIGURATION_DESCRIPTOR *)pD)->wTotalLength;
						}
					}
					//如果请求的描述符为空则返回stall
					if(((USB_CONFIGURATION_DESCRIPTOR *)pD)->bLength == 0)
						return	FALSE;
					//设置要传送的指针
					EP0Data.pData = pD;
					//虽然配置描述符中多了一个字节,但是wTotalLength中没有算上,所以这里不用管,直接用就行了
					len = ((USB_CONFIGURATION_DESCRIPTOR*)pD)->wTotalLength;
				  	break;
				//字符串描述符
				case USB_STRING_DESCRIPTOR_TYPE:
				  	printf("获取字符串描述符rn");
					EP0Data.pData = (BYTE *)USB_StringDescriptor + SetupPacket.wValue.WB.L;
					len = ((USB_STRING_DESCRIPTOR *)EP0Data.pData)->bLength;
					break;
				default:
					return	FALSE;
			}
			break;
		//请求接收端接口
//		case REQUEST_TO_INTERFACE:
//			printf("请求接收端接口rn");
//			switch(SetupPacket.wValue.WB.H)
//			{
//				//Hid 描述符
//				case HID_HID_DESCRIPTOR_TYPE:  
//					printf("请求HID描rn");
//					break;
//				case HID_REPORT_DESCRIPTOR_TYPE:
//					printf("请求报告描述符rn");
//					//这里只有一个报告描述符,报告描述符的索引是从0开始计,跟数组是一样的,如是不是0则返回Stall
//					if(SetupPacket.wIndex.WB.L != 0)
//						return FALSE;
//					EP0Data.pData = (BYTE *)HID_ReportDescriptor;
//					printf("报告描述符请求的字节:");
//					printhex(EP0Data.Count);
//					printf("报告描述符本来的长度:");
//					printhex(HID_ReportDescSize);
//					printf("rn");
//					len = HID_ReportDescSize;
//					break;
//				default:
//					printf("未识别的请求!!!###,类型是:");
//					printhex(SetupPacket.wValue.WB.H);
//					printf("rn");
//					break;
//			}
//			break;
		default:
			return	FALSE;
 	}

	printf("获取描述符成功rn");
	//将数据发送给主机
	if(EP0Data.Count > len)
		EP0Data.Count = len;
	USB_DataInStage();
	return 	TRUE;
}
/*功能:设置配置请求
 *参数:无
 *返回值:TRUE 	--- >成功
 *		 FALSE	--- >失败
 */
__inline BOOL USB_SetConfiguration(void)
{
	USB_COMMON_DESCRIPTOR * pD;
	DWORD	alt,n,m;
	printf("设置配置rn");
	//配置值不能为0
	if(SetupPacket.wValue.WB.L)
	{
		pD=(USB_COMMON_DESCRIPTOR *)USB_ConfigDescriptor;
		//循环到配置描述符中的最后一个端点描述符
		while(pD->bLength)
		{
			 switch(pD->bDescriptorType)
			 {
			 	//此时是配置描述符
			 	case USB_CONFIGURATION_DESCRIPTOR_TYPE:	
					//如果此配置描述符是主机要设置的配置描述符
					if(((USB_CONFIGURATION_DESCRIPTOR * )pD)->bConfigurationValue == SetupPacket.wValue.WB.L)
					{
						//保存起此时,设备所用的配置值
						USB_Configuration = SetupPacket.wValue.WB.L;
						//保存起此时,设备所用的配置的接口数目
						USB_NumInterfaces = ((USB_CONFIGURATION_DESCRIPTOR*)pD)->bNumInterfaces;
						//清0每个接口可替换设置值
						for(n=0;n<USB_IF_NUM;n++)
						{
							USB_AltSetting[n]=0;	
						}	
						//我们将所有的以前可能使用的端点禁用,如果需要,在端点描述符中再使能就行了
						//这样如果更改了配置就保证了,不会使用到以前配置使用,而本次配置没有使用的端点
						//可能是为了防止干扰,猜的^-^!
						for(n=1;n<16;n++)
						{
							if(USB_EndPointMask & (1<<n))
								USB_DisableEP(n);
							if(USB_EndPointMask & ((1<<16)<<n))
								USB_DisableEP(n|0x80);
						}
						//还没有枚举到端点因此这里只使用的是端点0
						USB_EndPointMask = 0x00010001;
						//刚配置,当然没有停止的端点啊,全新的&_^
						USB_EndPointHalt = 0x00000000;
						/*在复位时,我们默认是总线供电,现在开始正式运行以前要因为主机会根据我们的配
						 *置描述符进行调整,一旦配置描述符返回后,配置描述符就生效了,所以这里必须来依据实际
						 *来重新设置
						 */
						if(((USB_CONFIGURATION_DESCRIPTOR *)pD)->bmAttributes & USB_CONFIG_SELF_POWERED)
							USB_DeviceStatus |= USB_GETSTATUS_SELF_POWERED;
						else
							USB_DeviceStatus &= ~USB_GETSTATUS_SELF_POWERED;
					}
					//否则略过,下一个配置描述符
					else
					{
						(BYTE *)pD += ((USB_CONFIGURATION_DESCRIPTOR *)pD)->wTotalLength;
						continue;		
					}
					break;
				//下面是接口描述符
				case USB_INTERFACE_DESCRIPTOR_TYPE:
					alt = ((USB_INTERFACE_DESCRIPTOR *)pD)->bAlternateSetting;
					break;
				case USB_ENDPOINT_DESCRIPTOR_TYPE:
					//我们仅仅需要处理多个子接口中的第一个就行了,(如果有子接口的话,没有的话刚好也是0)
					if(alt == 0)
					{
						//得到此端点
						n = ((USB_ENDPOINT_DESCRIPTOR *)pD)->bEndpointAddress & 0x8f;
						//将端点号转化成bit以存储在USB_EndPointMask中去代表我们使用了此端点
						m = (n&0x80)?((1<<16)<<(n&0x0f)):(1<<n);
						USB_EndPointMask |= m;
						//配置端点
						USB_ConfigEP((USB_ENDPOINT_DESCRIPTOR  *)pD);
						//使能端点
						USB_EnableEP(n);
					    //复位端点,使端点处于确定的端点
						USB_ResetEP(n);
					}
					break;
			 }
		(BYTE *)pD += pD->bLength;
		}	
	}
	//如果请求的配置为0,则我们将所有的配置恢复到地址状态时的配置
	else
	{
		USB_Configuration = 0;
		for(n=1;n<16;n++)
		{
			//如果上一个配置使用了此端点,则disable it
			if(USB_EndPointMask & (1<<n)) //OUT端点
				 USB_DisableEP(n);
			if(USB_EndPointMask & (1<<16)<<n)  //IN端点
				 USB_DisableEP(n | 0x80);
		}	
		//清除使用的全部端点,(当然0端点还是除外的)
		USB_EndPointMask = 0x00010001;
		USB_EndPointHalt = 0x00000000;
	}
	//我们根据USB_Configuration的值来查看我们到底时否找到了所需要的值
	if(USB_Configuration == SetupPacket.wValue.WB.L)
	{	
		printf("设置配置成功rn");
		return	TRUE;
	}
	else
	{
		printf("设置配置失败rn");
		return	FALSE;
	}
}

/*功能:用于处理端点0的输入和输出请求函数
 *参数: USB_EVT_SETUP 枚举时上位机发送的数据包
 *      USB_EVT_OUT	  普通传输时,上位机发送的数据包
 *		USB_EVT_IN	  普通传输时,上位机请求发送数据
 *返回值:无
 */
void USB_EndPoint0 (DWORD event)
{
//	printf("进入端点0处理函数rn");
	switch (event)
	{
		//控制传输
		case USB_EVT_SETUP:
		//	printf("进入建立阶段rn");
			//首先读取主机发来的请求
			USB_SetupStage();
		   	//设置EP0的数据结构
			EP0Data.Count = SetupPacket.wLength;
			//识别是何请求
//			printf("请求是");
//			printhex(SetupPacket.bRequest);
//			printf("    被请求对象:");
//			printhex(SetupPacket.bmRequestType.B);
//			printf("rn");
			//bRequestType.bit5~bit6代表请求类型
			switch(SetupPacket.bmRequestType.BM.Type)
			{
				//标准请求
				case REQUEST_STANDARD:
					switch(SetupPacket.bRequest)
					{
					//获取USB状态
					case USB_REQUEST_GET_STATUS:
						 //如果状态失败则停止0x81端点
					 	if(!USB_GetStatus())
						{
							printf("获取状态失败rn");
							goto	stall_i;
						}
						break;
					case USB_REQUEST_CLEAR_FEATURE:
						//如果清除特征失败则停止0x81端点
						if(!USB_SetClrFeature(0))
						{
							printf("清楚特征失败rn");
							goto stall_i;	
						}
						//成功了握一下手OK
						USB_StatusInStage();	
						break;
					case USB_REQUEST_SET_FEATURE:
						//如果状态失败则停止0x81端点
						if(!USB_SetClrFeature(1))
						{
							printf("设置特征失败rn");
							goto stall_i;	
						}	
						//成功了握一下手OK
						USB_StatusInStage();
						break;
					case USB_REQUEST_SET_ADDRESS:
						switch(SetupPacket.bmRequestType.BM.Recipient)
						{
							//只支持向设备设置地址
							/*USB协议指出必须在改地址前完成检测并对此信息包进行响应
							 *即USB的设置地址阶段必须在主机接收到0长度数据包后才能进行
							 *当主机接收到状态阶段的0数据包时将会产生接收中断
							 *所以放到了USB_EVT_IN处*/
							case REQUEST_TO_DEVICE:
								printf("设置地址rn");
								USB_DeviceAddress = 0x80 | SetupPacket.wValue.WB.L;
								USB_StatusInStage();
						   		break;
							default:
								printf("未识别的接收端,接收端是");
								printhex(SetupPacket.bmRequestType.BM.Recipient);
								printf("rn");
								goto	stall_i;
						}
						break;
					//获取设置描述符
					case USB_REQUEST_GET_DESCRIPTOR:
						if(!USB_GetDescriptor())
							goto	stall_i;
						break;
					//设置配置请求
					case USB_REQUEST_SET_CONFIGURATION:
						switch(SetupPacket.bmRequestType.BM.Recipient)
						{
							//对设备
						 	case REQUEST_TO_DEVICE:
								if(!USB_SetConfiguration())
								{
									//如果失败则对主机返回stall
									goto stall_i;
								}
								//完成了请求,握个手OK
								USB_StatusInStage();
								break;
							//不支持对其他类型请求
							default:
								goto stall_i;
						}
						break;
					case USB_REQUEST_GET_INTERFACE:
						printf("请求接口rn");
						break;
					default:
						printf("未识别的请求,请求代码是");
						printhex(SetupPacket.bRequest);
						printf("rn");
						break;
					}
				break;
				//类请求
				case REQUEST_CLASS:
					printf("类请求rn");
					switch (SetupPacket.bmRequestType.BM.Recipient)
					{
						//对接口请求
						case REQUEST_TO_INTERFACE:
							if(SetupPacket.wIndex.WB.L == USB_HID_IF_NUM)
							{
								switch(SetupPacket.bRequest)
								{
									case HID_REQUEST_GET_REPORT:
										printf("获取报告描述符rn");
										break;
									case HID_REQUEST_SET_REPORT:
										printf("设置报告描述符rn");
										break;
									case HID_REQUEST_GET_IDLE:
										printf("获取空闲率rn");
										break;
									case HID_REQUEST_SET_IDLE:
										printf("设置空闲rn");
										break;
								    case HID_REQUEST_GET_PROTOCOL:
										printf("查询报告协议是否在活动rn");
										break;				
									case HID_REQUEST_SET_PROTOCOL:
										printf("SetReprotrn");
										break;	
									default:
										printf("不支持的HID请求rn");
										break;
						   		}
							}
							break;
					}
					break;
				//产商请求
				case REQUEST_VENDOR:
				//未定义请求
				default:
		stall_i:
					USB_SetStallEP(0x80);
					break;
			}
			break;
		//接收成功
		case USB_EVT_OUT:
			printf("USB_EVT_OUTrn");
			if(SetupPacket.bmRequestType.BM.Dir ==0)
			{
				printf(" 请求是:");
				printhex(SetupPacket.bRequest);
				printf("rn");
			}
			//返回握手
			else
			{
				USB_StatusOutStage();
			}
			break;
		//发送成功
		case USB_EVT_IN:
		//	printf("USB_EVT_INrn");
			if(SetupPacket.bmRequestType.BM.Dir == 1)
				USB_DataInStage();
			/*USB协议指出必须在改地址前完成检测并对此信息包进行响应
			 *即USB的设置地址阶段必须在主机接收到0长度数据包后才能进行
			 *当主机接收到状态阶段的0数据包时将会产生接收中断
			 *所以放到了USB_EVT_IN处*/
			 //原keil库没有加if判断,怀疑为bug,这里判断是否是由于设置地址的状态阶段成功而触发的中断
			 else// if(SetupPacket.bRequest == USB_REQUEST_SET_ADDRESS)
			 {
				if(USB_DeviceAddress & 0x80)
				{
					USB_DeviceAddress &= 0x7f;
					USB_SetAddress(USB_DeviceAddress);
				}		 	
			 }
			break;
	}

}

关于stm32的USB学习笔记之USB_HW.c

#include <stm32f10x_lib.h>
#include <stm32f10x_map.h>
#include "usbreg.h"
#include "usbuser.h"
#include "usbcore.h"
#include "usb_hw.h"
#define	_DEBUG_
#include "debug.h"

#define	USB_EP_NUM	4

/*端点缓冲区的开始地址
 *因为每个缓冲块都需要一个端点描术表
 *而所有的端点描述表放在,USB缓冲区的首部
 *此地址是相对于USB缓冲区的地址,我认为加上Offset更好些
 *这里使用2个端点
 *端点0与端点1
 *此时EP_BUF_ADDR指向缓冲区的内容
 */
#define	EP_BUF_ADDR	(sizeof(EP_BUF_DSCR)*USB_EP_NUM)

/*USB缓冲区首地址包括缓冲区描述表,绝对地址*/
EP_BUF_DSCR	* pBUF_DSCR = (EP_BUF_DSCR *) USB_PMA_ADDR;

/*端点空闲缓冲区地址 
 *用于指示目前为止USB缓冲区中还没有分配的空闲地址的首地址*/
WORD	FreeBufAddr;

	
/*功能:用于初始化USB的时钟等部分
 *参数:无
 *返回值:无
 */
void USB_Init(void)
{
	printf("进入USB_Init,进行初始化rn");
	//使能USB时钟
	RCC->APB1ENR |= (1<<23);

	//使能USB中断
	/*因为USB低优先级中断的中断号为20,而NVIC——IPRX
	 *寄存器用四位来存储中断优先级,所以20/4=5 ,
	 *然后使能第20位中断*/
	NVIC->IPR[5] |=0x10;
	NVIC->ISER[0]|=(1<<20);
}
/*功能:用于复位USB模块	  
 *参数:无
 *返回值:无
 */
/*现在以我的水平还搞不懂双缓冲为何物,所以先不搞^-^*/
/*一些资料:
  *USB低优先级中断(通道20):可由所有USB事件触发(正确传输,USB复位等).
  *USB高优先级中断(通道19):仅能由同步和双缓冲批量传输事件触发,目的是保证最大的传输速率.
  *USB唤醒中断(通道42):由USB挂起模式的唤醒事件触发.  OTG_FS_WKUP唤醒
  *
  *复位要执行的内容可以参见rm0008 21.4.2节
  */
void USB_Reset(void)
{
	PrintS("USB_Resetrn");
	/*复位了嘛,那所有以前产生的中断都没有用了,清了完事!*/
	ISTR=0;

	/*通过设置CNTR来控制stm32的USB模块的工作方式
	 *所有的USB事件中断都是在低优先级中断(通道20)上处理的
	 *好吧就先使能这么多吧,先跑起来再说!
	 */
	CNTR= 	CNTR_CTRM		|	// 使能正确传输中断
		 	CNTR_RESETM 	|	//使能复位中断
			CNTR_SUSPM		|	//使能挂起中断
			CNTR_WKUPM		;	//使能唤醒中断
	
	FreeBufAddr	=	EP_BUF_ADDR; //此时FreeBuff指向第一个缓冲区首地址(不包括描述符表),相对地址

	BTABLE	= 0x00;				 //设置缓冲区描述表的位置仍是相对地址
	
	/*为端点0设置缓冲区及各种控制位*/
	pBUF_DSCR->ADDR_TX	=	FreeBufAddr;
	FreeBufAddr+=8;		//端点0设置为8个字节,一般控制数据为8个字节
	pBUF_DSCR->ADDR_RX	=	FreeBufAddr;
	FreeBufAddr+=8;
	/*在count_Rx字段中10~14bit用来表示缓冲区字节的快数
	 *而15bit用来表示块的大小
	 *0---2byte
	 *1---1byte
	 *我们这里使用了8个字节,bit15为0,所以应该((8<<10)>>1)即8<<9;
	 *至于count_Rx我们在发送时再来赋值
	 */
	pBUF_DSCR->COUNT_RX= 8 << 9;	
	/*设置端点0为控制端点,接收缓冲区有效
	 *低四位代表端点地址
	 */
	EPxREG(0) = EP_CONTROL | EP_RX_VALID;

	/*使能USB模块,并设置USB地址为0,以响应枚举*/
	DADDR = DADDR_EF | 0;
}

/*功能:复位一个端点
 *参数:	端点号
 *				EPNum:bit3~bit0	----> 端点号
 * 				EPNum:bit7		----> 端点方向
 *
 *返回值:无
 */
 /*其实就是将下一个要发送的数据包变成DATA0*/
 void	EP_Reset(DWORD	EPNum)
 {
 	DWORD	num,var;
   	PrintS("EP_Resetrn");
	/*获得端点号,低四位为端点号*/
	num = EPNum & 0x0F;
	var = EPxREG(num);
	/*如果bit7为1则将此端点的发送toggle置为0,
	 *否则将此端点的接收toggle置为0
	 *因为数据总是从data0数据包开始发送的
	 */
	 if(EPNum & 0x80)
	 	EPxREG(num) = var & (EP_MASK | EP_DTOG_TX);/*输入端点*/
	 else
	 	EPxREG(num) = var & (EP_MASK | EP_DTOG_RX);/*输出端点*/
	 
 }
 /*功能:连接或断开USB功能
  *参数:true -->连接USB
  *      false-->关闭USB
  *返回值:无
  */
void USB_Connect(BOOL turnon)
{
	/*需要注意一点的事,所有的USB寄存器尽量用=而不要用与或非
	 *在编程手册上有明确表明,这样可能会导至出一些问题*/
	printf("进入连接USB程序rn");
	 /*将USB强制复位*/
	CNTR = CNTR_FRES;
//	printf("test1rn");
	/*因为刚连接所以应该跟才启动一样,将所有没有处理的中断给清理掉*/
	ISTR=0;
//	printf("test2rn");
	if(turnon)
	{
//		printf("test3rn");
		/*使能GPIOA,然后将PA.8置低,使USB
		 *的D+加1.5K上接电阻,使USB集线器识别了高速设备
		 *这样才能让USB识别
		 */
		  RCC->APB2ENR |= (1 << 2);                 	 /* enable clock for GPIOA */
		  GPIOA->CRH &= ~(0x0f << 0 * 4);                /* clear port PA8 */
		  GPIOA->CRH |=  (0x03 << 0 * 4);                /* PA6 General purpose output open-drain, max speed 50 MHz */
		  GPIOA->BRR  =  (   1 << 8    );                /* reset PA8  (set to low) */	 	
		/*经过调试发现,这个语句的本意是:复位USB模块
		 *然后在此使能CNTR_RESETM即复位中断标志
		 *至于端点0的初始化留在USB低优先级中进行处理
		 *当然,我们也只开了低优先级中断^_^!*/
		  CNTR = CNTR_RESETM;	 /*此处只使能了复位中断,*/	 
	}
	else
		CNTR = CNTR_FRES | CNTR_PDWN ;/*复位并关闭*/

}
/*功能:设置端点状态
 *参数:EPnum --->端点号
 *     stat  --->要设置的状态值
 *返回值:无
 */
 void USB_ConfigEP (USB_ENDPOINT_DESCRIPTOR * pEPD)
 {
 	DWORD num,val;
	
	//取得端点号
	num = pEPD->bEndpointAddress & 0xf;

	val = pEPD->wMaxPacketSize;
	//如果是IN端点
	if(pEPD->bEndpointAddress & USB_ENDPOINT_DIRECTION_MASK)
	{
		//此处我只想说pBUF_DSCR是指针,剩下的就是语法问题了
		(pBUF_DSCR + num)->ADDR_TX = FreeBufAddr;
		/*取2的倍数,因为缓冲区都是字对齐的,注意此处如果大于1023会出现浪费现象
		 *因为USB_COUNTn_TX只能接收bit0~bit9
		 */
		val = (val + 1)& ~1;
	}
	//输出端点
	else
	{
		(pBUF_DSCR + num)->ADDR_RX = FreeBufAddr;
		/*因为USB_COUNTn_RX中存储只用bit10~bit14,如果BLSIZE=0(即块大小为2字节),那么只能是0~62个字节
		 *所以如果大于62,则应将块大小设置为BLSIZE=1(即32个字节)
		 */
		if(val > 62	)
		{
			//块大小为32,则大小应该为32的倍数
			val = (val +31)&~31;
			/*关于此计算公式,参见rm0008,21,5.3节
			 *(val >> 5)<<10 == val <<5
			 */
			(pBUF_DSCR + num)->COUNT_RX = ((val << 5)-1) | 0x8000;
		}
		else
		{
			val = (val + 1) & ~1;
			(pBUF_DSCR + num)->COUNT_RX = val << 9;
		}
	}
	//修正空闲区域的起始地址
	FreeBufAddr += val ;

	switch(pEPD->bmAttributes & USB_ENDPOINT_TYPE_MASK)
	{
		//控制端点
		case USB_ENDPOINT_TYPE_CONTROL:
			val = EP_CONTROL;
			break;
		//同步端点
		case USB_ENDPOINT_TYPE_ISOCHRONOUS:
			val = EP_ISOCHRONOUS;
			break;
		//块传输
		case USB_ENDPOINT_TYPE_INTERRUPT:
			val = EP_INTERRUPT;
			break;
		default:
			printf("出错了,未识别的端点类型rn");
			break;
	}
	val |= num;
	//设置端点寄存器
	EPxREG(num) = val;
 }
/*功能:设置端点状态
 *参数:EPnum --->端点号
 *     stat  --->要设置的状态值
 *返回值:无
 */
 void EP_Status(DWORD EPNum,DWORD stat)
 {
 	DWORD num,var;
	
	/*取得端点号*/
	num = EPNum & 0x0f;
	var = EPxREG(num);
	/*此处用了一点小技巧,因为端点寄存器是写1反转,所以想设置相应的值只有使用异或*/
	if(EPNum & 0x80)	//输入端点
		EPxREG(num)=(var ^ (stat & EP_STAT_TX)) & (EP_MASK | EP_STAT_TX);
	else				//输出端点
		EPxREG(num)=(var ^ (stat & EP_STAT_RX)) & (EP_MASK | EP_STAT_RX);

 }
/*功能:复位端点
 *参数:EPNum 	bit0~bit3为端点号
 			  	bit7	 为端点方向
 *返回值:无
 */
void USB_ResetEP(DWORD EPNum)
{
	EP_Reset(EPNum);
}
/*功能:设置端点为stall
 *参数:EPNum 	bit0~bit3为端点号
 			  	bit7	 为端点方向
 *返回值:无
 */
void USB_SetStallEP(DWORD	EPNum)
{
	EP_Status(EPNum,EP_TX_STALL | EP_RX_STALL);
}
/*功能:设置端点为enable
 *参数:EPNum 	bit0~bit3为端点号
 			  	bit7	 为端点方向
 *返回值:无
 */
void USB_EnableEP(DWORD EPNum)
{
	EP_Status(EPNum,EP_TX_VALID | EP_RX_VALID);
}

/*功能:设置端点为disable
 *参数:EPNum 	bit0~bit3为端点号
 			  	bit7	 为端点方向
 *返回值:无
 */
void USB_DisableEP(DWORD EPNum)
{
	EP_Status(EPNum,EP_TX_DIS | EP_RX_DIS);
}

/*功能:清除端点的stall状态
 *参数:EPNum 	bit0~bit3为端点号
 			  	bit7	 为端点方向
 *返回值:无
 */
void USB_ClrStallEP(DWORD	EPNum)
{
	EP_Status(EPNum,EP_TX_VALID | EP_RX_VALID);
}
/*功能:设置USB地址
 *参数:addr要设置的地址
 *返回值:无
 */
void USB_SetAddress(DWORD	addr)
{
	//DADDR的高1位是用来使能USB模块的
	DADDR = DADDR_EF | addr;
}
/*功能:用于读端点缓冲区
 *参数:EPnum --->端点号
 *     pData --->用于接收从端点缓冲区中读到的数据
 *		此函数有些问题,应该加入一个参数显示缓冲区有多大
 *返回值:读到的字节数
 */
DWORD USB_ReadEP(DWORD EPnum,BYTE * pData)
{

	DWORD num,cnt,*pv,n;
	
	//得到端点号    
	num = EPnum & 0xf;

	//取得些端点的缓冲区地址
	pv=(DWORD *)(USB_PMA_ADDR + 2* ((pBUF_DSCR + num)->ADDR_RX));
	//COUNT_RX低10位存放的是缓冲区的数据
	cnt=(pBUF_DSCR + num)->COUNT_RX & EP_COUNT_MASK;
	for(n=0;n<(cnt+1)/2;n++)
	{
		/*pakced关键字用于单字节对齐,这在USB数据结构中的结构体中尤为重要
		 *因为stm32访问时使用32位,而USB访问时使用16位,所以pData为WORD,而
		 *pv为DWORD型指针
		 */
	 	*((__packed WORD *)pData)=*pv++;
		/*这里pData为单字节指针所以,还是加2而不是加4*/
		pData+=2;
	}
	/*OK,现在我们的端点又可以接收数据了,设置为VALID*/
	EP_Status (EPnum,EP_RX_VALID);
   	return cnt;
}

/*功能:用于写端点缓冲区
 *参数:EPNum --->端点号
 *     pData --->指向要发送的数据缓冲区
 *	   cnt	 --->要写入的字节数
 *返回值:写入的字节数
 */
 DWORD USB_WriteEP(DWORD EPNum , BYTE * pData,DWORD cnt)
 {
 	DWORD num,*pv,n;

	num = EPNum & 0x0f;

	pv=(DWORD *)(USB_PMA_ADDR + 2*((pBUF_DSCR+num)->ADDR_TX));
	/*此处应该判断要写入的数据是否超量了,可能会产一个隐藏bug*/
   	for(n=0;n<(cnt + 1)/2;n++)
	{
		*pv++=*((__packed WORD*)pData);
		pData+=2;
	}
	//OK,现在USB发送缓冲区中已经有东西了,可以响应主机了
	(pBUF_DSCR+num)->COUNT_TX=cnt;
	EP_Status(EPNum,EP_TX_VALID);
	return 	cnt;
 }

/*功能:USB挂起
 *参数:无
 *返回值:无
 */
void USB_Suspend(void)
{
    GPIOE->BSRR |=1 ;            /* Turn Off Suspend LED */
	printf("进入挂起中断rn");
	//强制挂起
	CNTR |= CNTR_FSUSP;	
	//进入低功耗模式
	CNTR |= CNTR_LPMODE;
}

/*功能:USB唤醒
 *参数:无
 *返回值:无
 */
void USB_WakeUp(void)
{
 	GPIOE->BRR |=1 ;            /* Turn On Suspend LED */
	printf("进入唤醒中断rn");
	//唤醒了,当然得把这一位给清了
	CNTR &=  ~CNTR_FSUSP;	
	//USB的唤醒事件会复位此位,我们这里不用管
	//CNTR &= ~CNTR_LPMODE;
}
/*功能:USB低优先级中断服务程序
 *参数:无
 *返回值:无
 */
 void USB_LP_CAN_RX0_IRQHandler(void)
 {
 	DWORD	istr;
	DWORD 	num,var;

	istr=ISTR;	//取得中断标志位
	/*USB复位中断的处理*/
	if(istr & ISTR_RESET)
	{
		//复位设备
		USB_Reset();
		//复位与USB协议有关的数据
		USB_ResetCore();
		ISTR = ~ISTR_RESET;	/*已经处理完复位中断了,清楚复位中断标志*/
	}
	/*USB挂起中断*/
/*	if(istr & 
********************************************************************
*/

  if (istr & ISTR_SUSP) {
  	USB_Suspend();
    ISTR = ~ISTR_SUSP;
  }

  /* USB Wakeup */
  if (istr & ISTR_WKUP) {
	USB_WakeUp();
    ISTR = ~ISTR_WKUP;
  }

//******************************************************************
	/*端点接中发送中断处理*/
	while((istr = ISTR) & ISTR_CTR) 
	{
		//清楚中断标志位
		ISTR = ~ISTR_CTR;
		//取得发生中断的端点号
		num=istr & ISTR_EP_ID;
		//取得端点寄存器的值		
		var = EPxREG(num);
		//正确的接收传输
		if(var & EP_CTR_RX )
		{
			//清0正确接收标志位
		//	printf("端点号为:");
		//	printhex((u8)num);
			EPxREG(num) = var & ~EP_CTR_RX & EP_MASK;
			//调用相应的端点进行处理
			if(USB_P_EP[num])
			{
				//如果是控制传输,则使有USG_EVT_SETUP
				if(var & EP_SETUP)
					USB_P_EP[num](USB_EVT_SETUP);
				else
					//否则就是普通的输出包
					USB_P_EP[num](USB_EVT_OUT);
			}
		}
		//产生的是发送成功中断
		if(var & EP_CTR_TX)
		{
			//清楚中断标志位
			EPxREG(num) = var & ~EP_CTR_TX & EP_MASK;
			//调用对应的中断函数进行处理
			if(USB_P_EP[num])
			{
				//USB发送成功
				USB_P_EP[num](USB_EVT_IN);	
			}
		}

	}

 }

如有错误还请指正!!!

使用STM32的USB模块中后对USB缓冲区的认识

最近在使用STM32的USB模块开发个项目,还以为挺简单,结果搞了快两天才把USB的包缓冲区的访问搞定,在此做个小总结吧。

  STM32的USB模块包缓冲区有512B,但是在STM32的参考手册中的存储器映像中却表明0x40006000-0x400063ff,整整多了512B,怎么会这样呢,同时在尝试着编程时也遇到了一个问题:

在usb_core.c文件的Setup0_Process(void)这个函数中,有这么一段:

uint16_t offset = 1; 
if (pInformation->ControlState != PAUSE)
  {
    pInformation->USBbmRequestType = *pBuf.b++; /* bmRequestType */
    pInformation->USBbRequest = *pBuf.b++; /* bRequest */
    pBuf.w += offset;  /* word not accessed because of 32 bits addressing */
    pInformation->USBwValue = ByteSwap(*pBuf.w++); /* wValue */
    pBuf.w += offset;  /* word not accessed because of 32 bits addressing */
    pInformation->USBwIndex  = ByteSwap(*pBuf.w++); /* wIndex */
    pBuf.w += offset;  /* word not accessed because of 32 bits addressing */
    pInformation->USBwLength = *pBuf.w; /* wLength */
  }  

这其中又不太明白为什么需要pBuf.w += offset;而且后面的解释也不太懂 /* word not accessed because of 32 bits addressing */
,我于是在这段之前加入调试以显示收到什么数
        #ifdef DEBUG
        UARTSend_String("***  端点0收到SETUP数据  ***rn");
        for(offset=0;offset<16;offset++)
        {
                UARTSend_Hex(*pBuf.b++);
        }
        #endif
结果串口调试显示如下:
***  USB总线复位  ***
***  USB总线CTR置位  ***
***  进入端点0  ***
***  端点0收到SETUP包  ***
***  端点0收到SETUP数据  ***
0x80 0x06 0x00 0x00 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x40 0x00 0x00 0x00

本来应该显示0x80 0x06 0x00 0x01 0x00 0x000x40 0x00才对,不太明白怎么0x80 0x06和0x00 0x01 后面多了两个 0x00 ,难道USB模块还会将收到的数据跳着放吗?

 后来反复看参考手册,问了些人,才知道原来STM32的USB缓冲区是一个双端口的RAM,CPU一端需要使用32位方式访问,但USB模块一端使用16位方式访问。也就是说每个USB模块中的地址*2才能对应到控制器中的实际地址,这样每四个字节地址空间后两个字节地址空间是空的。所以上面串口调试显示的数据每正确两个字节就会多出两个字节的0x00。

这里也对STM32的USB库函数中对缓冲区的操作函数做个说明:

在usb_men.c文件有这么一个函数

void PMAToUserBufferCopy(uint8_t *pbUsrBuf, uint16_t wPMABufAddr, uint16_t wNBytes)

  uint32_t n = (wNBytes + 1) >> 1;/* /2*/
  uint32_t i;
  uint32_t *pdwVal;
  pdwVal = (uint32_t *)(wPMABufAddr * 2 + PMAAddr);
  for (i = n; i != 0; i–)
  {
    *(uint16_t*)pbUsrBuf++ = *pdwVal++;
    pbUsrBuf++;
  }

它的作用是将缓冲区中的数据拷贝到你所定义的uint8_t Receive_Buffer[];数组中。其它地方都好理解,这里说说

  pdwVal = (uint32_t *)(wPMABufAddr * 2 + PMAAddr);
  for (i = n; i != 0; i–)
  {
    *(uint16_t*)pbUsrBuf++ = *pdwVal++;
    pbUsrBuf++;
  }
首先是pdwVal = (uint32_t *)(wPMABufAddr * 2 + PMAAddr);这里wPMABufAddr * 2之所以要*2就是前面所述的USB模块中的地址*2才能对应到控制器中的实际地址,在取得对应端点的缓冲区首地址后,将其(uint32_t*)强制指向uint32_t型,这样每次*pdwVal++,pdwVal的地址都增加4个字节,并且每次都会有四个字节的数据读出。

在for循环中,*(uint16_t*)pbUsrBuf++ = *pdwVal++;这句重点说说,++与指针*同优先级,结合顺序为至右向左结合,因此相当于*((uint16_t*)pbUsrBuf++),先*(uint16_t*)pbUsrBuf赋值只取*pdwVal++的32位数据的前16位,然后再将pbUsrBuf加上1个字节。到这里,前面定义的uint8_tReceive_Buffer[];数组中就有两个数组变量被赋值了,但这时地址还指增加一个字节,因此还需要pbUsrBuf++;让其指向Receive_Buffer[];的下下个数组变量。一次循环,知道读出所有数据。

转自http://blog.csdn.net/ringstart/article/details/6822377

关于linux0.12临界资源的一点理解和猜测

       今天看代码时突然发现,好像看到linus在写代码时的一些准则了,再根据赵博士的一句话"内核开发者不可能总是关注这些细节问题的!“,然后参照自己的理解总结出来一些准则,本人初学,如果有问题还请留言指正。

       1.在关于锁的操作上,希望是原子操作,所以不管什么先关中断再说

       2.在互斥资源上,如果下面可能睡眠,那么先上锁再说,这点可以从inode.c中的read_inode函数中可以看出!

       3.如果进程修改了某些indoe等一些公共内容,如果下面有bread之类可能睡眠的函数那么先把i_dirty置1,以免丢失数据。

      4.貌似只要是进程打开的,不是用作公共有途的,如打开一个i_node用来添加目录,打开一块缓冲区用作他用,这些都算是私有的,不算是临界,共同使用同个i_node或同一个缓冲区的可能性很小?我只能这么解释他为什么在bread后在进程中不上锁了!

关于linux0.12文件系统目录大小的一个发现

今天看到empty_dir函数发现他并不是比较i_size==32来判断是否为空,而是去读所有的块来判断是否为空,然后就觉得很奇怪,不过总觉得事出必有因,翻来覆去的找了一下,终于有了点发现,去看了一下add_entry和sys_rmdir这两个函数终于发现了一个事实!那就是目录文件的大小只会增大不会减小。下面是实验的截图:

在这个实验里我们可以看到当我建立了test2 test3 test4四个文件后

test1目录变成了80,但是我删除这3个目录项后,test1目录并没有减少,这就说明了,在empty_dir中为什么linus遍历来确定目录是不是为空了!不知道这算不算是一个小bug,还是minix1.0的文件系统中本就是如此!

关于linux0.12中的add_entry中bread中的些猜测

今天看到文件系统namei.c中的add_entry部分了,发现一个问题

                        block = create_block(dir,i/DIR_ENTRIES_PER_BLOCK);
			if (!block)
				return NULL;
			if (!(bh = bread(dir->i_dev,block))) {
				i += DIR_ENTRIES_PER_BLOCK;
				continue;
			}
			de = (struct dir_entry *) bh->b_data;

可以看到,create_block也是就是当目录的i结点不存在时便会创建一个,而在create_block调用的_bamp(inode.c)函数,而bitmap函数中又调用了new_block(bitmap.c)早对磁盘块做了些测试如下:

	for (i=0 ; i<8 ; i++)
		if (bh=sb->s_zmap[i])
			if ((j=find_first_zero(bh->b_data))<8192)
				break;
	if (i>=8 || !bh || j>=8192)
		return 0;
	if (set_bit(j,bh->b_data))
		panic("new_block: bit already set");
	bh->b_dirt = 1;
	j += i*8192 + sb->s_firstdatazone-1;
	if (j >= sb->s_nzones)
		return 0;

这也就是说,不可能会出现非法block号,那么bread失败也就无从谈起了!

退一步说:如果真的失败了那

if ((char *)de >= BLOCK_SIZE+bh->b_data)

这代码又会产生什么后果呢?

所以,我认为赵博士上面的注释有点小失误,下面的注释才是最合理的解释。

这应该是从find_entry函数中拷过来的,但是由于修改时没有注意到,所以留下的一点冗余代码,

注意!!!是冗余代码不是Bug!这也告诉我们,写代码一般不要copy,否则极易出错!

uid gid euid egid详解

由于用户在UNIX下经常会遇到SUID、SGID的概念,而且SUID和SGID涉及到系统安全,所以用户也比较关心这个问题。关于SUID、SGID的问题也经常有人提问,但回答的人一般答得不够详细,加上曾经回答过两个网友的问题,还查了一些资料,决定整理成本文,以供大家参考。限于本人的水平问题,文章中如果有不当之处,请广大网友指正。

一、UNIX下关于文件权限的表示方法和解析

SUID 是 Set User ID, SGID 是 Set Group ID的意思。

UNIX下可以用ls -l 命令来看到文件的权限。用ls命令所得到的表示法的格式是类似这样的:-rwxr-xr-x 。下面解析一下格式所表示的意思。这种表示方法一共有十位:

9 8 7 6 5 4 3 2 1 0 
– r w x r – x r – x

第9位表示文件类型,可以为p、d、l、s、c、b和-:

p表示命名管道文件 
d表示目录文件 
l表示符号连接文件 
-表示普通文件 
s表示socket文件 
c表示字符设备文件 
b表示块设备文件

第8-6位、5-3位、2-0位分别表示文件所有者的权限,同组用户的权限,其他用户的权限,其形式为rwx:

r表示可读,可以读出文件的内容

w表示可写,可以修改文件的内容

x表示可执行,可运行这个程序

没有权限的位置用-表示

例子:

ls -l myfile显示为:

-rwxr-x— 1 foo staff 7734 Apr 05 17:07 myfile

表示文件myfile是普通文件,文件的所有者是foo用户,而foo用户属于staff组,文件只有1个硬连接,长度是7734个字节,最后修改时间4月5日17:07。

所有者foo对文件有读写执行权限,staff组的成员对文件有读和执行权限,其他的用户对这个文件没有权限。

如果一个文件被设置了SUID或SGID位,会分别表现在所有者或同组用户的权限的可执行位上。例如:

1、-rwsr-xr-x 表示SUID和所有者权限中可执行位被设置

2、-rwSr–r– 表示SUID被设置,但所有者权限中可执行位没有被设置

3、-rwxr-sr-x 表示SGID和同组用户权限中可执行位被设置

4、-rw-r-Sr– 表示SGID被设置,但同组用户权限中可执行位没有被社

其实在UNIX的实现中,文件权限用12个二进制位表示,如果该位置上的值是

1,表示有相应的权限:

11 10 9 8 7 6 5 4 3 2 1 0 
S G T r w x r w x r w x

第11位为SUID位,第10位为SGID位,第9位为sticky位,第8-0位对应于上面的三组rwx位。

11 10 9 8 7 6 5 4 3 2 1 0

上面的-rwsr-xr-x的值为: 1 0 0 1 1 1 1 0 1 1 0 1

-rw-r-Sr–的值为: 0 1 0 1 1 0 1 0 0 1 0 0


给文件加SUID和SUID的命令如下:

chmod u+s filename 设置SUID位

chmod u-s filename 去掉SUID设置

chmod g+s filename 设置SGID位

chmod g-s filename 去掉SGID设置

另外一种方法是chmod命令用八进制表示方法的设置。如果明白了前面的12位权限表示法也很简单。

二、SUID和SGID的详细解析

由于SUID和SGID是在执行程序(程序的可执行位被设置)时起作用,而可执行位只对普通文件和目录文件有意义,所以设置其他种类文件的SUID和SGID位是没有多大意义的。

首先讲普通文件的SUID和SGID的作用。例子:

如果普通文件myfile是属于foo用户的,是可执行的,现在没设SUID位,ls命令显示如下:

-rwxr-xr-x 1 foo staff 7734 Apr 05 17:07myfile任何用户都可以执行这个程序。UNIX的内核是根据什么来确定一个进程对资源的访问权限的呢?是这个进程的运行用户的(有效)ID,包括user id和group id。用户可以用id命令来查到自己的或其他用户的user id和group id。

除了一般的user id 和group id外,还有两个称之为effective 的id,就是有效id,上面的四个id表示为:uid,gid,euid,egid。内核主要是根据euid和egid来确定进程对资源的访问权限。

一个进程如果没有SUID或SGID位,则euid=uidegid=gid,分别是运行这个程序的用户的uid和gid。例如kevin用户的uid和gid分别为204和202,foo用户的uid和gid为200,201,kevin运行myfile程序形成的进程的euid=uid=204,egid=gid=202,内核根据这些值来判断进程对资源访问的限制,其实就是kevin用户对资源访问的权限,和foo没关系。 


如果一个程序设置了SUID,则euid和egid变成被运行的程序的所有者的uid和gid,例如kevin用户运行myfile,euid=200,egid=201,uid=204,gid=202,则这个进程具有它的属主foo的资源访问权限。

SUID的作用就是这样:让本来没有相应权限的用户运行这个程序时,可以访问他没有权限访问的资源。passwd就是一个很鲜明的例子。

SUID的优先级比SGID高,当一个可执行程序设置了SUID,则SGID会自动变成相应的egid。

下面讨论一个例子:

UNIX系统有一个/dev/kmem的设备文件,是一个字符设备文件,里面存储了核心程序要访问的数据,包括用户的口令。所以这个文件不能给一般的用户读写,权限设为:cr–r—– 1 root system 2, 1 May 25 1998 kmem

但ps等程序要读这个文件,而ps的权限设置如下:

-r-xr-sr-x 1 bin system 59346 Apr 05 1998 ps

这是一个设置了SGID的程序,而ps的用户是bin,不是root,所以不能设置SUID来访问kmem,但大家注意了,bin和root都属于system组,而且ps设置了SGID,一般用户执行ps,就会获得system组用户的权限,而文件kmem的同组用户的权限是可读,所以一般用户执行ps就没问题了。但有些人说,为什么不把ps程序设置为root用户的程序,然后设置SUID位,不也行吗?这的确可以解决问题,但实际中为什么不这样做呢?因为SGID的风险比SUID小得多,所以出于系统安全的考虑,应该尽量用SGID代替SUID的程序,如果可能的话。下面来说明一下SGID对目录的影响。SUID对目录没有影响。如果一个目录设置了SGID位,那么如果任何一个用户对这个目录有写权限的话,他在这个目录所建立的文件的组都会自动转为这个目录的属主所在的组,而文件所有者不变,还是属于建立这个文件的用户。


三、关于SUID和SGID的编程

和SUID和SGID编程比较密切相关的有以下的头文件和函数:

#include

#include

uid_t getuid(void);

uid_t geteuid(void);

gid_t getgid (void);

gid_t getegid (void);

int setuid (uid_t UID);

int setruid (uid_t RUID);

int seteuid (uid_t EUID);

int setreuid (uid_t RUID,uid_t EUID);

int setgid (gid_t GID);

int setrgid (gid_t RGID);

int setegid (git_t EGID);

int setregid (gid_t RGID, gid_t EGID);

具体这些函数的说明在这里就不详细列出来了,要用到的可以用man查。

SUID/SGID :

假如你有文件a.txt

#ls -l a.txt

-rwxrwxrwx

#chmod 4777 a.txt

-rwsrwxrwx ======>注意s位置

#chmod 2777 a.txt

-rwxrwsrwx ======>注意s位置

#chmod 7777 a.txt

-rwsrwxswt ======>出现了t,t的作用在内存中尽量保存a.txt,节省系统再加载的时间.

现在再看前面设置 SUID/SGID作用:

#cd /sbin

#./lsusb

#su aaa(普通用户)

$./lsusb

是不是现在显示出错?

$su

#chmod 4755 lsusb

#su aaa

$./lsusb

… 现在明白了吗?本来是只有root用户才能执行的命令,加了SUID后,普通用户就可以像root一样的用,权限提升了。上面是对于文件来说的,对于目录也差不多!

目录的S属性使得在该目录下创建的任何文件及子目录属于该目录所拥有的组,目录的T属性使得该目录的所有者及root才能删除该目录。还有对于s与S,设置SUID/SGID需要有运行权限,否则用ls -l后就会看到S,证明你所设置的SUID/SGID没有起作用。

Why we need suid,how do we use suid?

r — 读访问

   w — 写访问

   x — 执行许可

   s — SUID/SGID

   t — sticky位

那么 suid/sgid是做什么的? 为什么会有suid位呢?

要想明白这个,先让我们看个问题:如果让每个用户更改自己的密码?

用户修改密码,是通过运行命令passwd来实现的。最终必须要修改/etc/passwd文件,而passwd的文件的属性是:

#ls -l /etc/passwd

-rw-r–r– 1 root root 2520 Jul 12 18:25 passwd

我们可以看到passwd文件只有对于root用户是可写的,而对于所有的他用户来说都是没有写权限的。 那么一个普通的用户如何能够通过运行passwd命令修改这个passwd文件呢?

为了解决这个问题,SUID/SGID便应运而生。而且AT&T对它申请了专利。 呵呵。

SUID和SGID是如何解决这个问题呢?

首先,我们要知道一点:进程在运行的时候,有一些属性,其中包括 实际用户ID,实际组ID,有效用户ID,有效组ID等。 实际用户ID和实际组ID标识我们是谁,谁在运行这个程序,一般这2个字段在登陆时决定,在一个登陆会话期间, 这些值基本上不改变。

而有效用户ID和有效组ID则决定了进程在运行时的权限。内核在决定进程是否有文件存取权限时,是采用了进程的有效用户ID来进行判断的。

知道了这点,我们来看看SUID的解决途径:

当一个程序设置了为SUID位时,内核就知道了运行这个程序的时候,应该认为是文件的所有者在运行这个程序。即该程序运行的时候,有效用户ID是该程序的所有者。举个例子:

[root@sgrid5 bin]# ls -l passwd

-r-s–s–x 1 root root 16336 Feb 14 2003 passwd

虽然你以test登陆系统,但是当你输入passwd命令来更改密码的时候,由于passwd设置了SUID位,因此虽然进程的实际用户ID是test对应的ID,但是进程的有效用户ID则是passwd文件的所有者root的ID,因此可以修改/etc/passwd文件。

让我们看另外一个例子。

ping命令应用广泛,可以测试网络是否连接正常。ping在运行中是采用了ICMP协议,需要发送ICMP报文。但是只有root用户才能建立ICMP报文,如何解决这个问题呢?同样,也是通过SUID位来解决。

[root@sgrid5 bin]# ls -l /bin/ping

-rwsr-sr-x 1 root root 28628 Jan 25 2003 /bin/ping

我们可以测试一下,如果去掉ping的SUID位,再用普通用户去运行命令,看会怎么样。

[root@sgrid5 bin]#chmod u-s /bin/ping

[root@sgrid5 bin]# ls -l ping 

-rwxr-xr-x 1 root root 28628 Jan 25 2003 ping

[root@sgrid5 bin]#su test

[test@sgrid5 bin]$ ping byhh.net

ping: icmp open socket: Operation not permitted

SUID虽然很好了解决了一些问题,但是同时也会带来一些安全隐患。

因为设置了 SUID 位的程序如果被攻击(通过缓冲区溢出等方面),那么hacker就可以拿到root权限。

因此在安全方面特别要注意那些设置了SUID的程序。

通过以下的命令可以找到系统上所有的设置了suid的文件:

[root@sgrid5 /]# find / -perm -04000 -type f -ls

对于这里为什么是4000,大家可以看一下前面的st_mode的各bit的意义就明白了。

在这些设置了suid的程序里,如果用不上的,就最好取消该程序的suid位

转自:http://blog.csdn.net/leo_wanta/article/details/7266551