一 : sendto()系统调用

本文基于 linux-4.9.38+Xenomai 3.0.5 的实时系统
加载用的是xenomai下pci总线驱动rtcan_adv_pci,以及rtcan_sja1000的can控制器驱动。
Xenomai 3 的rtdm驱动更像一般的Linux驱动,device会在/dev/rtdm/xxx创建一个设备文件。而用户空间使用时,写得来也和Linux的一般char设备相似,使用sendto/recvfrom/open/close/read/write/ioctl等系统调用,实际上只不过在link的时候这些函数都被做了手脚,替换成了libcobalt.so中的函数(参见 /usr/xenomai/lib/cobalt.wrappers)。

二:代码分析

在应用程序中通过 sendto()传入套接字生成的文件描述符,将会产生如下的调用过程。
rtdm_sendto()->rtdm_sendmsg()->rtdm_fd_sendmsg()->rtcan_raw_sendmsg()->rtcan_sja_start_xmit()->chip->write_reg()->iowrite8()
将应用程序中的数据内容复制到内核发送缓冲区中,再进行对硬件的发送:
rtdm.h中

static inline ssize_t rtdm_sendto(int s, const void *buf, size_t len,int flags, const struct sockaddr *to,socklen_t tolen)
{struct user_msghdr msg;struct iovec iov;iov.iov_base = (void *)buf;iov.iov_len = len;msg.msg_name = (struct sockaddr *)to;msg.msg_namelen = tolen;msg.msg_iov = &iov;msg.msg_iovlen = 1;msg.msg_control = NULL;msg.msg_controllen = 0;return rtdm_sendmsg(s, &msg, flags);
}
static inline ssize_t rtdm_sendmsg(int s, const struct user_msghdr *msg, int flags)
{return rtdm_fd_sendmsg(s, msg, flags);
}

在fd.c中

ssize_t rtdm_fd_sendmsg(int ufd, const struct user_msghdr *msg, int flags)
{struct rtdm_fd *fd;ssize_t ret;fd = get_fd_fixup_mode(ufd);if (IS_ERR(fd)) {ret = PTR_ERR(fd);goto out;}set_compat_bit(fd);trace_cobalt_fd_sendmsg(current, fd, ufd, flags);if (ipipe_root_p)ret = fd->ops->sendmsg_nrt(fd, msg, flags);elseret = fd->ops->sendmsg_rt(fd, msg, flags);if (!XENO_ASSERT(COBALT, !spltest()))splnone();rtdm_fd_put(fd);
out:if (ret < 0)trace_cobalt_fd_sendmsg_status(current, fd, ufd, ret);return ret;
}

