Message ID | 20241114104517.51726-1-anuj20.g@samsung.com |
---|---|
Headers | show |
Series | Read/Write with meta/integrity | expand |
On Thu, Nov 14, 2024 at 04:15:12PM +0530, Anuj Gupta wrote: > PI attribute is supported only for direct IO. Also, vectored read/write > operations are not supported with PI currently. Eww. I know it's frustration for your if maintainers give contradicting guidance, but this is really an awful interface. Not only the pointless indirection which make the interface hard to use, but limiting it to not support vectored I/O makes it pretty useless. I guess I need to do a little read-up on why Pavel wants this, but from the block/fs perspective the previous interface made so much more sense.
On 11/14/24 12:16, Christoph Hellwig wrote: > On Thu, Nov 14, 2024 at 04:15:12PM +0530, Anuj Gupta wrote: >> PI attribute is supported only for direct IO. Also, vectored read/write >> operations are not supported with PI currently. And my apologies Anuj, I've been busy, I hope to take a look at this series today / tomorrow. > Eww. I know it's frustration for your if maintainers give contradicting > guidance, but this is really an awful interface. Not only the pointless Because once you placed it at a fixed location nothing realistically will be able to reuse it. Not everyone will need PI, but the assumption that there will be more more additional types of attributes / parameters. With SQE128 it's also a problem that now all SQEs are 128 bytes regardless of whether a particular request needs it or not, and the user will need to zero them for each request. The discussion continued in the v6 thread, here https://lore.kernel.org/all/20241031065535.GA26299@lst.de/T/#m12beca2ede2bd2017796adb391bedec9c95d85c3 and a little bit more here: https://lore.kernel.org/all/20241031065535.GA26299@lst.de/T/#mc3f7a95915a64551e061d37b33a643676c5d87b2 > indirection which make the interface hard to use, but limiting it to > not support vectored I/O makes it pretty useless. I'm not sure why that's the case and need to take a look), but I don't immediately see how it's relevant to that part of the API. It shouldn't really matter where the main PI structure is located, you get an iovec pointer and code from there wouldn't be any different. > I guess I need to do a little read-up on why Pavel wants this, but > from the block/fs perspective the previous interface made so much > more sense.
On 11/14/24 15:19, Christoph Hellwig wrote: > On Thu, Nov 14, 2024 at 01:09:44PM +0000, Pavel Begunkov wrote: >>> Eww. I know it's frustration for your if maintainers give contradicting >>> guidance, but this is really an awful interface. Not only the pointless >> >> Because once you placed it at a fixed location nothing realistically >> will be able to reuse it. Not everyone will need PI, but the assumption >> that there will be more more additional types of attributes / parameters. > > So? If we have a strong enough requirement for something else we > can triviall add another opcode. Maybe we should just add different > opcodes for read/write with metadata so that folks don't freak out > about this? IMHO, PI is not so special to have a special opcode for it unlike some more generic read/write with meta / attributes, but that one would have same questions. FWIW, the series was steered from the separate opcode approach to avoid duplicating things, for example there are 3 different OP_READ* opcodes varying by the buffer type, and there is no reason meta reads wouldn't want to support all of them as well. I have to admit that the effort is a bit unfortunate on that side switching back a forth at least a couple of times including attempts from 2+ years ago by some other guy. >> With SQE128 it's also a problem that now all SQEs are 128 bytes regardless >> of whether a particular request needs it or not, and the user will need >> to zero them for each request. > > The user is not going to create a SQE128 ring unless they need to, > so this seem like a bit of an odd objection. It doesn't bring this overhead to those who don't use meta/PI, that's right, but it does add it if you want to mix it with nearly all other request types, and that is desirable. As I mentioned before, it's just one downside but not a deal breaker. I'm more concerned that the next type of meta information won't be able to fit into the SQE and then we'll need to solve the same problem (indirection + optimising copy_from_user with other means) while having PI as a special case. And that's more of a problem of the static placing from previous version, e.g. it wouldn't be a problem if in the long run it becomes sth like: struct attr attr, *p; if (flags & META_IN_USE_SQE128) p = sqe + 1; else { copy_from_user(&attr); p = &attr; } but that shouldn't be PI specific.
On 11/15/24 10:12 AM, Christoph Hellwig wrote: > On Fri, Nov 15, 2024 at 04:40:58PM +0000, Pavel Begunkov wrote: >>> So? If we have a strong enough requirement for something else we >>> can triviall add another opcode. Maybe we should just add different >>> opcodes for read/write with metadata so that folks don't freak out >>> about this? >> >> IMHO, PI is not so special to have a special opcode for it unlike >> some more generic read/write with meta / attributes, but that one >> would have same questions. > > Well, apparently is one the hand hand not general enough that you > don't want to give it SQE128 space, but you also don't want to give > it an opcode. > > Maybe we just need make it uring_cmd to get out of these conflicting > requirements. Let's please lay off the hyperbole here, uring_cmd would be a terrible way to do this. We're working through the flags requirements. Obviously this is now missing 6.13, but there's no reason why it's not on track to make 6.14 in a saner way.
On Thu, Nov 14, 2024 at 01:09:44PM +0000, Pavel Begunkov wrote: > With SQE128 it's also a problem that now all SQEs are 128 bytes regardless > of whether a particular request needs it or not, and the user will need > to zero them for each request. The way we handled this in NVMe was to use a bit in the command that was called (iirc) FUSED, which let you use two consecutive entries for a single command. Some variant on that could surely be used for io_uring. Perhaps a special opcode that says "the real opcode is here, and this is a two-slot command". Processing gets a little spicy when one slot is the last in the buffer and the next is the the first in the buffer, but that's a SMOP.
On 11/14/24 10:45, Anuj Gupta wrote: > Add the ability to pass additional attributes along with read/write. > Application can populate an array of 'struct io_uring_attr_vec' and pass > its address using the SQE field: > __u64 attr_vec_addr; > > Along with number of attributes using: > __u8 nr_attr_indirect; > > Overall 16 attributes are allowed and currently one attribute > 'ATTR_TYPE_PI' is supported. Why only 16? It's possible that might need more, 256 would be a safer choice and fits into u8. I don't think you even need to commit to a number, it should be ok to add more as long as it fits into the given types (u8 above). It can also be u16 as well. > With PI attribute, userspace can pass following information: > - flags: integrity check flags IO_INTEGRITY_CHK_{GUARD/APPTAG/REFTAG} > - len: length of PI/metadata buffer > - addr: address of metadata buffer > - seed: seed value for reftag remapping > - app_tag: application defined 16b value In terms of flexibility I like it apart from small nits, but the double indirection could be a bit inefficient, this thing: struct pi_attr pi = {}; attr_array = { &pi, ... }; sqe->attr_addr = attr_array; So maybe we should just flatten it? An attempt to pseudo code it to understand what it entails is below. Certainly buggy and some handling is omitted, but should show the idea. // uapi/.../io_uring.h struct sqe { ... u64 attr_addr; /* the total size of the array pointed by attr_addr */ u16 attr_size; /* max 64KB, more than enough */ } struct io_attr_header { /* bit mask of attributes passed, can be helpful in the future * for optimising processing. */ u64 attr_type_map; }; /* each attribute should start with a preamble */ struct io_uring_attr_preamble { u16 attr_type; }; // user space struct PI_param { struct io_attr_header header; struct io_uring_attr_preamble preamble; struct io_uring_attr_pi pi; }; struct PI_param p = {}; p.header.map = 1 << ATTR_TYPE_PI; p.preamble.type = ATTR_TYPE_PI; p.pi = {...}; sqe->attr_addr = &p; sqe->attr_size = sizeof(p); The holes b/w structures should be packed better. For the same reason I don't like a separate preamble structure much, maybe it should be embedded into the attribute structures, e.g. struct io_uring_attr_pi { u16 attr_type; ... } The user side looks ok to me, should be pretty straightforward if the user can define a structure like PI_param, i.e. knows at compilation time which attributes it wants to use. // kernel space (current patch set, PI only) addr = READ_ONCE(sqe->attr_addr); if (addr) { size = READ_ONCE(sqe->attr_size); process_pi(addr, size); } process_pi(addr, size) { struct PI_param p; if (size != sizeof(PI_attr + struct attr_preamble + struct attr_header)) return -EINVAL; copy_from_user(p, addr, sizeof(p)); if (p.preamble != ATTR_TYPE_PI) return -EINVAL; do_pi_setup(&p->pi); } This one is pretty simple as well. A bit more troublesome if extended with many attributes, but it'd need additional handling regardless: process_pi(addr, size) { if (size < sizeof(header + preamble)) return -EINVAL; attr_array = malloc(size); // +caching by io_uring copy_from_user(attr_array); handle_attributes(attr_array, size); } handle_attributes(attr_array, size) { struct io_attr_header *hdr = attr_array; offset = sizeof(*hdr); while (1) { if (offset + sizeof(struct preamble) > size) break; struct preamble *pr = attr_array + offset; if (pr->type > MAX_TYPES) return -EINVAL; attr_size = attr_sizes[pr->type]; if (offset + sizeof(preamble) + attr_size > size) return -EINVAL; offset += sizeof(preamble) + attr_size; process_attr(pr->type, (void *)(pr + 1)); } } Some checks can probably be optimised by playing with the uapi a bit. attr_type_map is unused here, but I like the idea. I'd love to see all actual attribute handling to move deeper into the stack to those who actually need it, but that's for far away undecided future. E.g. io_uring_rw { p = malloc(); copy_from_user(p, sqe->attr_addr); kiocb->attributes = p; } block_do_read { hdr = kiocb->attributes; type_mask = /* all types block layer recognises */ if (hdr->attr_type_map & type_mask) use_attributes(); } copy_from_user can be optimised, I mentioned before, we can have a pre-mapped area into which the indirection can point. The infra is already in there and even used for passing waiting arguments. process_pi(addr, size) { struct PI_param *p, __p; if (some_flags & USE_REGISTERED_REGION) { // Glorified p = ctx->ptr; with some checks p = io_uring_get_mem(addr, size); } else { copy_from_user(__p, addr, sizeof(__p)); p = &__p; } ... } In this case all reads would need to be READ_ONCE, but that shouldn't be a problem. It might also optimise out the kmalloc in the extended version.