USB_scsi 之旅
现在总结一下scsi,scsi协议有很多,所以只总结这次在usb mass storage里面用到的协议,主要包括inquiry,format , read write等等命令。
下面会一个一个总结。
U盘需要处理的命令如下:
1:inquiry:设备的一个描述,告诉host你的设备是什么,名字叫什么,用的什么协议,这里用的SCSI协议—SPC2
2:READ FORMAT CAPACITIES:读格式容量(The READ FORMAT CAPACITIES command allows the host to request a list of the possible capacities that can be formatted on the currently installed medium.)
3:READ CAPACITY:读取容量信息
4:READ(10):回发在逻辑单元的数据,既回发MBR(Main Boot Record)主引导扇区
5:SENSE6:目的在于获得设备内部很多潜在的信息,其中包括了是否设置了写保 (The MODE SENSE(6) command (see table 62) provides a means for a device server to report parameters to an application client. It is a complementary command to the MODE SELECT(6) command. Device servers that implement the MODE SENSE(6) command shall also implement the MODE SELECT(6) command.)
6:WRITE(10):host向slave发生数据并写在u盘存储器里面。
7:TEST UNIT READY:检查U盘准备好没有。
- 1:inquiry
// The standard INQUIRY data shall contain at least 36 bytes
// This is the reduced structure for Mass Storage Devices
typedef struct
{
cyg_uint8 peripheral; // Device Type
cyg_uint8 rmb; // Removable Media Bit
cyg_uint8 version; // Version Field
cyg_uint8 resp_data_format; // Response Data Format
cyg_uint8 additional_len; // Additional Length
cyg_uint8 sccstp; // SCC Supported (include embedded storage array)
cyg_uint8 bque; // Basic Queuing
cyg_uint8 cmdque; // Command Queuing
cyg_uint8 vendor_id[];
cyg_uint8 product_id[];
cyg_uint8 product_rev[];
} msd_scsi_inq_resp;
这个结构体就是为inquiry准备的数据包,这个命令是usb mass storage第一个接收到的命令