Xenomai 3.x protocol device description
(https://gitlab.denx.de/Xenomai/xenomai/-/wikis/Migrating_From_Xenomai_2_To_3)
rtcan_raw.c中
函数 fd->ops->sendmsg_rt(fd, msg, flags) = rtcan_raw_sendmsg()

static struct rtdm_driver rtcan_driver = {.profile_info     = RTDM_PROFILE_INFO(rtcan,RTDM_CLASS_CAN,RTDM_SUBCLASS_GENERIC,RTCAN_PROFILE_VER),.device_flags        = RTDM_PROTOCOL_DEVICE,.device_count       = 1,.context_size      = sizeof(struct rtcan_socket),.protocol_family = PF_CAN,.socket_type      = SOCK_RAW,.ops = {.socket        = rtcan_raw_socket,.close      = rtcan_raw_close,.ioctl_nrt   = rtcan_raw_ioctl,.recvmsg_rt  = rtcan_raw_recvmsg,.sendmsg_rt    = rtcan_raw_sendmsg,},
};static struct rtdm_device rtcan_device = {.driver = &rtcan_driver,.label = "rtcan",
};

rtcan_raw.c中的
rtcan_raw_sendmsg()

ssize_t rtcan_raw_sendmsg(struct rtdm_fd *fd,const struct user_msghdr *msg, int flags)
{struct rtcan_socket *sock = rtdm_fd_to_private(fd);struct sockaddr_can *scan = (struct sockaddr_can *)msg->msg_name;struct sockaddr_can scan_buf;struct iovec *iov = (struct iovec *)msg->msg_iov;struct iovec iov_buf;can_frame_t *frame;can_frame_t frame_buf;rtdm_lockctx_t lock_ctx;nanosecs_rel_t timeout = 0;struct tx_wait_queue tx_wait;struct rtcan_device *dev;int ifindex = 0;int ret  = 0;spl_t s;if (flags & MSG_OOB)   /* Mirror BSD error message compatibility */return -EOPNOTSUPP;/* Only MSG_DONTWAIT is a valid flag. */if (flags & ~MSG_DONTWAIT)return -EINVAL;/* Check msg_iovlen, only one buffer allowed */if (msg->msg_iovlen != 1)return -EMSGSIZE;if (scan == NULL) {/* No socket address. Will use bound interface for sending */if (msg->msg_namelen != 0)return -EINVAL;/* We only want a consistent value here, a spin lock would be* overkill. Nevertheless, the binding could change till we have* the chance to send. Blame the user, though. */ifindex = atomic_read(&sock->ifindex);if (!ifindex)/* Socket isn't bound or bound to all interfaces. Go out. */return -ENXIO;} else {/* Socket address given */if (msg->msg_namelen < sizeof(struct sockaddr_can))return -EINVAL;if (rtdm_fd_is_user(fd)) {/* Copy socket address from userspace */if (!rtdm_read_user_ok(fd, msg->msg_name,sizeof(struct sockaddr_can)) ||rtdm_copy_from_user(fd, &scan_buf, msg->msg_name,sizeof(struct sockaddr_can)))return -EFAULT;scan = &scan_buf;}/* Check address family */if (scan->can_family != AF_CAN)return -EINVAL;ifindex = scan->can_ifindex;}if (rtdm_fd_is_user(fd)) {/* Copy IO vector from userspace */if (!rtdm_rw_user_ok(fd, msg->msg_iov,sizeof(struct iovec)) ||rtdm_copy_from_user(fd, &iov_buf, msg->msg_iov,sizeof(struct iovec)))return -EFAULT;iov = &iov_buf;}/* Check size of buffer */if (iov->iov_len != sizeof(can_frame_t))return -EMSGSIZE;frame = (can_frame_t *)iov->iov_base;if (rtdm_fd_is_user(fd)) {/* Copy CAN frame from userspace */if (!rtdm_read_user_ok(fd, iov->iov_base,sizeof(can_frame_t)) ||rtdm_copy_from_user(fd, &frame_buf, iov->iov_base,sizeof(can_frame_t)))return -EFAULT;frame = &frame_buf;}/* Adjust iovec in the common way */iov->iov_base += sizeof(can_frame_t);iov->iov_len -= sizeof(can_frame_t);/* ... and copy it back to userspace if necessary */if (rtdm_fd_is_user(fd)) {if (rtdm_copy_to_user(fd, msg->msg_iov, iov,sizeof(struct iovec)))return -EFAULT;}/* At last, we've got the frame ... *//* Check if DLC between 0 and 15 */if (frame->can_dlc > 15)return -EINVAL;/* Check if it is a standard frame and the ID between 0 and 2031 */if (!(frame->can_id & CAN_EFF_FLAG)) {u32 id = frame->can_id & CAN_EFF_MASK;if (id > (CAN_SFF_MASK - 16))return -EINVAL;}if ((dev = rtcan_dev_get_by_index(ifindex)) == NULL)return -ENXIO;timeout = (flags & MSG_DONTWAIT) ? RTDM_TIMEOUT_NONE : sock->tx_timeout;tx_wait.rt_task = rtdm_task_current();/* Register the task at the socket's TX wait queue and decrement* the TX semaphore. This must be atomic. Finally, the task must* be deregistered again (also atomic). */cobalt_atomic_enter(s);list_add(&tx_wait.tx_wait_list, &sock->tx_wait_head);/* Try to pass the guard in order to access the controller */ret = rtdm_sem_timeddown(&dev->tx_sem, timeout, NULL);/* Only dequeue task again if socket isn't being closed i.e. if* this task was not unblocked within the close() function. */if (likely(!list_empty(&tx_wait.tx_wait_list)))/* Dequeue this task from the TX wait queue */list_del_init(&tx_wait.tx_wait_list);else/* The socket was closed. */ret = -EBADF;cobalt_atomic_leave(s);/* Error code returned? */if (ret != 0) {/* Which error code? */switch (ret) {case -EIDRM:/* Controller is stopped or bus-off */ret = -ENETDOWN;goto send_out1;case -EWOULDBLOCK:/* We would block but don't want to */ret = -EAGAIN;goto send_out1;default:/* Return all other error codes unmodified. */goto send_out1;}}/* We got access *//* Push message onto stack for loopback when TX done */if (rtcan_loopback_enabled(sock))rtcan_tx_push(dev, sock, frame);rtdm_lock_get_irqsave(&dev->device_lock, lock_ctx);/* Controller should be operating */if (!CAN_STATE_OPERATING(dev->state)) {if (dev->state == CAN_STATE_SLEEPING) {ret = -ECOMM;rtdm_lock_put_irqrestore(&dev->device_lock, lock_ctx);rtdm_sem_up(&dev->tx_sem);goto send_out1;}ret = -ENETDOWN;goto send_out2;}dev->tx_count++;ret = dev->hard_start_xmit(dev, frame);/* Return number of bytes sent upon successful completion */if (ret == 0)ret = sizeof(can_frame_t);send_out2:rtdm_lock_put_irqrestore(&dev->device_lock, lock_ctx);send_out1:rtcan_dev_dereference(dev);return ret;
}

rtdm_copy_from_user()从用户缓冲区复制出数据到内核缓冲区中//Copy CAN frame from userspace,这里设置了 ret = rtdm_sem_timeddown(&dev->tx_sem, timeout, NULL);来等待中断的发送信号,具体介绍在CAN通信中有介绍。

ret = dev->hard_start_xmit(dev, frame);
在以下结构体中 定义了 Device operations(设备的操作)
rtcan_raw.c

struct rtcan_device {unsigned int        version;char                name[IFNAMSIZ];char                *ctrl_name; /* Name of CAN controller */char                *board_name;/* Name of CAN board */unsigned long       base_addr;  /* device I/O address   */rtdm_irq_t          irq_handle; /* RTDM IRQ handle */int                 ifindex;
#ifdef RTCAN_USE_REFCOUNTatomic_t            refcount;
#endifvoid                *priv;      /* pointer to chip private data */void                *board_priv;/* pointer to board private data*/struct semaphore    nrt_lock;   /* non-real-time locking        *//* Spinlock for all devices (but not for all attributes) and also for HW* access to all CAN controllers所有设备(但不是所有属性)和所有控制器的硬件访问的自旋锁 */rtdm_lock_t         device_lock;/* Acts as a mutex allowing only one sender to write to the MSCAN* simultaneously. Created when the controller goes into operating mode,* destroyed if it goes into reset mode. 作为互斥锁,只允许一个发送者同时向MSCAN写入数据。控制器进入工作模式时创建,进入复位模式时销毁。* /*/rtdm_sem_t          tx_sem;/* Baudrate of this device. Protected by device_lock in all device* structures. 这个设备的波特率。在所有设备结构中由device_lock保护。*/unsigned int        can_sys_clock;/* Baudrate of this device. Protected by device_lock in all device* structures. */can_baudrate_t      baudrate;struct can_bittime  bit_time;const struct can_bittiming_const *bittiming_const;/* State which the controller is in. Protected by device_lock in all* device structures. */can_state_t state;/* State which the controller was before sleeping. Protected by* device_lock in all device structures. */can_state_t          state_before_sleep;/* Controller specific settings. Protected by device_lock in all* device structures. */can_ctrlmode_t       ctrl_mode;/* Device operations 设备操作*/int                 (*hard_start_xmit)(struct rtcan_device *dev,struct can_frame *frame);int                 (*do_set_mode)(struct rtcan_device *dev,can_mode_t mode,rtdm_lockctx_t *lock_ctx);can_state_t         (*do_get_state)(struct rtcan_device *dev);int                 (*do_set_bit_time)(struct rtcan_device *dev,struct can_bittime *bit_time,rtdm_lockctx_t *lock_ctx);
#ifdef CONFIG_XENO_DRIVERS_CAN_BUS_ERRvoid                (*do_enable_bus_err)(struct rtcan_device *dev);
#endif/* Reception list head. This list contains all filters which have been* registered via a bind call. */struct rtcan_recv               *recv_list;/* Empty list head. This list contains all empty entries not needed* by the reception list and therefore is disjunctive with it. */struct rtcan_recv               *empty_list;/* Preallocated array for the list entries. To increase cache* locality all list elements are kept in this array. */struct rtcan_recv               receivers[RTCAN_MAX_RECEIVERS];/* Indicates the length of the empty list */int                             free_entries;/* A few statistics counters */unsigned int tx_count;unsigned int rx_count;unsigned int err_count;#ifdef CONFIG_PROC_FSstruct proc_dir_entry *proc_root;
#endif
#ifdef CONFIG_XENO_DRIVERS_CAN_LOOPBACKstruct rtcan_skb tx_skb;struct rtcan_socket *tx_socket;
#endif /* CONFIG_XENO_DRIVERS_CAN_LOOPBACK */
};

pci驱动硬件can 控制器中,进行can帧进行发生
rtcansja1000.c中定义
dev->hard_start_xmit = rtcan_sja_start_xmit;

int rtcan_sja1000_register(struct rtcan_device *dev)
{int                         ret;struct rtcan_sja1000 *chip = dev->priv;if (chip == NULL)return -EINVAL;/* Set dummy state for following call 为以下调用设置虚拟状态 */dev->state = CAN_STATE_ACTIVE;/* Enter reset mode 进入重置模式 */rtcan_sja_mode_stop(dev, NULL);if ((chip->read_reg(dev, SJA_SR) &(SJA_SR_RBS | SJA_SR_DOS | SJA_SR_TBS)) != SJA_SR_TBS) {printk("ERROR! No SJA1000 device found!\n");return -ENODEV;}dev->ctrl_name = sja_ctrl_name;dev->hard_start_xmit = rtcan_sja_start_xmit;dev->do_set_mode = rtcan_sja_set_mode;dev->do_get_state = rtcan_sja_get_state;dev->do_set_bit_time = rtcan_sja_set_bit_time;dev->do_enable_bus_err = rtcan_sja_enable_bus_err;
#ifndef CONFIG_XENO_DRIVERS_CAN_CALC_BITTIME_OLDdev->bittiming_const = &sja1000_bittiming_const;
#endifchip->bus_err_on = 1;// rtdm 中断请求 ret = rtdm_irq_request(&dev->irq_handle,chip->irq_num, rtcan_sja_interrupt,chip->irq_flags, sja_ctrl_name, dev);if (ret) {printk(KERN_ERR "ERROR %d: IRQ %d is %s!\n",ret, chip->irq_num, ret == -EBUSY ?"busy, check shared interrupt support" : "invalid");return ret;}sja1000_chip_config(dev);/* Register RTDM device 注册RTDM设备*/ret = rtcan_dev_register(dev);if (ret) {printk(KERN_ERR"ERROR %d while trying to register RTCAN device!\n", ret);goto out_irq_free;}rtcan_sja_create_proc(dev);return 0;out_irq_free:rtdm_irq_free(&dev->irq_handle);return ret;
}

rtcan_sja1000.c 中
rtcan_sja_start_xmit() 开始发送数据

/**  Start a transmission to a SJA1000 device 启动到SJA1000设备的传输 */
static int rtcan_sja_start_xmit(struct rtcan_device *dev,can_frame_t *frame)
{int             i;/* "Real" size of the payload 有效载荷的“真实”大小*/u8   size;/* Content of frame information register 帧信息寄存器的内容*/u8   fir;struct rtcan_sja1000 *chip = (struct rtcan_sja1000 *)dev->priv;/* Get DLC */fir  = frame->can_dlc;/* If DLC exceeds 8 bytes adjust it to 8 (for the payload) 如果数据链路连接器超过8字节,则将其调整为8(对于有效负载 */size = (fir > 8) ? 8 : fir;if (frame->can_id & CAN_EFF_FLAG) {/* Send extended frame  发送扩展帧 */fir |= SJA_FIR_EFF;/* Write ID */chip->write_reg(dev, SJA_ID1, frame->can_id >> 21);chip->write_reg(dev, SJA_ID2, frame->can_id >> 13);chip->write_reg(dev, SJA_ID3, frame->can_id >> 5);chip->write_reg(dev, SJA_ID4, frame->can_id << 3);/* RTR? 远程请求发送位?*/if (frame->can_id & CAN_RTR_FLAG)fir |= SJA_FIR_RTR;else {/* No RTR, write data bytes */for (i = 0; i < size; i++)chip->write_reg(dev, SJA_DATA_EFF(i),frame->data[i]);}} else {/* Send standard frame   发送标准帧 *//* Write ID */chip->write_reg(dev, SJA_ID1, frame->can_id >> 3);chip->write_reg(dev, SJA_ID2, frame->can_id << 5);/* RTR? */if (frame->can_id & CAN_RTR_FLAG)fir |= SJA_FIR_RTR;else {/* No RTR, write data bytes */for (i = 0; i < size; i++)chip->write_reg(dev, SJA_DATA_SFF(i),frame->data[i]);}}/* Write frame information register 写帧信息带寄存器*/chip->write_reg(dev, SJA_FIR, fir);/* Push the 'send' button 按“发送”按钮 */if (dev->ctrl_mode & CAN_CTRLMODE_LOOPBACK)chip->write_reg(dev, SJA_CMR, SJA_CMR_SRR);elsechip->write_reg(dev, SJA_CMR, SJA_CMR_TR);return 0;
}

其中用的pci驱动的写函数,在rtcan_adv_pci.c中有定义
/* Write ID */
chip->write_reg(dev, SJA_ID1, frame->can_id >> 21);
chip->write_reg(dev, SJA_ID2, frame->can_id >> 13);
chip->write_reg(dev, SJA_ID3, frame->can_id >> 5);
chip->write_reg(dev, SJA_ID4, frame->can_id << 3)

rtcan_adv_pci_add_chan()函数中

static int rtcan_adv_pci_add_chan(struct pci_dev *pdev,int channel,unsigned int bar,unsigned int offset,struct rtcan_device **master_dev)
{struct rtcan_device *dev;struct rtcan_sja1000 *chip;struct rtcan_adv_pci *board;void __iomem *base_addr; //寄存器地址映射int ret;dev = rtcan_dev_alloc(sizeof(struct rtcan_sja1000),sizeof(struct rtcan_adv_pci));if (dev == NULL)return -ENOMEM;chip = (struct rtcan_sja1000 *)dev->priv;board = (struct rtcan_adv_pci *)dev->board_priv;if (channel == CHANNEL_SLAVE) {struct rtcan_adv_pci *master_board =(struct rtcan_adv_pci *)(*master_dev)->board_priv;master_board->slave_dev = dev;if (offset)base_addr = master_board->base_addr+offset;elsebase_addr = pci_iomap(pdev, bar, ADV_PCI_BASE_SIZE);if (!base_addr) {if (!base_addr) {ret = -EIO;goto failure;}} else {base_addr = pci_iomap(pdev, bar, ADV_PCI_BASE_SIZE) + offset;ret = -EIO;goto failure;}}board->pci_dev = pdev;board->conf_addr = NULL;board->base_addr = base_addr;dev->board_name = adv_pci_board_name;chip->read_reg = rtcan_adv_pci_read_reg;chip->write_reg = rtcan_adv_pci_write_reg;/* Clock frequency in Hz */dev->can_sys_clock = ADV_PCI_CAN_CLOCK;/* Output control register 输出控制寄存器*/chip->ocr = ADV_PCI_OCR;/* Clock divider register  时钟分频器寄存器     */chip->cdr = ADV_PCI_CDR;strncpy(dev->name, RTCAN_DEV_NAME, IFNAMSIZ);/* Make sure SJA1000 is in reset mode 确保SJA1000处于复位模式 */chip->write_reg(dev, SJA_MOD, SJA_MOD_RM);/* Set PeliCAN mode */chip->write_reg(dev, SJA_CDR, SJA_CDR_CAN_MODE);/* check if mode is set  检查是否设置了模式 */ret = chip->read_reg(dev, SJA_CDR);if (ret != SJA_CDR_CAN_MODE) {ret = -EIO;goto failure_iounmap;}/* Register and setup interrupt handling 注册和设置中断处理*/chip->irq_flags = RTDM_IRQTYPE_SHARED;chip->irq_num = pdev->irq;RTCAN_DBG("%s: base_addr=%p conf_addr=%p irq=%d ocr=%#x cdr=%#x\n",RTCAN_DRV_NAME, board->base_addr, board->conf_addr,chip->irq_num, chip->ocr, chip->cdr);/* Register SJA1000 device    */ret = rtcan_sja1000_register(dev);if (ret) {printk(KERN_ERR "ERROR %d while trying to register SJA1000 device!\n",ret);goto failure_iounmap;}if (channel != CHANNEL_SLAVE)*master_dev = dev;return 0;failure_iounmap:if (channel != CHANNEL_SLAVE || !offset)pci_iounmap(pdev, base_addr);
failure:rtcan_dev_free(dev);return ret;
}

其中定义写函数操作io口:
在rtcan_adv_pci.c中
chip->read_reg = rtcan_adv_pci_read_reg;
chip->write_reg = rtcan_adv_pci_write_reg;
rtcan_adv_pci_write_reg()函数:

static void rtcan_adv_pci_write_reg(struct rtcan_device *dev, int port, u8 data)
{struct rtcan_adv_pci *board = (struct rtcan_adv_pci *)dev->board_priv;iowrite8(data, board->base_addr + port);
}

其中iowrite8()函数进行对io口8位数进行写

参考:
https://gitlab.denx.de/Xenomai/xenomai/-/wikis/Migrating_from_Xenomai_2_To_3
https://www.cnblogs.com/windtail/p/6263235.html
https://www.cnblogs.com/hoys/archive/2011/04/10/2011689.html

Linux +xenomai 下sendto()系统调用过程 --WT相关推荐

  1. 【Linux、进程隐藏】在Linux环境下添加系统调用实现进程隐藏

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 [进程隐藏]在Linux环境下添加系统调用实现进程隐藏 前言 一.环境设置: 二.实现方法步骤: 1.思路图 2.利用strace命令 ...

  2. linux grup进入操作系统,Linux操作系统下GRUB引导过程及原理

    Linux操作系统下GRUB引导过程及原理 作者: 硅谷动力 CNETNews.com.cn 2008-09-09 19:53:52 GRUB是一个多重启动管理器.GRUB是GRand Unified ...

  3. linux系统下io的过程,Linux系统基础知识:IO调度

    Linux系统基础知识:IO调度 IO调度发生在Linux内核的IO调度层.这个层次是针对Linux的整体IO层次体系来说的.从read()或者write()系统调用的角度来说,Linux整体IO体系 ...

  4. linux环境下MongoDB安装过程详解

    Linux下MongoDB安装和配置详解 *注:本文是参考了多篇文章后,并通过实践总结来的,按照步骤来,下载好对应的版本,保准你能安装个心仪的mongodb服务器.... 一.创建MongoDB的安装 ...

  5. solar在linux系统下的部署过程

    需要把solr服务器安装到linux环境: 第一步:安装linux.jdk.tomcat. [root@bogon ~]# ll total 8044 -rw-r--r--. 1 root root ...

  6. linux下的系统调用函数到内核函数的追踪

    Original from: http://blog.chinaunix.net/uid-28458801-id-3468966.html 使用的 glibc : glibc-2.17 使用的 lin ...

  7. Linux系统下SVN服务器的搭建过程详解 UpJ}s7+

    Linux系统下SVN服务器的搭建过程详解 UpJ}s7+   1 环境:  服务器放在redhatAS4.0上,客户端在windows 2000. k_lb"5z   Z]jSq@%1H* ...

  8. 【Linux 内核 内存管理】内存管理架构 ④ ( 内存分配系统调用过程 | 用户层 malloc free | 系统调用层 brk mmap | 内核层 kmalloc | 内存管理流程 )

    文章目录 一.内存分配系统调用过程 ( 用户层 | 系统调用 | 内核层 ) 二.内存管理流程 一.内存分配系统调用过程 ( 用户层 | 系统调用 | 内核层 ) " 堆内存 " ...

  9. 跟踪分析Linux内核5.0系统调用处理过程

    跟踪分析Linux内核5.0系统调用处理过程 学号384 原创作业转载请注明出处+中国科学技术大学孟宁老师的Linux操作系统分析 https://github.com/mengning/linuxk ...

