When asked which of the changes in 2.6.24 was most likely to create problems, an informed observer might well point at the i386/x86_64 merger. As it happens, that large patch set has gone in with relatively few hitches, but a rather smaller change has created quite a bit of fallout. The change in question is the updated API for the management of scatterlists, which are used in scatter/gather I/O. This work broke a number of in-tree drivers, so it seems likely to affect a lot of out-of-tree code as well.
Scatter / gather I/O는 시스템이 실제 메모리에 분산되어있는 버퍼에 대해 DMA I / O 작업을 수행 할 수있게합니다. 예를 들어, 사용자 공간에서 생성 된 대형 (다중 페이지) 버퍼의 경우를 생각해보십시오. 응용 프로그램은 연속적인 범위의 가상 주소를 보지만 그 주소 뒤에 있는 물리적 페이지는 거의 서로 인접하지 않습니다. 단일 I / O 작업에서 해당 버퍼를 장치에 기록하려면 다음 두 가지 중 하나를 수행해야합니다. (1) 데이터를 물리적으로 인접한 버퍼에 복사해야하거나 (2) 장치가 물리적 주소 및 길이 목록을 사용하여 각 세그먼트에서 올바른 양의 데이터를 가져옵니다. Scatter / gatter I/O는, 연속적인 버퍼로 데이터를 복사 할 필요를 제거함으로써, I/O 작업의 효율성을 크게 높일 수있을뿐만 아니라 물리적으로 연속적인 대형 버퍼의 생성이 처음에는 문제가 될 수 있다는 문제를 해결할 수 있습니다.
Scatter/gather I/O allows the system to perform DMA I/O operations on buffers which are scattered throughout physical memory. Consider, for example, the case of a large (multi-page) buffer created in user space. The application sees a continuous range of virtual addresses, but the physical pages behind those addresses will almost certainly not be adjacent to each other. If that buffer is to be written to a device in a single I/O operation, one of two things must be done: (1) the data must be copied into a physically-contiguous buffer, or (2) the device must be able to work with a list of physical addresses and lengths, grabbing the right amount of data from each segment. Scatter/gather I/O, by eliminating the need to copy data into contiguous buffers, can greatly increase the efficiency of I/O operations while simultaneously getting around the problem that the creation of large, physically-contiguous buffers can be problematic in the first place.
커널 내에서, scatter / gather DMA 연산에 사용될 버퍼는 <linux / scatterlist.h>에 정의 된 하나 이상의 scatterlist 구조의 배열로 표현됩니다. 이 배열은 전통적으로 단일 페이지 내에 들어가도록 제한되어있어 분산 / 수집 작업에 최대 길이를 부여합니다. 이 한계는 하이 엔드 시스템에서 병목 현상이되는 것으로 나타났습니다. 그렇지 않으면 대용량 버퍼 (일반적으로 디스크 장치간에)를 전송할 때 이점이 있습니다. 결과적으로 그 한계를 극복 할 수있는 방안이 모색되고 있습니다. 간혹 mailing list에 나타나는 대용량 블록 크기 패치가 한 가지 방법입니다. 그러나 커널을 2.6.24 커널로 만들었던 해법은 Scatter / gatter list의 길이를 묶어서(chained) 제거하는 것입니다.
Within the kernel, a buffer to be used in a scatter/gather DMA operation is represented by an array of one or more scatterlist structures, defined in <linux/scatterlist.h>. This array has traditionally been constrained to fit within a single page, which imposes a maximum length on scatter/gather operations. That limit has proved to be a bottleneck on high-end systems, which could otherwise benefit from transferring very large buffers (usually to and from disk devices). As a result, there has been a search for ways to get around that limit; the large block size patches which occasionally surface on the mailing lists are one approach. But the solution which has made it into the 2.6.24 kernel is to remove the limit on the length of scatter/gather lists by allowing them to be chained.
Chained Scatter / gatter list는 둘 이상의 페이지로 구성 될 수 있으며 해당 페이지도 실제 메모리에 흩어질 수 있습니다. 이 체인(chaining)이 완료되면 버퍼 포인터의 하위 비트 두 개를 사용하여 체인 항목과 목록의 끝을 표시합니다. 이 사용법은 드라이버 코드에서 걱정할 필요가 있는 것이 아니지만 특수 bit와 체인 pointer가 있으면 드라이버가 scatter list에서 작동하는 방식을 일부 변경해야합니다.
A chained scatter/gather list can be made up of more than one page, and those pages, too, are likely to be scattered throughout physical memory. When this chaining is done, a couple of low-order bits in the buffer pointer are used to mark chain entries and the end of the list. This usage is not something which driver code needs to worry about, but the existence of special bits and chain pointers forces some changes to how drivers work with scatterlists.
연결(chaining)을 수행하지 않는 드라이버는 일반적으로 kcalloc () 또는 일부 호출을 통해 scatterlist array를 일반적인 방식으로 할당합니다. 2.6.23 이전에는 초기화 단계가 필요하지 않았으며, 아마도 전체 배열을 초기화하지 않았습니다. 그러나 그것은 바뀌었습니다. 드라이버는 다음과 같이 scatterlist array를 초기화해야합니다:
Drivers which do not perform chaining will allocate their scatterlist arrays in the usual way - usually through a call to kcalloc() or some such. Prior to 2.6.23, there was no initialization step required, beyond, perhaps, zeroing the entire array. That has changed, however; drivers should now initialize a scatterlist array with:
void sg_init_table(struct scatterlist *sg, unsigned int nents);
여기서 sg 는 할당 된 배열을 가리키고, nents 는 할당 된 scatter/gather entry들의 갯수입니다.
Here, sg points to the allocated array, and nents is the number of allocated scatter/gather entries.
이전과 마찬가지로, 드라이버는 버퍼의 세그먼트를 반복하여 각각에 대해 하나의 scatterlist 항목을 설정해야합니다. 그러나 더 이상 페이지 포인터를 직접 설정할 수는 없습니다: 그 포인터는 2.6.24에 존재하지 않습니다. 대신, scatterlist 항목을 설정하는 일반적인 방법은 다음 중 하나입니다:
As before, a driver should loop through the segments of the buffer, setting one scatterlist entry for each. It is no longer possible to set the page pointer directly, however: that pointer does not exist in 2.6.24. Instead, the usual way to set a scatterlist entry will be with one of:
void sg_set_page(struct scatterlist *sg, struct page *page, unsigned int len, unsigned int offset); void sg_set_buf(struct scatterlist *sg, const void *buf, unsigned int buflen);
2.6.24 scatterlist는 또한 목록의 끝 부분에 명시적으로 표시해야합니다. 이 표시는 sg_init_table () 이 호출 될 때 수행되므로, 드라이버는 일반적으로 명시적으로 끝을 표시하지 않아도됩니다. I/O 작업이 목록에 할당된 모든 항목을 사용하지 않으면, 드라이버는 최종 세그먼트를 다음처럼 표시해야합니다:
2.6.24 scatterlists also require that the end of the list be explicitly marked. This marking is performed when sg_init_table() is called, so drivers will not normally have to mark the end explicitly. Should the I/O operation not use all of the entries which were allocated in the list, though, the driver should mark the final segment with:
void sg_mark_end(struct scatterlist *sg, unsigned int nents);
여기서 nents 는 scatterlist의 유효한 항목 수입니다.
Where nents is the number of valid entries in the scatterlist.
scatterlist가 매핑 된 후 ( dma_map_sg () 와 같은 함수를 사용하여), 드라이버는 결과 DMA 주소를 하드웨어에 프로그래밍해야합니다. 배열을 단계적으로 실행하는 이전 접근 방식은 더 이상 작동하지 않습니다. 대신 드라이버는 다음을 사용하여 scatterlist의 다음 항목으로 이동해야합니다:
After the scatterlist has been mapped (with a function like dma_map_sg()), the driver will need to program the resulting DMA addresses into the hardware. The old approach of just stepping through the array will no longer work; instead, a driver should move on to the next entry in a scatterlist with:
반환 값은 처리 할 다음 항목이되거나 목록의 끝에 도달하면 NULL이 됩니다. 또한 전체 scatterlist를 반복하는 데 사용할 수있는 for_each_sg () 매크로가 있습니다. 일반적으로 다음과 같은 코드에서 사용됩니다:
The return value will be the next entry to process - or NULL if the end of the list has been reached. There is also a for_each_sg() macro which can be used to iterate through an entire scatterlist; it will typically be used in code which looks like:
int i; struct scatterlist *list, *sgentry; /* Fill in list and pass it to dma_map_sg(). Then... */ for_each_sg(i, list, sgentry, nentries) { program_hw(device, sg_dma_address(sgentry), sg_dma_len(sgentry)); }
chaining feature를 이용하려는 드라이버는 조금만 더 작업해야합니다. scatterlist의 각 조각은 독립적으로 할당되어야하며 그 조각은 다음과 함께 체인(chained)되어야합니다.
Drivers which wish to take advantage of the chaining feature must do just a little more work. Each piece of the scatterlist must be allocated independently, then those pieces must be chained together with:
void sg_chain(struct scatterlist *prv, unsigned int prv_nents, struct scatterlist *next);
이 호출은 scatterlist 엔트리 prv [nents] 를 next 에 대한 체인 링크로 변환합니다. 목록이 채워지는 동안 연결이 완료되면, prv 에는 prv_nents-1 세그먼트가 저장되어 있어야합니다. 또는 드라이버는 미리 목록의 조각을 함께 묶을 수 있습니다 (각 체인 링크마다 하나의 항목을 할당하는 것을 기억하십시오). 그런 다음 sg_next () 를 사용하여 체인 링크가 어디에 있는지 걱정할 필요없이 목록을 채웁니다.
This call turns the scatterlist entry prv[nents] into a chain link to next. If the chaining is done while the list is being filled, prv should have no more than prv_nents-1 segments stored into it. Alternatively, a driver can chain together the pieces of the list ahead of time (remembering to allocate one entry for each chain link), then use sg_next() to fill the list without the need to worry about where the chain links are.
이 글을 쓰는 시점에서,이 API는 in-tree 드라이버와 관련된 문제에 대응하여 계속 진화하고 있습니다. 2.6.24 릴리즈 이전에는 더 이상의 실질적인 변경이 이루어질 것 같지 않지만, 변경이 될 수도 있습니다.
As of this writing, this API is still evolving in response to issues which have come up with in-tree drivers. It seems unlikely that any more substantial changes will be made before the 2.6.24 release, but surprises are always possible.