Does get_user_pages_fast lock the user pages in memory in my case?
I'm writting a driver for a video card with the V4L2 interface . V4L2 interface supports the USER-POINTER method for the video frame
handling.
VLC player supports the USER-POINTER method,while MPALYER does not.
In the USER-POINTER method, VLC will call the posix_memalign() to
allocate
203 pages in certain PAL mode (that is 720*576*2) for a single frame.
In my driver , I call the get_user_pages_fast() to obtain the pages
array,and then call
the vmap() to map the pages to VMALLOC space for the memcpy().The code shows below:
....................
get_user_pages_fast();
...
f->data = vmap();
.......................
In comments, it said :
"
+/**
+ * get_user_pages_fast() - pin user pages in memory
+ * @start: starting user address + * @nr_pages: number of pages from start to pin + * @write: whether pages will be written to + * @pages: array that receives pointers to the pages pinned. + * Should be at least nr_pages long.
"
But after I digged the code of kswap and the get_user_pages(called byget_user_pages_fast),
I did not find how the pages pinned in memory.I really need the pages pinned in memory.
Assume page A is one of the pages obtained by get_user_pages_fast()
during page-fault.
[1] page A will on the LRU_ACTIVE_ANON list;
the _count of page A increment by one;
PTE for page A will be set ACCESSED.
[2] kswapd will scan the lru list,and move page A from LRU_ACTIVE_ANON to LRU_INACTIVE_ANON. In the shrink_page_list(), there is nothing can stop page A been
swapped out.
I don't think the page_reference() can move page A back to
LRU_ACTIVE_ANON.In my driver,
I am not sure if the VLC can access the page A.
Is this a bug? or I miss something?
Thanks .
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Hi
Quoted 30 lines
"
+/**
+ *get_user_pages_fast()- pin user pages in memory+ * @start: starting user address + * @nr_pages: number of pages from start to pin + * @write: whether pages will be written to + * @pages: array that receives pointers to the pages pinned. + * Should be at least nr_pages long."
But after I digged the code of kswap and the get_user_pages(called by
get_user_pages_fast),
I did not find how the pages pinned in memory.I really need the pages pinned in memory.Assume page A is one of the pages obtained by
get_user_pages_fast()
during page-fault.[1] page A will on the
LRU_ACTIVE_ANONlist;
the_countof page A increment by one;
PTE for page A will be set ACCESSED.[2] kswapd will scan the lru list,and move page A from
LRU_ACTIVE_ANONtoLRU_INACTIVE_ANON.
In theshrink_page_list(), there is nothing can stop page A been
swapped out.
I don't think thepage_reference()can move page A back to
LRU_ACTIVE_ANON.In my driver,
I am not sure if the VLC can access the page A.
Quoted 2 lines
Is this a bug? or I miss something?
Thanks .
BUG.
We are talking about it just now.
see the following thread in lkml
"[RFC][PATCH 0/6] IO pinning(get_user_pages()) vs fork race fix"
but unfortunately, we don't have no painful fix. perhaps you need change your code...
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
KOSAKI Motohiro a??e??:
Quoted 35 lines
Hi
Quoted 28 lines
"
+/**
+ *get_user_pages_fast()- pin user pages in memory+ * @start: starting user address + * @nr_pages: number of pages from start to pin + * @write: whether pages will be written to + * @pages: array that receives pointers to the pages pinned. + * Should be at least nr_pages long."
But after I digged the code of kswap and the get_user_pages(called by
get_user_pages_fast),
I did not find how the pages pinned in memory.I really need the pages pinned in memory.Assume page A is one of the pages obtained by
get_user_pages_fast()
during page-fault.[1] page A will on the
LRU_ACTIVE_ANONlist;
the_countof page A increment by one;
PTE for page A will be set ACCESSED.[2] kswapd will scan the lru list,and move page A from
LRU_ACTIVE_ANONtoLRU_INACTIVE_ANON. In theshrink_page_list(), there is nothing can stop page A been
swapped out.
I don't think thepage_reference()can move page A back to
LRU_ACTIVE_ANON.In my driver,
I am not sure if the VLC can access the page A.Quoted 3 lines
Is this a bug? or I miss something?
Thanks .
Quoted 8 lines
BUG.
We are talking about it just now.
see the following thread in lkml
"[RFC][PATCH 0/6] IO pinning(get_user_pages()) vs fork race fix"
thanks, I read the thread as well as your patch.
What about to put the gup() page back in the isolate_pages_global()?
Quoted 4 lines
but unfortunately, we don't have no painful fix. perhaps you need change your code...
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 14 lines · On Fri, 17 Apr 2009 15:01:01 +0800 Huang Shijie <[email protected]> wrote:
I'm writting a driver for a video card with the V4L2 interface . V4L2 interface supports the USER-POINTER method for the video frame
handling.VLC player supports the USER-POINTER method,while MPALYER does not.
In the USER-POINTER method, VLC will call the
posix_memalign()to
allocate
203 pages in certain PAL mode (that is 720*576*2) for a single frame.
In my driver , I call theget_user_pages_fast()to obtain the pages
array,and then call
thevmap()to map the pages to VMALLOC space for the memcpy().The code shows below:
Quoted 5 lines
.................... get_user_pages_fast(); ... f->data = vmap(); .......................
What I understand is that you get the pages of posix_memalign by get_user_pages_fast and then that pages are mapped at kernel vmalloc space by vmap.
Is it for removing copy overhead from kernel to user ?
Quoted 31 lines
In comments, it said :
"
+/**
+ *get_user_pages_fast()- pin user pages in memory+ * @start: starting user address + * @nr_pages: number of pages from start to pin + * @write: whether pages will be written to + * @pages: array that receives pointers to the pages pinned. + * Should be at least nr_pages long."
But after I digged the code of kswap and the get_user_pages(called by
get_user_pages_fast),
I did not find how the pages pinned in memory.I really need the pages pinned in memory.Assume page A is one of the pages obtained by
get_user_pages_fast()
during page-fault.[1] page A will on the
LRU_ACTIVE_ANONlist;
the_countof page A increment by one;
PTE for page A will be set ACCESSED.[2] kswapd will scan the lru list,and move page A from
LRU_ACTIVE_ANONtoLRU_INACTIVE_ANON.
In theshrink_page_list(), there is nothing can stop page A been
swapped out.
I don't think thepage_reference()can move page A back to
LRU_ACTIVE_ANON.In my driver,
I am not sure if the VLC can access the page A.
Quoted 2 lines
Is this a bug? or I miss something?
Thanks .
If above my assumption is right, It's not a BUG.
You get the application's pages by get_user_pages_fast. 'Page pinning' means it shouldn't be freed.
Application's pages always can be swapped out.
If you don't want to swap out the page, you should use mlock. If you use mlock, kernel won't insert the page to lru [in]active list. So the page never can be swapped out.
Quoted 16 lines
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Signature · 8 lines
Kinds Regards Minchan Kim -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Minchan Kim a??e??:
Quoted 24 lines
Quoted 12 lines · On Fri, 17 Apr 2009 15:01:01 +0800 Huang Shijie <[email protected]> wrote:
I'm writting a driver for a video card with the V4L2 interface . V4L2 interface supports the USER-POINTER method for the video frame
handling.VLC player supports the USER-POINTER method,while MPALYER does not.
In the USER-POINTER method, VLC will call the
posix_memalign()to
allocate
203 pages in certain PAL mode (that is 720*576*2) for a single frame.
In my driver , I call theget_user_pages_fast()to obtain the pages
array,and then call
thevmap()to map the pages to VMALLOC space for the memcpy().The code shows below:Quoted 6 lines
.................... get_user_pages_fast(); ... f->data = vmap(); .......................
Quoted 5 lines
What I understand is that you get the pages of
posix_memalignbyget_user_pages_fastand then that pages are mapped at kernel vmalloc space by vmap.Is it for removing copy overhead from kernel to user ?
I need a large range of virtual contigous memory to store my video frame(about 203 pages). When I received a full frame ,I will queue the buffer in a VIDIOC queue,which will be remove by the VIDIOC_DQBUF.
Quoted 29 lines
Quoted 29 lines
In comments, it said :
"
+/**
+ *get_user_pages_fast()- pin user pages in memory+ * @start: starting user address + * @nr_pages: number of pages from start to pin + * @write: whether pages will be written to + * @pages: array that receives pointers to the pages pinned. + * Should be at least nr_pages long."
But after I digged the code of kswap and the get_user_pages(called by
get_user_pages_fast),
I did not find how the pages pinned in memory.I really need the pages pinned in memory.Assume page A is one of the pages obtained by
get_user_pages_fast()
during page-fault.[1] page A will on the
LRU_ACTIVE_ANONlist;
the_countof page A increment by one;
PTE for page A will be set ACCESSED.[2] kswapd will scan the lru list,and move page A from
LRU_ACTIVE_ANONtoLRU_INACTIVE_ANON. In theshrink_page_list(), there is nothing can stop page A been
swapped out.
I don't think thepage_reference()can move page A back to
LRU_ACTIVE_ANON.In my driver,
I am not sure if the VLC can access the page A.
Quoted 8 lines
Quoted 3 lines
Is this a bug? or I miss something?
Thanks .If above my assumption is right, It's not a BUG. You get the application's pages by
get_user_pages_fast. 'Page pinning' means it shouldn't be freed. Application's pages always can be swapped out. If you don't want to swap out the page, you should use mlock. If you use mlock, kernel won't insert the page to lru [in]active list. So the page never can be swapped out.
Yes, it not a bug .
I read the kernel code again. In my case ,the kernel will pin the pages in memory. I missed function is_page_cache_freeable() in the pageout().
In my case, is_page_cache_freeable()will return false ,for page_count(page) is 3 now:
<1> one is from alloc_page_* in page fault.
<2> one is from get_usr_pages()
<3> one is from add_to_swap() in shrink_page_list()
So ,there is no need to use the mlock, it will mess my driver. is_page_cache_freeable()will return PAGE_KEEP, and page is locked in swap cache.
Unfortunately, the page is unmaped, and the PTE of the page has been replaced by a swp_entry_t . When the process read the page ,it will raise a page fault again, the kernel will find the page in the swap cache, and requeue the page in LRU_ACTIVE_ANON, ---I think it is a vicious circle for the kernel.
I think there two places to put back the gup() pages. <1> isolate_page_glable()
<2> in the shrink_page_list(), before called the try_to_unmap(). KOSAKI Motohiro 's patch takes effect in the second place. I think the first place is better.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 29 lines · On Mon, 20 Apr 2009 10:15:55 +0800 Huang Shijie <[email protected]> wrote:
Minchan Kim a??e??:
Quoted 28 lines
Quoted 13 lines · On Fri, 17 Apr 2009 15:01:01 +0800 Huang Shijie <[email protected]> wrote:
I'm writting a driver for a video card with the V4L2 interface . V4L2 interface supports the USER-POINTER method for the video frame
handling.VLC player supports the USER-POINTER method,while MPALYER does not.
In the USER-POINTER method, VLC will call the
posix_memalign()to
allocate
203 pages in certain PAL mode (that is 720*576*2) for a single frame.
In my driver , I call theget_user_pages_fast()to obtain the pages
array,and then call
thevmap()to map the pages to VMALLOC space for the memcpy().The code shows below:Quoted 6 lines
.................... get_user_pages_fast(); ... f->data = vmap(); .......................What I understand is that you get the pages of
posix_memalignbyget_user_pages_fastand then that pages are mapped at kernel vmalloc space by vmap.
Quoted 7 lines
Quoted 3 lines
Is it for removing copy overhead from kernel to user ?
I need a large range of virtual contigous memory to store my video frame(about 203 pages). When I received a full frame ,I will queue the buffer in
a VIDIOC queue,which will be remove by theVIDIOC_DQBUF.'
I can't understand your point.
Sorry for that.
Could you explain more detail relation (user buffer which is allocated by posix_memalign) and (kernel buffer which is mapped by vmap) ?
Quoted 50 lines
Quoted 35 lines
Quoted 31 lines
In comments, it said :
"
+/**
+ *get_user_pages_fast()- pin user pages in memory+ * @start: starting user address + * @nr_pages: number of pages from start to pin + * @write: whether pages will be written to + * @pages: array that receives pointers to the pages pinned. + * Should be at least nr_pages long."
But after I digged the code of kswap and the get_user_pages(called by
get_user_pages_fast),
I did not find how the pages pinned in memory.I really need the pages pinned in memory.Assume page A is one of the pages obtained by
get_user_pages_fast()
during page-fault.[1] page A will on the
LRU_ACTIVE_ANONlist;
the_countof page A increment by one;
PTE for page A will be set ACCESSED.[2] kswapd will scan the lru list,and move page A from
LRU_ACTIVE_ANONtoLRU_INACTIVE_ANON.
In theshrink_page_list(), there is nothing can stop page A been
swapped out.
I don't think thepage_reference()can move page A back to
LRU_ACTIVE_ANON.In my driver,
I am not sure if the VLC can access the page A.Quoted 3 lines
Is this a bug? or I miss something?
Thanks .Quoted 9 lines
If above my assumption is right, It's not a BUG.
You get the application's pages byget_user_pages_fast. 'Page pinning' means it shouldn't be freed.
Application's pages always can be swapped out.
If you don't want to swap out the page, you should use mlock. If you use mlock, kernel won't insert the page to lru [in]active list. So the page never can be swapped out.Yes, it not a bug .
I read the kernel code again. In my case ,the kernel will pin the pages in memory.
I missed functionis_page_cache_freeable()in thepageout().
Quoted 5 lines
In my case, is_page_cache_freeable()will return false ,for
page_count(page)is 3 now:
<1> one is from alloc_page_* in page fault.
<2> one is fromget_usr_pages()
<3> one is fromadd_to_swap()inshrink_page_list()
One more, try_to_unmap will call page_cache_release. So, count is 2.
Quoted 4 lines
So ,there is no need to use the mlock, it will mess my driver. is_page_cache_freeable()will return
PAGE_KEEP, and page is locked in swap cache.
I can't understand your point exactly yet.
But what I mean is following as in user mode
posix_memalignq(&buffer);mlock(buffer, buffer_len);
I will not dirty your driver.
Do I miss something ?
Quoted 20 lines
Unfortunately, the page is unmaped, and the PTE of the page has been replaced by a
swp_entry_t.
When the process read the page ,it will raise a page fault again, the kernel will find the page in the
swap cache, and requeue the page inLRU_ACTIVE_ANON, ---I think it is a vicious circle for the kernel.I think there two places to put back the
gup()pages. <1>isolate_page_glable()
<2> in theshrink_page_list(), before called thetry_to_unmap(). KOSAKI Motohiro 's patch takes effect in the second place. I think the first place is better.
Signature · 8 lines
Kinds Regards Minchan Kim -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Minchan Kim a??e??:
Quoted 36 lines
Quoted 32 lines · On Mon, 20 Apr 2009 10:15:55 +0800 Huang Shijie <[email protected]> wrote:
Minchan Kim a??e??:
Quoted 30 lines
Quoted 12 lines · On Fri, 17 Apr 2009 15:01:01 +0800 Huang Shijie <[email protected]> wrote:
I'm writting a driver for a video card with the V4L2 interface . V4L2 interface supports the USER-POINTER method for the video frame
handling.VLC player supports the USER-POINTER method,while MPALYER does not.
In the USER-POINTER method, VLC will call the
posix_memalign()to
allocate
203 pages in certain PAL mode (that is 720*576*2) for a single frame.
In my driver , I call theget_user_pages_fast()to obtain the pages
array,and then call
thevmap()to map the pages to VMALLOC space for the memcpy().The code shows below:Quoted 7 lines
.................... get_user_pages_fast(); ... f->data = vmap(); .......................What I understand is that you get the pages of
posix_memalignbyget_user_pages_fastand then that pages are mapped at kernel vmalloc space by vmap.Is it for removing copy overhead from kernel to user ?
Quoted 9 lines
Quoted 3 lines
I need a large range of virtual contigous memory to store my video frame(about 203 pages). When I received a full frame ,I will queue the buffer in a VIDIOC queue,which will be remove by the
VIDIOC_DQBUF.'I can't understand your point. Sorry for that.
Could you explain more detail relation (user buffer which is allocated by
posix_memalign) and (kernel buffer which is mapped by vmap) ?
:) sorry for my poor english.
[1] VLC uses the posix_memalign to allocate a big buffer for a single frame(203 pages).
[2] vmap sets up the mapping of virtual contigous address for gup()'s pages array(the pages are not consecutive). memcpy() needs a contiguous address to copy in kernel mode.
[3] my driver do some specail operations to received data, then memcopy the data to the buffer get in step [2].
[4] when the buffer is full ,I will give the the user process (VLC).
That's all.
Quoted 43 lines
Quoted 43 lines
Quoted 29 lines
Quoted 29 lines
In comments, it said :
"
+/**
+ *get_user_pages_fast()- pin user pages in memory+ * @start: starting user address + * @nr_pages: number of pages from start to pin + * @write: whether pages will be written to + * @pages: array that receives pointers to the pages pinned. + * Should be at least nr_pages long."
But after I digged the code of kswap and the get_user_pages(called by
get_user_pages_fast),
I did not find how the pages pinned in memory.I really need the pages pinned in memory.Assume page A is one of the pages obtained by
get_user_pages_fast()
during page-fault.[1] page A will on the
LRU_ACTIVE_ANONlist;
the_countof page A increment by one;
PTE for page A will be set ACCESSED.[2] kswapd will scan the lru list,and move page A from
LRU_ACTIVE_ANONtoLRU_INACTIVE_ANON. In theshrink_page_list(), there is nothing can stop page A been
swapped out.
I don't think thepage_reference()can move page A back to
LRU_ACTIVE_ANON.In my driver,
I am not sure if the VLC can access the page A.Quoted 9 lines
Quoted 4 lines
Is this a bug? or I miss something?
Thanks .If above my assumption is right, It's not a BUG. You get the application's pages by
get_user_pages_fast. 'Page pinning' means it shouldn't be freed. Application's pages always can be swapped out. If you don't want to swap out the page, you should use mlock. If you use mlock, kernel won't insert the page to lru [in]active list. So the page never can be swapped out.Yes, it not a bug .
I read the kernel code again. In my case ,the kernel will pin the pages in memory. I missed function
is_page_cache_freeable()in thepageout().
Quoted 9 lines
Quoted 5 lines
In my case, is_page_cache_freeable()will return false ,for
page_count(page)is 3 now:
<1> one is from alloc_page_* in page fault.
<2> one is fromget_usr_pages()
<3> one is fromadd_to_swap()inshrink_page_list()One more,
try_to_unmapwill callpage_cache_release. So, count is 2.
Yes, you are right. I missed the page_cache_release() in try_to_unmap(). :(
Quoted 5 lines
Quoted 4 lines
So ,there is no need to use the mlock, it will mess my driver. is_page_cache_freeable()will return
PAGE_KEEP, and page is locked in swap cache.
Quoted 7 lines
I can't understand your point exactly yet.
But what I mean is following as in user mode
posix_memalignq(&buffer);mlock(buffer, buffer_len);
I also wish the VLC use the mlock,but it does not.If it uses mlock(), the pages will be put in LRU_UNEVICETABL LIST.
Maybe the programmer of VLC thinks: Why i add mlock, for the kernel has the gup() which could pin the pages in memory?
Quoted 3 lines
I will not dirty your driver. Do I miss something ?
I did add the Mlock bit to the VMA->vm_flags in my driver before,but I think that's ugly.
Quoted 4 lines
Quoted 4 lines
Unfortunately, the page is unmaped, and the PTE of the page has been replaced by a
swp_entry_t. When the process read the page ,it will raise a page fault again, the kernel will find the page in the swap cache, and requeue the page inLRU_ACTIVE_ANON, ---I think it is a vicious circle for the kernel.
Quoted 17 lines
Quoted 14 lines
I think there two places to put back the
gup()pages. <1>isolate_page_glable()
<2> in theshrink_page_list(), before called thetry_to_unmap(). KOSAKI Motohiro 's patch takes effect in the second place. I think the first place is better.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 7 lines
Quoted 7 lines
I can't understand your point exactly yet.
But what I mean is following as in user mode
posix_memalignq(&buffer);mlock(buffer, buffer_len);
Quoted 5 lines
I also wish the VLC use the mlock,but it does not.If it uses
mlock(), the pages will be put inLRU_UNEVICETABLLIST.Maybe the programmer of VLC thinks: Why i add mlock, for the kernel has the
gup()which could pin the pages in memory?
more weakness.
mlock() only gurantee the address range is memory-resident, not gurantee to no change virtual-physical mappings.
There are different operation.
example, gup() prevent page migration but mlock doesn't.
Quoted 5 lines
Quoted 3 lines
I will not dirty your driver.
Do I miss something ?I did add the Mlock bit to the
VMA->vm_flagsin my driver before,but I think that's ugly.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
On Mon, 20 Apr 2009 11:28:00 +0800
Huang Shijie <[email protected]> wrote:
I will summarize your method.
Is right ?
kernel(driver) application
posix_memalign(buffer) ioctl(buffer)
ioctl handlerget_user_pages(pages);
/* This pages are mapped at user's vma'
address space */
vaddr = vmap(pages);
/* This pages are mapped at vmalloc space */
.
.
<after sometime,
It may change to other process context>
.
.
interrupt handler in your drivermemcpy(vaddr, src, len);notify_user();
processing(buffer);
It's rather awkward use case of get_user_pages.
If you want to share one big buffer between kernel and user, You can vmalloc and remap_pfn_range.
You can refer cpia_mmap in drivers/media/video/cpia.c
Quoted 62 lines
Minchan Kim a??e??:
Quoted 44 lines
Quoted 30 lines · On Mon, 20 Apr 2009 10:15:55 +0800 Huang Shijie <[email protected]> wrote:
Minchan Kim a??e??:
Quoted 28 lines
Quoted 13 lines · On Fri, 17 Apr 2009 15:01:01 +0800 Huang Shijie <[email protected]> wrote:
I'm writting a driver for a video card with the V4L2 interface . V4L2 interface supports the USER-POINTER method for the video frame
handling.VLC player supports the USER-POINTER method,while MPALYER does not.
In the USER-POINTER method, VLC will call the
posix_memalign()to
allocate
203 pages in certain PAL mode (that is 720*576*2) for a single frame.
In my driver , I call theget_user_pages_fast()to obtain the pages
array,and then call
thevmap()to map the pages to VMALLOC space for the memcpy().The code shows below:Quoted 7 lines
.................... get_user_pages_fast(); ... f->data = vmap(); .......................What I understand is that you get the pages of
posix_memalignbyget_user_pages_fastand then that pages are mapped at kernel vmalloc space by vmap.Quoted 9 lines
Quoted 4 lines
Is it for removing copy overhead from kernel to user ?
I need a large range of virtual contigous memory to store my video frame(about 203 pages). When I received a full frame ,I will queue the buffer in
a VIDIOC queue,which will be remove by theVIDIOC_DQBUF.'Quoted 6 lines
I can't understand your point.
Sorry for that.Could you explain more detail relation (user buffer which is allocated by
posix_memalign) and (kernel buffer which is mapped by vmap) ?:) sorry for my poor english.
[1] VLC uses theposix_memalignto allocate a big buffer for a single frame(203 pages).
[2] vmap sets up the mapping of virtual contigous address for gup()'s pages array(the pages are not consecutive).
memcpy()needs a contiguous address to copy in kernel mode.
[3] my driver do some specail operations to received data, then memcopy the data to the buffer get in step [2].
[4] when the buffer is full ,I will give the the user process (VLC).That's all.
Signature · 8 lines
Kinds Regards Minchan Kim -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 5 lines
It's rather awkward use case of
get_user_pages.If you want to share one big buffer between kernel and user, You can vmalloc and
remap_pfn_range.
You can refercpia_mmapindrivers/media/video/cpia.c
Hm, good opinion.
gup()ed page stay in lru, but remap_pfn_range() page doesn't. it cause
gup() pinning:
merit
- Can processing any user process patch (DirectIO need it)
demerit
- introduce reclaim slowdown
remap
merit
- Don't cause any slowdown
demerit
- can be used on some special situation only.
Then, driver can use special memory and need long time pinning. remap_pfn_range() is better.
but, DirectIO liked general pinning need gup() pinning.
I think.
but I'm not remap_pfn_range() specialist. perhaps I can talk about incorrect thing ...
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Minchan Kim a??e??:
Quoted 23 lines
On Mon, 20 Apr 2009 11:28:00 +0800
Huang Shijie <[email protected]> wrote:I will summarize your method. Is right ?
kernel(driver) application
posix_memalign(buffer)ioctl(buffer)ioctl handler
get_user_pages(pages);
/* This pages are mapped at user's vma' address space */ vaddr = vmap(pages);
/* This pages are mapped at vmalloc space */
.
.
<after sometime, It may change to other process context>
.
.
interrupt handler in your drivermemcpy(vaddr, src, len);notify_user();
Quoted 6 lines
processing(buffer);It's rather awkward use case of
get_user_pages.If you want to share one big buffer between kernel and user, You can vmalloc and
remap_pfn_range.
The v4l2 method IO_METHOD_MMAP does use the vmaloc() method you told above , our driver also support this method,we user vmalloc /remap_vmalloc_range().
But the v4l2 method IO_METHOD_USERPTR must use the method I told above.
Quoted 70 lines
You can refer
cpia_mmapindrivers/media/video/cpia.cQuoted 62 lines
Minchan Kim a??e??:
Quoted 41 lines
Quoted 36 lines · On Mon, 20 Apr 2009 10:15:55 +0800 Huang Shijie <[email protected]> wrote:
Minchan Kim a??e??:
Quoted 33 lines
Quoted 12 lines · On Fri, 17 Apr 2009 15:01:01 +0800 Huang Shijie <[email protected]> wrote:
I'm writting a driver for a video card with the V4L2 interface . V4L2 interface supports the USER-POINTER method for the video frame
handling.VLC player supports the USER-POINTER method,while MPALYER does not.
In the USER-POINTER method, VLC will call the
posix_memalign()to
allocate
203 pages in certain PAL mode (that is 720*576*2) for a single frame.
In my driver , I call theget_user_pages_fast()to obtain the pages
array,and then call
thevmap()to map the pages to VMALLOC space for the memcpy().The code shows below:Quoted 8 lines
.................... get_user_pages_fast(); ... f->data = vmap(); .......................What I understand is that you get the pages of
posix_memalignbyget_user_pages_fastand then that pages are mapped at kernel vmalloc space by vmap.Is it for removing copy overhead from kernel to user ?
Quoted 10 lines
Quoted 4 lines
I need a large range of virtual contigous memory to store my video frame(about 203 pages). When I received a full frame ,I will queue the buffer in a VIDIOC queue,which will be remove by the
VIDIOC_DQBUF.'I can't understand your point. Sorry for that.
Could you explain more detail relation (user buffer which is allocated by
posix_memalign) and (kernel buffer which is mapped by vmap) ?:) sorry for my poor english.
[1] VLC uses theposix_memalignto allocate a big buffer for a single frame(203 pages).
[2] vmap sets up the mapping of virtual contigous address for gup()'s pages array(the pages are not consecutive).memcpy()needs a contiguous address to copy in kernel mode.
[3] my driver do some specail operations to received data, then memcopy the data to the buffer get in step [2].
[4] when the buffer is full ,I will give the the user process (VLC).That's all.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 4 lines
The v4l2 method
IO_METHOD_MMAPdoes use thevmaloc()method you told above , our driver also support this method,we user vmalloc /remap_vmalloc_range().But the v4l2 method
IO_METHOD_USERPTRmust use the method I told above.
I guess you mean IO_METHOD_USERPTR can't use remap_vmalloc_range, right? we need explanation of v4l2 requirement.
Can you explain why v4l2 use two different way? Why application developer need two way?
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
KOSAKI Motohiro a??e??:
Quoted 8 lines
Quoted 5 lines
The v4l2 method
IO_METHOD_MMAPdoes use thevmaloc()method you told above , our driver also support this method,we user vmalloc /remap_vmalloc_range().But the v4l2 method
IO_METHOD_USERPTRmust use the method I told above.I guess you mean
IO_METHOD_USERPTRcan't useremap_vmalloc_range, right?
Yes.
IO_METHOD_USERPTR method uses the anonymous pages allocated by posix_memalign(), while the remap_vmalloc_range() use the pages alloced by vmalloc().
Quoted 7 lines
we need explanation of v4l2 requirement.
Can you explain why v4l2 use two different way? Why application developer need two way?
pleasure :)
http://v4l2spec.bytesex.org/spec/r13696.htm
shows the vidioc_reqbufs(). It determines the method of IO : "Memory Mapping or User Pointer I/O"
The application developers can support any methodes of the Two, there is no mandatory request to realize
both methods. For example, the Mplayer only support the "memory maping" method ,and it does't support the "user pointer",
while the VLC supports both.
The full spec is below.
http://v4l2spec.bytesex.org/spec/
Quoted 3 lines
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 9 lines
http://v4l2spec.bytesex.org/spec/r13696.htm
shows thevidioc_reqbufs(). It determines the method of IO : "Memory Mapping or User Pointer I/O"The application developers can support any methodes of the Two, there is no mandatory request to realize
both methods. For example, the Mplayer only support the "memory
maping" method ,and it does't support the "user pointer", while the VLC supports both.
I greped VIDIOC_REQBUFS on current tree.
Almost driver has following check.
if (rb->memory != V4L2_MEMORY_MMAP) return -EINVAL;
IOW, almost one don't provide V4L2_MEMORY_USERPTR method. Thus, I think any userland application don't want use V4L2_MEMORY_USERPTR. I recommend you also return -EINVAL.
I think we can't implement V4L2_MEMORY_USERPTR properly. it is mistake by specification.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
KOSAKI Motohiro a??e??:
Quoted 11 lines
Quoted 7 lines
http://v4l2spec.bytesex.org/spec/r13696.htm
shows thevidioc_reqbufs(). It determines the method of IO : "Memory Mapping or User Pointer I/O"The application developers can support any methodes of the Two, there is no mandatory request to realize
both methods. For example, the Mplayer only support the "memory maping" method ,and it does't support the "user pointer",
while the VLC supports both.I greped
VIDIOC_REQBUFSon current tree.
Almost driver has following check.
Quoted 8 lines
if (rb->memory != V4L2_MEMORY_MMAP) return -EINVAL;IOW, almost one don't provide
V4L2_MEMORY_USERPTRmethod. Thus, I think any userland application don't want useV4L2_MEMORY_USERPTR. I recommend you also return-EINVAL.
Thanks.
In the V4L2_MEMORY_USERPTR method, what I want to do is pin the anonymous pages in memory.
I used to add the VM_LOCKED to vma associated with the pages.In my opinion, the pages will:LRU_ACTIVE_ANON ---> LRU_INACTIVE_ANON---> LRU_UNEVICTABLE
so the pages are pinned in memory.It was ugly, but it works I think. Do you have any suggestions about this method?
Quoted 8 lines
I think we can't implement
V4L2_MEMORY_USERPTRproperly. it is mistake by specification.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 9 lines
In the
V4L2_MEMORY_USERPTRmethod, what I want to do is pin the anonymous pages in memory.I used to add the
VM_LOCKEDto vma associated with the pages.In my opinion, the pages will:LRU_ACTIVE_ANON---> LRU_INACTIVE_ANON--->LRU_UNEVICTABLEso the pages are pinned in memory.It was ugly, but it works I think. Do you have any suggestions about this method?
page migration (e.g. move_pages) ignore MLOCK. maybe, VM_LOCKED + gut()ed solved it partially :)
but, user process still can call munlock. it cause disaster. I still think -EINVAL is better.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
KOSAKI Motohiro a??e??:
Quoted 5 lines
Quoted 5 lines
In the
V4L2_MEMORY_USERPTRmethod, what I want to do is pin the anonymous pages in memory.I used to add the
VM_LOCKEDto vma associated with the pages.In my opinion, the pages will:LRU_ACTIVE_ANON---> LRU_INACTIVE_ANON--->LRU_UNEVICTABLE
Quoted 8 lines
Quoted 3 lines
so the pages are pinned in memory.It was ugly, but it works I think. Do you have any suggestions about this method?
page migration (e.g.
move_pages) ignore MLOCK.
maybe,VM_LOCKED+ gut()ed solved it partially :)
My old codes used the get_user_pages()/VM_LOCKED just as you said.
I will read the migration code, I am not clear about why the gup() can stop the migraion.
Quoted 5 lines
but, user process still can call munlock. it cause disaster. I still think
-EINVALis better.
Why the user process call munlock? VLC or Mplayer do not call it, so I don't worry about that.
Our video card is still not on sale.So I can wait until the bug is fixed. :) If there is no method to bypass the problem in future,I will return -EINVAL.
thanks
Quoted 2 lines
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 8 lines
Quoted 3 lines
but, user process still can call munlock. it cause disaster. I still think
-EINVALis better.Why the user process call munlock? VLC or Mplayer do not call it, so I don't worry about that.
Our video card is still not on sale.So I can wait until the bug is fixed. :) If there is no method to bypass the problem in future,I will return
-EINVAL.
We don't assume any userspace behavior in kernel. but you can ignore our recommendation, of cource :)
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 2 lines · Huang Shijie
I will read the migration code, I am not clear about why the
gup()can stop the migraion.
Because it increases the refcount of the page. Page migration is then unable to account for all the references to a page and therefore the page cannot be migrated.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 33 lines · On Mon, 20 Apr 2009 13:05:33 +0800 Huang Shijie <[email protected]> wrote:
Minchan Kim a??e??:
Quoted 32 lines
On Mon, 20 Apr 2009 11:28:00 +0800
Huang Shijie <[email protected]> wrote:I will summarize your method.
Is right ?kernel(driver) application
posix_memalign(buffer)ioctl(buffer)ioctl handler
get_user_pages(pages);
/* This pages are mapped at user's vma'
address space */
vaddr = vmap(pages);
/* This pages are mapped at vmalloc space */
.
.
<after sometime,
It may change to other process context>
.
.
interrupt handler in your drivermemcpy(vaddr, src, len);notify_user();processing(buffer);It's rather awkward use case of
get_user_pages.
Quoted 7 lines
Quoted 3 lines
If you want to share one big buffer between kernel and user, You can vmalloc and
remap_pfn_range.The v4l2 method
IO_METHOD_MMAPdoes use thevmaloc()method you told above , our driver also support this method,we user vmalloc /remap_vmalloc_range().But the v4l2 method
IO_METHOD_USERPTRmust use the method I told above.
I can't understand IO_METHOD_USERPTR's benefit compared with IO_METHOD_MMAP.
I think both solution can support that application programmer can handle buffer as like pointer and kernel can reduce copy overhead from kernel to user.
Why do you have to support IO_METHOD_USERPTR? If you can justify your goal, we can add locked GUP.
Signature · 8 lines
Kinds Regards Minchan Kim -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Minchan Kim a??e??:
Quoted 36 lines
Quoted 32 lines · On Mon, 20 Apr 2009 13:05:33 +0800 Huang Shijie <[email protected]> wrote:
Minchan Kim a??e??:
Quoted 30 lines
On Mon, 20 Apr 2009 11:28:00 +0800
Huang Shijie <[email protected]> wrote:I will summarize your method. Is right ?
kernel(driver) application
posix_memalign(buffer)ioctl(buffer)ioctl handler
get_user_pages(pages);
/* This pages are mapped at user's vma' address space */ vaddr = vmap(pages);
/* This pages are mapped at vmalloc space */
.
.
<after sometime, It may change to other process context>
.
.
interrupt handler in your drivermemcpy(vaddr, src, len);notify_user();processing(buffer);It's rather awkward use case of
get_user_pages.If you want to share one big buffer between kernel and user, You can vmalloc and
remap_pfn_range.
Quoted 9 lines
Quoted 5 lines
The v4l2 method
IO_METHOD_MMAPdoes use thevmaloc()method you told above , our driver also support this method,we user vmalloc /remap_vmalloc_range().But the v4l2 method
IO_METHOD_USERPTRmust use the method I told above.I can't understand IO_METHOD_USERPTR's benefit compared with
IO_METHOD_MMAP. I think both solution can support that application programmer can handle buffer as like pointer and kernel can reduce copy overhead from kernel to user.
yes ,I agree with you .
But the application programmers do not know which method is more efficient.
Quoted 2 lines
Why do you have to support
IO_METHOD_USERPTR?
just for fun. For the v4l2 spec has the method ,why I don't realize it?
Quoted 3 lines
If you can justify your goal, we can add locked GUP.
I can't .
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Minchan Kim a??e??:
Quoted 36 lines
Quoted 32 lines · On Mon, 20 Apr 2009 10:15:55 +0800 Huang Shijie <[email protected]> wrote:
Minchan Kim a??e??:
Quoted 30 lines
Quoted 12 lines · On Fri, 17 Apr 2009 15:01:01 +0800 Huang Shijie <[email protected]> wrote:
I'm writting a driver for a video card with the V4L2 interface . V4L2 interface supports the USER-POINTER method for the video frame
handling.VLC player supports the USER-POINTER method,while MPALYER does not.
In the USER-POINTER method, VLC will call the
posix_memalign()to
allocate
203 pages in certain PAL mode (that is 720*576*2) for a single frame.
In my driver , I call theget_user_pages_fast()to obtain the pages
array,and then call
thevmap()to map the pages to VMALLOC space for the memcpy().The code shows below:Quoted 7 lines
.................... get_user_pages_fast(); ... f->data = vmap(); .......................What I understand is that you get the pages of
posix_memalignbyget_user_pages_fastand then that pages are mapped at kernel vmalloc space by vmap.Is it for removing copy overhead from kernel to user ?
Quoted 9 lines
Quoted 3 lines
I need a large range of virtual contigous memory to store my video frame(about 203 pages). When I received a full frame ,I will queue the buffer in a VIDIOC queue,which will be remove by the
VIDIOC_DQBUF.'I can't understand your point. Sorry for that.
Could you explain more detail relation (user buffer which is allocated by
posix_memalign) and (kernel buffer which is mapped by vmap) ?
[1] fork() will copy the range of 768-1024 index of swapper_pg_dir to mm->pgd.
[2] vmap setup mapping in the swapper_pg_dir.
[3] my driver will use the mm->pgd in the ioctl context.for the reason of [1],driver could see the address setup by vmap.
Quoted 57 lines
Quoted 40 lines
Quoted 29 lines
Quoted 29 lines
In comments, it said :
"
+/**
+ *get_user_pages_fast()- pin user pages in memory+ * @start: starting user address + * @nr_pages: number of pages from start to pin + * @write: whether pages will be written to + * @pages: array that receives pointers to the pages pinned. + * Should be at least nr_pages long."
But after I digged the code of kswap and the get_user_pages(called by
get_user_pages_fast),
I did not find how the pages pinned in memory.I really need the pages pinned in memory.Assume page A is one of the pages obtained by
get_user_pages_fast()
during page-fault.[1] page A will on the
LRU_ACTIVE_ANONlist;
the_countof page A increment by one;
PTE for page A will be set ACCESSED.[2] kswapd will scan the lru list,and move page A from
LRU_ACTIVE_ANONtoLRU_INACTIVE_ANON. In theshrink_page_list(), there is nothing can stop page A been
swapped out.
I don't think thepage_reference()can move page A back to
LRU_ACTIVE_ANON.In my driver,
I am not sure if the VLC can access the page A.Quoted 9 lines
Quoted 4 lines
Is this a bug? or I miss something?
Thanks .If above my assumption is right, It's not a BUG. You get the application's pages by
get_user_pages_fast. 'Page pinning' means it shouldn't be freed. Application's pages always can be swapped out. If you don't want to swap out the page, you should use mlock. If you use mlock, kernel won't insert the page to lru [in]active list. So the page never can be swapped out.Yes, it not a bug .
Quoted 8 lines
I read the kernel code again. In my case ,the kernel will pin the pages in memory. I missed function
is_page_cache_freeable()in thepageout().In my case, is_page_cache_freeable()will return false ,for
page_count(page)is 3 now:
<1> one is from alloc_page_* in page fault.
<2> one is fromget_usr_pages()
<3> one is fromadd_to_swap()inshrink_page_list()One more,
try_to_unmapwill callpage_cache_release. So, count is 2.Quoted 4 lines
So ,there is no need to use the mlock, it will mess my driver. is_page_cache_freeable()will return
PAGE_KEEP, and page is locked in swap cache.
Quoted 30 lines
I can't understand your point exactly yet.
But what I mean is following as in user mode
posix_memalignq(&buffer);mlock(buffer, buffer_len);I will not dirty your driver. Do I miss something ?
Quoted 18 lines
Unfortunately, the page is unmaped, and the PTE of the page has been replaced by a
swp_entry_t. When the process read the page ,it will raise a page fault again, the kernel will find the page in the swap cache, and requeue the page inLRU_ACTIVE_ANON, ---I think it is a vicious circle for the kernel.I think there two places to put back the
gup()pages. <1>isolate_page_glable()
<2> in theshrink_page_list(), before called thetry_to_unmap(). KOSAKI Motohiro 's patch takes effect in the second place. I think the first place is better.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 4 lines
Quoted 4 lines
I read the kernel code again. In my case ,the kernel will pin the pages in memory. I missed function
is_page_cache_freeable()in thepageout().
Quoted 9 lines
Quoted 5 lines
In my case, is_page_cache_freeable()will return false ,for
page_count(page)is 3 now:
<1> one is from alloc_page_* in page fault.
<2> one is fromget_usr_pages()
<3> one is fromadd_to_swap()inshrink_page_list()One more,
try_to_unmapwill callpage_cache_release. So, count is 2.
I found I missed something.When code reachs is_page_cache_freeable(). page_count(page) is 3:
<1> alloc_page_* in page fault . [page count is 1]
<2> get_usr_pages(). [page count is 2]
<3> isolate_pages_global() [page count is 3]
<4> add_to_swap() [page count is 4]
<5> try_to_unmap() [page count is 3]
so it not a bug, just a vicious circle.
Do i miss something?
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 15 lines · Huang Shijie
Quoted 5 lines
Quoted 5 lines
I read the kernel code again. In my case ,the kernel will pin the pages in memory.
I missed functionis_page_cache_freeable()in thepageout().Quoted 9 lines
Quoted 6 lines
In my case, is_page_cache_freeable()will return false ,for
page_count(page)is 3 now:
<1> one is from alloc_page_* in page fault.
<2> one is fromget_usr_pages()
<3> one is fromadd_to_swap()inshrink_page_list()One more,
try_to_unmapwill callpage_cache_release. So, count is 2.
Quoted 9 lines
I found I missed something.When code reachs
is_page_cache_freeable().page_count(page)is 3:<1> alloc_page_* in page fault . [page count is 1] <2>
get_usr_pages(). [page count is 2] <3>isolate_pages_global()[page count is 3] <4>add_to_swap()[page count is 4] <5>try_to_unmap()[page count is 3]
Yes. It seems you're right.
I missed isolate. ;-;
Thanks for fixing me.
Quoted 6 lines
so it not a bug, just a vicious circle.
Do i miss something?
Signature · 8 lines
Kinds regards, Minchan Kim -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 5 lines
I think there two places to put back the
gup()pages. <1>isolate_page_glable()
<2> in theshrink_page_list(), before called thetry_to_unmap(). KOSAKI Motohiro 's patch takes effect in the second place. I think the first place is better.
It seems don't works it.
Andrea pointed out mmu_notifier issue. kvm pinned various page for shadow pte.
it is unmapped by mmu_notifier_invalidate_page() in try_to_unmap_one().
Thus, we can only check page_count after mmu_notifier_invalidate_page.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>