diff options
Diffstat (limited to 'libpipe/pq.c')
-rw-r--r-- | libpipe/pq.c | 434 |
1 files changed, 434 insertions, 0 deletions
diff --git a/libpipe/pq.c b/libpipe/pq.c new file mode 100644 index 00000000..07196000 --- /dev/null +++ b/libpipe/pq.c @@ -0,0 +1,434 @@ +/* Packet queues + + Copyright (C) 1995, 1996, 1998, 1999 Free Software Foundation, Inc. + + Written by Miles Bader <miles@gnu.ai.mit.edu> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, or (at + your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include <malloc.h> +#include <string.h> +#include <stddef.h> +#include <sys/mman.h> + +#include "pq.h" + +/* ---------------------------------------------------------------- */ + +/* Create a new packet queue, returning it in PQ. The only possible error is + ENOMEM. */ +error_t +pq_create (struct pq **pq) +{ + *pq = malloc (sizeof (struct pq)); + + if (! *pq) + return ENOMEM; + + (*pq)->head = (*pq)->tail = 0; + (*pq)->free = 0; + + return 0; +} + +/* Free every packet (and its contents) in the linked list rooted at HEAD. */ +static void +free_packets (struct packet *head) +{ + if (head) + { + struct packet *next = head->next; + if (head->ports) + free (head->ports); + if (head->buf_len > 0) + { + if (head->buf_vm_alloced) + munmap (head->buf, head->buf_len); + else + free (head->buf); + } + free (head); + free_packets (next); + } +} + +/* Frees PQ and any resources it holds, including deallocating any ports in + packets left in the queue. */ +void +pq_free (struct pq *pq) +{ + pq_drain (pq); + free_packets (pq->free); + free (pq); +} + +/* ---------------------------------------------------------------- */ + +/* Remove the first packet (if any) in PQ, deallocating any resources it + holds. True is returned if a packet was found, false otherwise. */ +int +pq_dequeue (struct pq *pq) +{ + extern void pipe_dealloc_addr (void *addr); + struct packet *packet = pq->head; + + if (! packet) + return 0; + + /* Deallocate any resource in PACKET. */ + if (packet->num_ports) + packet_dealloc_ports (packet); + if (packet->source) + pipe_dealloc_addr (packet->source); + + pq->head = packet->next; + packet->next = pq->free; + pq->free = packet; + if (pq->head) + pq->head->prev = 0; + else + pq->tail = 0; + + return 1; +} + +/* Empties out PQ. This *will* deallocate any ports in any of the packets. */ +void +pq_drain (struct pq *pq) +{ + while (pq_dequeue (pq)) + ; +} + +/* Pushes a new packet of type TYPE and source SOURCE onto the tail of the + queue, and returns it, or 0 if there was an allocation error. */ +struct packet * +pq_queue (struct pq *pq, unsigned type, void *source) +{ + struct packet *packet = pq->free; + + if (!packet) + { + packet = malloc (sizeof (struct packet)); + if (!packet) + return 0; + packet->buf = 0; + packet->buf_len = 0; + packet->ports = 0; + packet->num_ports = packet->ports_alloced = 0; + packet->buf_start = packet->buf_end = packet->buf; + packet->buf_vm_alloced = 0; + } + else + pq->free = packet->next; + + packet->type = type; + packet->source = source; + packet->next = 0; + packet->prev = pq->tail; + if (pq->tail) + pq->tail->next = packet; + pq->tail = packet; + if (!pq->head) + pq->head = packet; + + return packet; +} + +/* ---------------------------------------------------------------- */ + +/* Returns a legal size to which PACKET can be set allowing enough room for + EXTRA bytes more than what's already in it, and perhaps more. */ +size_t +packet_new_size (struct packet *packet, size_t extra) +{ + size_t new_len = (packet->buf_end - packet->buf) + extra; + if (packet->buf_vm_alloced || new_len >= PACKET_SIZE_LARGE) + /* Round NEW_LEN up to a page boundary (OLD_LEN should already be). */ + return round_page (new_len); + else + /* Otherwise, just round up to a multiple of 512 bytes. */ + return (new_len + 511) & ~511; +} + +/* Try to extend PACKET to be NEW_LEN bytes long, which should be greater + than the current packet size. This should be a valid length -- i.e., if + it's greater than PACKET_SIZE_LARGE, it should be a mulitple of + VM_PAGE_SIZE. If PACKET cannot be extended for some reason, false is + returned, otherwise true. */ +int +packet_extend (struct packet *packet, size_t new_len) +{ + size_t old_len = packet->buf_len; + + if (old_len == 0) + /* No existing buffer to extend. */ + return 0; + + if (packet->buf_vm_alloced) + /* A vm_alloc'd packet. */ + { + char *extension = packet->buf + old_len; + /* Try to allocate memory at the end of our current buffer. */ + if (vm_allocate (mach_task_self (), + (vm_address_t *)&extension, new_len - old_len, 0) != 0) + return 0; + } + else + /* A malloc'd packet. */ + { + char *new_buf; + char *old_buf = packet->buf; + + if (new_len >= PACKET_SIZE_LARGE) + /* The old packet length is malloc'd, but we want to vm_allocate the + new length, so we'd have to copy the old contents. */ + return 0; + + new_buf = realloc (old_buf, new_len); + if (! new_buf) + return 0; + + packet->buf = new_buf; + packet->buf_start = new_buf + (packet->buf_start - old_buf); + packet->buf_end = new_buf + (packet->buf_end - old_buf); + } + + packet->buf_len = new_len; + + return 1; +} + +/* Reallocate PACKET to have NEW_LEN bytes of buffer space, which should be + greater than the current packet size. This should be a valid length -- + i.e., if it's greater than PACKET_SIZE_LARGE, it should be a multiple of + VM_PAGE_SIZE. If an error occurs, PACKET is not modified and the error is + returned. */ +error_t +packet_realloc (struct packet *packet, size_t new_len) +{ + error_t err; + char *new_buf; + char *old_buf = packet->buf; + int vm_alloc = (new_len >= PACKET_SIZE_LARGE); + + /* Make a new buffer. */ + if (vm_alloc) + { + new_buf = mmap (0, new_len, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0); + err = (new_buf == (char *) -1) ? errno : 0; + } + else + { + new_buf = malloc (new_len); + err = (new_buf ? 0 : ENOMEM); + } + + if (! err) + { + size_t old_len = packet->buf_len; + char *start = packet->buf_start, *end = packet->buf_end; + + /* Copy what we must. */ + if (end != start) + /* If there was an operation like vm_move, we could use that in the + case where both the old and the new buffers were vm_alloced (on + the assumption that creating COW pages is somewhat more costly). + But there's not, and bcopy will do vm_copy where it can. Will we + still takes faults on the new copy, even though we've deallocated + the old one??? XXX */ + bcopy (start, new_buf, end - start); + + /* And get rid of the old buffer. */ + if (old_len > 0) + { + if (packet->buf_vm_alloced) + vm_deallocate (mach_task_self (), (vm_address_t)old_buf, old_len); + else + free (old_buf); + } + + packet->buf = new_buf; + packet->buf_len = new_len; + packet->buf_vm_alloced = vm_alloc; + packet->buf_start = new_buf; + packet->buf_end = new_buf + (end - start); + } + + return err; +} + +/* ---------------------------------------------------------------- */ + +/* If PACKET has any ports, deallocates them. */ +void +packet_dealloc_ports (struct packet *packet) +{ + unsigned i; + for (i = 0; i < packet->num_ports; i++) + { + mach_port_t port = packet->ports[i]; + if (port != MACH_PORT_NULL) + mach_port_deallocate (mach_task_self (), port); + } +} + +/* Sets PACKET's ports to be PORTS, of length NUM_PORTS. ENOMEM is returned + if a memory allocation error occurred, otherwise, 0. */ +error_t +packet_set_ports (struct packet *packet, + mach_port_t *ports, size_t num_ports) +{ + if (packet->num_ports > 0) + packet_dealloc_ports (packet); + if (num_ports > packet->ports_alloced) + { + mach_port_t *new_ports = malloc (sizeof (mach_port_t *) * num_ports); + if (! new_ports) + return ENOMEM; + free (packet->ports); + packet->ports_alloced = num_ports; + } + bcopy (ports, packet->ports, sizeof (mach_port_t *) * num_ports); + packet->num_ports = num_ports; + return 0; +} + +/* Returns any ports in PACKET in PORTS and NUM_PORTS, and removes them from + PACKET. */ +error_t +packet_read_ports (struct packet *packet, + mach_port_t **ports, size_t *num_ports) +{ + int length = packet->num_ports * sizeof (mach_port_t *); + if (*num_ports < packet->num_ports) + { + *ports = mmap (0, length, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0); + if (*ports == (mach_port_t *) -1) + return errno; + } + *num_ports = packet->num_ports; + bcopy (packet->ports, *ports, length); + packet->num_ports = 0; + return 0; +} + +/* Append the bytes in DATA, of length DATA_LEN, to what's already in PACKET, + and return the amount appended in AMOUNT. */ +error_t +packet_write (struct packet *packet, + char *data, size_t data_len, size_t *amount) +{ + error_t err = packet_ensure (packet, data_len); + + if (err) + return err; + + /* Add the new data. */ + bcopy (data, packet->buf_end, data_len); + packet->buf_end += data_len; + *amount = data_len; + + return 0; +} + +/* Removes up to AMOUNT bytes from the beginning of the data in PACKET, and + puts it into *DATA, and the amount read into DATA_LEN. If more than the + original *DATA_LEN bytes are available, new memory is vm_allocated, and + the address and length of this array put into DATA and DATA_LEN. */ +error_t +packet_read (struct packet *packet, + char **data, size_t *data_len, size_t amount) +{ + char *start = packet->buf_start; + char *end = packet->buf_end; + + if (amount > end - start) + amount = end - start; + + if (amount > 0) + { + char *buf = packet->buf; + + if (packet->buf_vm_alloced && amount >= vm_page_size) + /* We can return memory from BUF directly without copying. */ + { + if (buf + vm_page_size <= start) + /* BUF_START has been advanced past the start of the buffer + (perhaps by a series of small reads); as we're going to assume + everything before START is gone, make sure we deallocate any + memory on pages before those we return to the user. */ + vm_deallocate (mach_task_self (), + (vm_address_t)buf, + trunc_page (start) - (vm_address_t)buf); + + *data = start; /* Return the buffer directly. */ + start += amount; /* Advance the read point. */ + + if (start < end) + /* Since returning a partial page actually means returning the + whole page, we have to be careful not to grab past the page + boundary before the end of the data we want. */ + { + char *non_aligned_start = start; + start = (char *)trunc_page (start); + amount -= non_aligned_start - start; + } + else + /* This read will be up to the end of the buffer, so we can just + consume any space on the page following BUF_END (vm_alloced + buffers are always allocated in whole pages). */ + { + start = (char *)round_page (start); + packet->buf_end = start; /* Ensure BUF_START <= BUF_END. */ + } + + /* We've actually consumed the memory at the start of BUF. */ + packet->buf = start; + packet->buf_start = start; + packet->buf_len -= start - buf; + } + else + /* Just copy the data the old fashioned way.... */ + { + if (*data_len < amount) + *data = mmap (0, amount, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0); + + bcopy (start, *data, amount); + start += amount; + + if (start - buf > 2 * PACKET_SIZE_LARGE) + /* Get rid of unused space at the beginning of the buffer -- we + know it's vm_alloced because of the size, and this will allow + the buffer to just slide through memory. Because we wait for + a relatively large amount of free space before doing this, and + packet_write() would have gotten rid the free space if it + didn't require copying much data, it's unlikely that this will + happen if it would have been cheaper to just move the packet + contents around to make space for the next write. */ + { + vm_size_t dealloc = trunc_page (start) - (vm_address_t)buf; + vm_deallocate (mach_task_self (), (vm_address_t)buf, dealloc); + packet->buf = buf + dealloc; + packet->buf_len -= dealloc; + } + + packet->buf_start = start; + } + } + *data_len = amount; + + return 0; +} |