最新文章

  1. Android面试题收集
  2. 常考数据结构与算法:重建二叉树
  3. 浅析人类最贵、最大的机器学习模型GPT-3及背后隐含的商业逻辑
  4. 云栖掠影|回首开源十年,RocketMQ 焕发新生
  5. PAT (Basic Level) Practice (中文)1009 说反话 (20 分)
  6. c语言字符密码验证码,c语言下的学生管理系统(含密码加密和验证码).docx
  7. tomcat启动报错:Bean name 'XXX' is already used in this beans element
  8. AI人才平均月薪3万,最赚钱岗位出炉;上海人才吸引力跌至第四
  9. php实现最后登录时间,php实例之基于Cookie的登录表单和获取最后登录时间
  10. 088、Docker 如何支持多种日志方案 (2019-05-10 周五)
  11. python openstackclient_在Linux系统上安装和配置OpenStack Client(客户端)的方法
  12. 大数据平台及挖掘调研
  13. 安全设备相关知识总结
  14. wxid中文是什么_wxid开头的微信号是什么意思?怎么添加微信好友
  15. linux下c使用lzma_linux lzma软件包下载 ...lzma-sdk.updatestar.com/
  16. Python:输入身份证号,计算出生日期、年龄、性别(源码+效果图)
  17. Java高并发程序设计入门
  18. Xutils中网络请求
  19. javase能做什么项目
  20. 计算机专业实习生一般做什么

热门文章

  1. 怎样用java编程抓取动态生成的网页
  2. MySQL协议解析及C/C++代码实现
  3. 机器学习:无监督异常检测算法
  4. 基于Javamail的邮件收发系统(系统+论文+开题报告+任务书+外文翻译+文献综述+答辩PPT)
  5. FH Admin fhadmin
  6. 洛谷 P3868 [TJOI2009]猜数字【中国剩余定理】
  7. 当团结的程序员队伍中,突然闯进了HTML...
  8. 计算机视觉基础实验——高斯模糊
  9. vue valley_独立农场游戏Stardew Valley今日在iOS上大获成功
  10. txt转换pdf格式转换器的操作步骤