在这里只需要关注两个字段OPERATION CODE,ALLOCATION LENGTH。其它的还没用到,每个命令都有一个OPERATION CODE用来标志这个命令是什么命令,毕竟我们不会看数字就知道什么意思,电脑也不会看文字就是要做什么,是吧。现在这里是12,我们查一查SPC-2协议,就知道OPERATION CODE为12就知道这次host需要slave做什么操作。
既然知道了这个操作,那么咱们就应该回复一下,毕竟来而不往非礼也。那咱们得准备一下礼物送给host,不然这个门打不开,马上就关起来了,就好像现在这个社会,有钱有权什么都可以,这个社会让我们这些刚毕业的怎么生存啊,咱学习嵌入式就是为了多赚点money。下面就是一个能够回复Host的inquiry包。
const msd_scsi_inq_resp inrq_resp = {
USBS_SCSI_DIRECT_ACCESS_BLOCK_DEVICE, // Direct Access Block device 0x00
USBS_SCSI_REMOVABLE_DEVICE, // Device is removable
0x04, // Comply with SPC-2
0x02, // Standard format
0x20,
0x00,
0x00,
0x00,
{'R','i','s','e','t','e','k',' '},
{'M','a','s','s',' ','S','t','o','r','a','g','e',' ',' ',' ',' '},
{'},
};
这个图下面还有数据端,但是在这里就只回了一个最小的数据包回去,那就总结这些字段就好了吧。
第0个字节:有这两个PERIPHERAL QUALIFIER,PERIPHERAL DEVICE TYPE东东需要关注,PERIPHERAL QUALIFIER这里三位全为0,PERIPHERAL DEVICE TYPE的值都为0,表明意思是Direct-access device。
第一个字节:只要关注RMB表明这个设备可不可以移除,原文为:A removable medium (RMB) bit of zero indicates that the medium is not removable. A RMB bit of one indicates that the medium is removable.
第二个字节:VERSION表明数据交流使用的协议版本是SPC-2,其值为0x04,
第三个字节:An AERC bit of one indicates that the processor device is capable of accepting asynchronous event reports. An AERC bit of zero indicates that the processor device does not support asynchronous event reports;
RESPONSE DATA FORMAT:A RESPONSE DATA FORMAT field value of two indicates that the data shall be in the format specified in this standard.
这里写为2就好,整个字节的值就是为0x02。
第四个字节:ADDITIONAL LENGTH表明这个值附加了多少数据,Response is 0x20 + 4 bytes,表明这个请求包有多长,这里为36,前面有四个数据下面有32个刚好36个数据。
第五,第六,第七个字节都填0,想知道为什么的话,看看手册吧,不然这么总结就成了在翻译协议了。
第八字节—第十五字节:VENDOR IDENTIFICATION,在8个字节里面,随便写一些字符,写法如上面就可以了。而PRODUCT IDENTIFICATION和PRODUCT REVISION LEVEL都是想填什么就填什么,只要没有操作范围。一定要注意范围哦。
static inline cyg_int32 usb_msd_scsi_handle_inquiry( usb_msd * msd )
{
cyg_int32 tx_bytes = sizeof(msd_scsi_inq_resp);
DBG("USB Slave MSD: SCSI inquiry\n");
// Copy over to message buffer
memcpy( msd->buffer, (cyg_uint8 *) &inrq_resp, tx_bytes );
usb_msd_tx_send(msd , tx_bytes);
return tx_bytes;
}
通过端点2就把数据发给host,第一步就完成了。
- 2:READ FORMAT CAPACITIES
下表就是READ FORMATCAPACITIES Command的命令表,这个命令也只需要关注两个字节,Operation Code (23h) and Logical Unit Number。Logical Unit Number(LUN),前面说过这个LUN,这里就为一个LUN。下面准备回复的数据。
这个usb_msd_scsi_read_format_capacities函数就是用来准备回复host数据的函数并且发送给host。还是按照协议回复,上图。
第一个需要准备的是Capacity List Header,然后是Current/Maximum Capacity Header等等。
又是一个图,这图图还真多,看看图,开心了吧,只用关注一个字节就好了,手册上说这个字段写0x08就好。
接下来是Current/Maximum Capacity Descriptor,0~3字节用来描述这个存储设备有多少块block,第四个字节Descriptor Code的值为0x02表示Formatted Media,5~7字节用来描述每块有多大,这次写的存储器每块的大小为512。下面的字段咱们偷偷懒就不回了,其实这样是很正确的。
static inline cyg_int32 usb_msd_scsi_read_format_capacities( usb_msd * msd )
{
cyg_uint8 BulkBuf[] ={
[ ] = 0x00,
[ ] = 0x00,
[ ] = 0x00,
[ ] = 0x08,
[ ] = (MSC_BlockCount >> ) & 0xFF,
[ ] = (MSC_BlockCount >> ) & 0xFF,
[ ] = (MSC_BlockCount >> ) & 0xFF,
[ ] = (MSC_BlockCount >> ) & 0xFF,
[ ] = 0x02,
[ ] = (MSC_BlockSize >> ) & 0xFF,
[] = (MSC_BlockSize >> ) & 0xFF,
[] = (MSC_BlockSize >> ) & 0xFF,
};
memset(msd->buffer , , );
memcpy(msd->buffer , BulkBuf, );
usb_msd_tx_send(msd , );
;
}
- 3:READ CAPACITY
这里回复存储容量,但是这里和上面的format还有点不明白,以后再处理,现在先这么总结先。现在在上图,图解直观。
这个命令关注五个东东,Operation Code (25h),Logical Unit Number,RelAdr,Logical Block Address,PMI。其实待会有惊喜
Operation Code (25h),Logical Unit Number这两个就不说了,已经说过了,Logical Block Address(逻辑块地址)应该怎么设置呢,手册上有这么一句话,看了应该很是高兴。RelAdr: This bit should be set to zero. Logical Block Address should be set to zero.
PMI: This bit should be set to zero.贴了三句E文,看了后,是不是很开心,不用在烦恼剩下的描述了,全是0。既然协议都说是0那么就不管了。命令是不管了,但是偶们还是要给host回复点东西吧,送礼了哦,不然host会嚷嚷你去睡觉。
这个回复和format差不多,还比它简单,只是注意现在在Block Count那里减1,我想应该是0的问题,如果0是第一个数据,那么你自然要减一个下来。
static inline cyg_int32 usb_msd_scsi_handle_capacity( usb_msd * msd )
{
DBG("usb_msd_scsi_handle_capacity \n");
cyg_uint8 length = ;
cyg_uint8 BulkBuf[ ] ={
[ ] = ((MSC_BlockCount - ) >> ) & 0xFF,
[ ] = ((MSC_BlockCount - ) >> ) & 0xFF,
[ ] = ((MSC_BlockCount - ) >> ) & 0xFF,
[ ] = ((MSC_BlockCount - ) >> ) & 0xFF,
[ ] = (MSC_BlockSize >> ) & 0xFF,
[ ] = (MSC_BlockSize >> ) & 0xFF,
[ ] = (MSC_BlockSize >> ) & 0xFF,
[ ] = (MSC_BlockSize >> ) & 0xFF,
};
memset(msd->buffer , , );
memcpy(msd->buffer , BulkBuf, length);
usb_msd_tx_send(msd , length);
;
}
- 4:READ(10)
现在直奔主题了,上图
这个命令就是用来读取存储器里面存储的数据,包括MBR等数据。现在挑不熟悉的总结了,熟悉的就不说了。
咋一看都是比较熟悉的呢,那几个不熟悉的,在协议上说全是0。这里还有一个关注一下,Logical Block Address前面说的是些0,但是这里不一样了,host必须给出存储器的块,slave才知道提交那块的数据。usb_msd_scsi_rwsetup
就是用来解析CBW包里面包含的块和要求回复数据的长度。Transfer Length (MSB)和Transfer Length (LSB)存储了数据的长度。
static inline void usb_msd_scsi_MemoryRead (usb_msd * msd) {
cyg_uint8 len;
DBG("usb_msd_scsi_MemoryRead \n");
if (Length > MSC_MAX_PACKET) {
len = MSC_MAX_PACKET;
} else {
len= Length;
}
if ((Offset + len) > MSC_MemorySize) {
len = MSC_MemorySize - Offset;
BulkStage = MSC_BS_DATA_IN_LAST_STALL;
}
usb_scsi_send(&DiskImage[Offset] , len);
Offset += len;
Length -= len;
msd->csw.data_residue -= len;
) {
BulkStage = MSC_BS_DATA_IN_LAST;
}
if (BulkStage != MSC_BS_DATA_IN) {
msd->csw.status = CSW_CMD_PASSED;
}
}
static inline cyg_int32 usb_msd_scsi_handle_read(usb_msd * msd)
{
DBG("usb_msd_scsi_handle_read \n");
if(usb_msd_scsi_rwsetup(msd))
{
) {
BulkStage = MSC_BS_DATA_IN;
usb_msd_scsi_MemoryRead(msd);
} else {
stm32_usb_set_rxep_status (msd->tx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL
|CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);
msd->csw.status = CSW_PHASE_ERROR;
usb_msd_csw_send();
}
}
;
}
- 5:SENSE6
这里需要知道SENSE6命令的操作码是0x1A,其他的都不怎么重要了,至少在这个实验里面。那应该怎么回复数据呢,如下图所示
MODE DATA LENGTH表示接下来有几个字段,这里自然是三个,接下来的三个字段都得换一个手册了,从SPC-2 JUMP到SSC-3。
在SSC-3里面有这么一个图图,就是用来解析第三个字段的。表示是不是只读。当WP为0表示可读可写,反之只读。其他的就写0了。要是在其他的情况,比如不是usb mass storage那么还得研究协议哦。
static inline cyg_int32 usb_msd_scsi_handle_sense(usb_msd * msd)
{
DBG("usb_msd_scsi_handle_sense \n");
cyg_uint8 length = ;
cyg_uint8 BulkBuf[ ] ={
[ ] = 0x03,
[ ] = 0x00,
[ ] = 0x80,
[ ] = 0x00,
};
memset(msd->buffer , , );
memcpy(msd->buffer , BulkBuf, length);
usb_msd_tx_send(msd , length);
;
}
- 6:WRITE(10)
这个命令和read差不了多少,只是一个向host提交数据,一个是host向salve写数据。数据包不同就在操作码。
static inline void usb_msd_scsi_MemoryWrite (usb_msd * msd) {
//cyg_int32 n;
DBG("usb_msd_scsi_MemoryWrite \n");
if ((Offset + bulk_length) > MSC_MemorySize) {
bulk_length = MSC_MemorySize - Offset;
BulkStage = MSC_BS_CSW;
stm32_usb_set_rxep_status (msd->rx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL
|CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);
}
// for (n = 0; n < bulk_length; n++) {
//Memory[Offset + n] = msd->buffer[n];
// }
Offset += bulk_length;
Length -= bulk_length;
msd->csw.data_residue -= bulk_length;
) || (BulkStage == MSC_BS_CSW)) {
msd->csw.status= CSW_CMD_PASSED;
usb_msd_csw_send();
}
}
static inline void usb_msd_scsi_MemoryVerify (usb_msd * msd) {
#if 0
cyg_int32 reg_val , n;
DBG("usb_msd_scsi_MemoryVerify \n");
if ((Offset + bulk_length) > MSC_MemorySize) {
bulk_length = MSC_MemorySize - Offset;
BulkStage = MSC_BS_CSW;
stm32_usb_set_rxep_status (msd->rx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL|CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);
}
; n < bulk_length; n++) {
if (Memory[Offset + n] != msd->buffer[n]) {
MemOK = __FALSE;
break;
}
}
Offset += bulk_length;
Length -= bulk_length;
msd->csw.data_residue -= bulk_length;
) || (BulkStage == MSC_BS_CSW)) {
msd->csw.status= (MemOK) ? CSW_CMD_PASSED : CSW_CMD_FAILED;
usb_msd_csw_send();
}
#endif
}
BOOL usb_msd_scsi_rwsetup (usb_msd * msd) {
cyg_uint32 n;
DBG("usb_msd_scsi_rwsetup \n");
n = (msd->cbw->cb.data[]<< ) |
(msd->cbw->cb.data[] << ) |
(msd->cbw->cb.data[] << ) |
(msd->cbw->cb.data[] << );
Offset = n * MSC_BlockSize;
]) {
case USBS_SCSI_READ_10:
case USBS_SCSI_WRITE_10:
case USBS_SCSI_VERIFY_10:
n = (msd->cbw->cb.data[] << ) |
(msd->cbw->cb.data[] << );
break;
case USBS_SCSI_READ_12:
case USBS_SCSI_WRITE_12:
n = (msd->cbw->cb.data[] << ) |
(msd->cbw->cb.data[] << ) |
(msd->cbw->cb.data[] << ) |
(msd->cbw->cb.data[] << );
break;
}
Length = n * MSC_BlockSize;
) {
msd->csw.status = CSW_CMD_FAILED;
usb_msd_csw_send();
return (__FALSE);
}
if (msd->cbw->data_transfert_len != Length) {
) {
stm32_usb_set_rxep_status (msd->tx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL
|CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);
} else {
stm32_usb_set_rxep_status (msd->rx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL
|CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);
}
msd->csw.status = CSW_CMD_FAILED;
usb_msd_csw_send();
return (__FALSE);
}
return (__TRUE);
}
- 7:TEST UNIT READY
这个命令没什么好说的,只是看看磁盘准备好没,咱们也不用向host进贡了,高兴一下吧。
static inline cyg_int32 usb_msd_scsi_handle_test_unit_ready(usb_msd * msd)
{
DBG("usb_msd_scsi_handle_test_unit_ready \n");
) {
) {
stm32_usb_set_rxep_status (msd->tx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL
|CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);
} else {
stm32_usb_set_rxep_status (msd->rx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL
|CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);
}
}
msd->csw.status = CSW_CMD_PASSED;
usb_msd_csw_send();
;
}
static inline cyg_int32 usb_msd_scsi_handle_write(usb_msd * msd)
{
DBG("usb_msd_scsi_handle_write \n");
if (usb_msd_scsi_rwsetup(msd)) {
) {
BulkStage = MSC_BS_DATA_OUT;
usb_scsi_setup_status(msd->rx_ep_num);
} else {
stm32_usb_set_rxep_status (msd->tx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL
|CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);
msd->csw.status = CSW_PHASE_ERROR;
usb_msd_csw_send();
}
}
;
}
static inline cyg_int32 usb_msd_scsi_handle_verify(usb_msd * msd )
{
DBG("usb_msd_scsi_handle_verify \n");
//usb_msd_scsi_MemoryVerify(msd);
;
}
cyg_int32 usb_msd_scsi_handle_cmd( usb_msd * msd , cyg_uint8 *ctrlep_msg_buffer )
{
cyg_uint32 ret;
bulk_length = strlen((;
msd->cbw =(usb_msd_cbw *)ctrlep_msg_buffer;
cyg_uint8 cmd = msd->cbw->cb.data[];
usb_scsi_command = cmd;
msd->csw.tag = msd->cbw->tag; //csw tag
msd->csw.data_residue = msd->cbw->data_transfert_len; //csw length
DBG("usb_msd_scsi_handle_cmd cmd = 0x%x msd->csw.data_residue = %d\n" , cmd , msd->csw.data_residue);
switch( cmd )
{
case USBS_SCSI_INQUIRY:
ret = usb_msd_scsi_handle_inquiry(msd);
break;
case USBS_SCSI_READ_CAPACITY:
ret = usb_msd_scsi_handle_capacity( msd );
break;
case USBS_SCSI_READ_10:
ret = usb_msd_scsi_handle_read(msd );
break;
case USBS_SCSI_WRITE_10:
ret = usb_msd_scsi_handle_write( msd );
break;
case USBS_SCSI_REQUEST_SENSE:
//ret = usb_msd_scsi_handle_req_sense( msd);
break;
case USBS_SCSI_SENSE_6:
ret = usb_msd_scsi_handle_sense( msd );
break;
case USBS_SCSI_TEST_UNIT_READY:
ret = usb_msd_scsi_handle_test_unit_ready( msd );
break;
case USBS_SCSI_VERIFY_10:
ret = usb_msd_scsi_handle_verify( msd );
break;
case USBS_SCSI_READ_FORMAT_CAPACITIES:
ret = usb_msd_scsi_read_format_capacities( msd );
break;
case USBS_SCSI_PREVENT_ALLOW_REMOVAL:
//ret = usb_msd_scsi_handle_removal( msd );
break;
default:
// Use for all commands not implemented, not
// supported
DBG("USB Slave MSD: SCSI illegal request %x \n", cmd );
msd->csw.status = USBS_MSD_CSW_STATUS_FAILED;
ret = ;
break;
}
return ret;
}
void usb_msd_scsi_bulk_out(cyg_uint8 *ctrlep_msg_buffer , cyg_uint32 length)
{
cyg_uint32 reg_val;
DBG("usb_msd_scsi_bulk_out length = %d \n" , length);
memset(msd.buffer , , );
bulk_length = length;
memcpy(msd.buffer , ctrlep_msg_buffer, bulk_length);
switch (BulkStage) {
case MSC_BS_CBW:
usb_msd_scsi_handle_cmd(&msd , ctrlep_msg_buffer);
break;
case MSC_BS_DATA_OUT:
switch (usb_scsi_command) {
case USBS_SCSI_WRITE_10:
case USBS_SCSI_WRITE_12:
usb_msd_scsi_MemoryWrite(&msd );
usb_scsi_setup_status(msd.rx_ep_num);
break;
case USBS_SCSI_VERIFY_10:
//usb_msd_scsi_MemoryVerify(&msd);
break;
}
break;
case MSC_BS_CSW:
break;
default:
HAL_READ_UINT32 (CYGHWR_HAL_STM32_USB + CYGHWR_HAL_STM32_USB_EPXR (msd.rx_ep_num), reg_val);
stm32_usb_set_rxep_status (msd.rx_ep_num , CYGHWR_HAL_STM32_USB_EPXR_STATRX_STALL
|CYGHWR_HAL_STM32_USB_EPXR_STATTX_STALL);
msd.csw.status = CSW_PHASE_ERROR;
usb_msd_csw_send();
break;
}
}
void usb_msd_scsi_bulk_in()
{
DBG("usb_msd_scsi_bulk_in \n");
switch (BulkStage) {
case MSC_BS_DATA_IN:
]) {
case USBS_SCSI_READ_10:
usb_msd_scsi_MemoryRead(&msd );
break;
}
break;
case MSC_BS_DATA_IN_LAST:
usb_msd_csw_send();
break;
case MSC_BS_DATA_IN_LAST_STALL:
usb_msd_csw_send();
break;
case MSC_BS_CSW:
BulkStage = MSC_BS_CBW;
break;
}
}
终于写完了肩膀都累了。第一次做usb协议,写的不好的见谅,有错希望给予指正。如果有STM32开发USB的可以看看 http://fpgamcu.gotoip55.com/forum.php?mod=viewthread&tid=37&pid=96&page=1&extra=#pid96pid96
这个论坛可以找本人!
USB_scsi 之旅的更多相关文章
- Linq之旅:Linq入门详解(Linq to Objects)
示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...
- WCF学习之旅—第三个示例之四(三十)
上接WCF学习之旅—第三个示例之一(二十七) WCF学习之旅—第三个示例之二(二十八) WCF学习之旅—第三个示例之三(二十九) ...
- 【C#代码实战】群蚁算法理论与实践全攻略——旅行商等路径优化问题的新方法
若干年前读研的时候,学院有一个教授,专门做群蚁算法的,很厉害,偶尔了解了一点点.感觉也是生物智能的一个体现,和遗传算法.神经网络有异曲同工之妙.只不过当时没有实际需求学习,所以没去研究.最近有一个这样 ...
- Hadoop学习之旅二:HDFS
本文基于Hadoop1.X 概述 分布式文件系统主要用来解决如下几个问题: 读写大文件 加速运算 对于某些体积巨大的文件,比如其大小超过了计算机文件系统所能存放的最大限制或者是其大小甚至超过了计算机整 ...
- .NET跨平台之旅:在生产环境中上线第一个运行于Linux上的ASP.NET Core站点
2016年7月10日,我们在生产环境中上线了第一个运行于Linux上的ASP.NET Core站点,这是一个简单的提供后端服务的ASP.NET Core Web API站点. 项目是在Windows上 ...
- 【Knockout.js 学习体验之旅】(3)模板绑定
本文是[Knockout.js 学习体验之旅]系列文章的第3篇,所有demo均基于目前knockout.js的最新版本(3.4.0).小茄才识有限,文中若有不当之处,还望大家指出. 目录: [Knoc ...
- 【Knockout.js 学习体验之旅】(2)花式捆绑
本文是[Knockout.js 学习体验之旅]系列文章的第2篇,所有demo均基于目前knockout.js的最新版本(3.4.0).小茄才识有限,文中若有不当之处,还望大家指出. 目录: [Knoc ...
- 【Knockout.js 学习体验之旅】(1)ko初体验
前言 什么,你现在还在看knockout.js?这货都已经落后主流一千年了!赶紧去学Angular.React啊,再不赶紧的话,他们也要变out了哦.身旁的90后小伙伴,嘴里还塞着山东的狗不理大蒜包, ...
- WCF学习之旅—第三个示例之二(二十八)
上接WCF学习之旅—第三个示例之一(二十七) 五.在项目BookMgr.Model创建实体类数据 第一步,安装Entity Framework 1) 使用NuGet下载最新版的Entity Fram ...
随机推荐
- UESTC 75 The Queen's New Necklaces
题意:一个项链的珠子的颜色有若干种.每种颜色的珠子个数为Ai.求有多少种不同的项链? 我们考虑,如果旋转i个珠子,那么会产生gcd(n,i)个循环节,每个循环节的大小我们假设为K,那么如果有一个颜色的 ...
- 屌炸天实战 MySQL 系列教程(二) 史上最屌、你不知道的数据库操作
此篇写MySQL中最基础,也是最重要的操作! 第一篇:屌炸天实战 MySQL 系列教程(一) 生产标准线上环境安装配置案例及棘手问题解决 第二篇:屌炸天实战 MySQL 系列教程(二) 史上最屌.你不 ...
- 避免ssh断开导致运行命令的终止:screen
事情是这样的,需要使用ssh登陆服务器,进行工程的编译,结果不知道什么原因ssh出现write failed:broken pipe,掉线了.反复实验了好几次还是这样(白花花的时间啊,又是config ...
- Smarty 变量使用
Smarty的标签都是使用定界符括起来. 默认定界符是{ 和 }, 但定界符可以被改变. 比如说在本手册,我们会假定你在使用默认的定界符. 在Smarty看来,任何在定界符之外的内容,都是静态的,或者 ...
- 深入浅出Node.js (3) - 异步I/O
3.1 为什么要异步I/O 3.1.1 用户体验 3.1.2 资源分配 3.2 异步I/O实现现状 3.2.1 异步I/O与非阻塞I/O 3.2.2 理想的非阻塞异步I/O 3.2.3 现实的异步I/ ...
- 深入解析spring中用到的九种设计模式
转载请注明出处,文章首发于:http://itxxz.com/a/javashili/tuozhan/2014/0601/7.html 设计模式作为工作学习中的枕边书,却时常处于勤说不用的尴尬境地,也 ...
- cf448A Rewards
A. Rewards time limit per test 1 second memory limit per test 256 megabytes input standard input out ...
- PhpForm表单相关的超全局变量操作
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- 后台数据导出为Excel
数据导出的方法如下: 一.下载office的类库:microsoft.office.interop.excel.zip 根据电脑安装的office版本选择引入相应的类库,office2007选择12. ...
- Linux正則表達式-反复出现的字符
星号(*)元字符表示它前面的正則表達式能够出现零次或多次.也就是说,假设它改动了单个字符.那么该字符能够在那里也能够不在那里,而且假设它在那里,那可能会不止出现一个.能够使用星号元字符匹配出如今引號中 ...