APUE函数笔记十三: 进程间通信

本文深入探讨了多种进程间通信机制,包括管道、命名管道、消息队列、信号量、共享内存等,并通过示例代码展示了如何使用这些机制进行进程间的通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第十五章  进程间通信:

#include <unistd.h>
int pipe(int filedes[2]);
    if success return 0, else error return -1

#include <stdio.h>
FILE * popen(const char * cmdstring, const char * type);
int pclose(FILE * fp);
    if success return exit-status, else error return -1

#include <sys/stat.h>
int mkfifo(const char * pathname, mode_t mode);
    after mkfifo, we can open/read/write/close/unlink it
    if success return 0, else error return -1

#include <sys/ipc.h>
key_t ftok(const char * path, int id);
    if error return (key_t)-1

#include <sys/msg.h>
int msgget(key_t key, int flag);
    if success return msg-id, else error return -1

#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds * buf);
    cmd:
        IPC_STAT, IPC_SET, IPC_RMID
    if success return 0, else error return -1

#include <sys/msg.h>
int msgsnd(int msqid, const void * ptr, size_t nbytes, int flag);
    if success return 0, else error return -1

#include <sys/msg.h>
ssize_t msgrcv(int msqid, void * ptr, size_t nbytes, long type, int flag);
    if success return msg-data-length, else error return -1

#include <sys/sem.h>
int semget(key_t key, int nsems, int flag);
    if success return 0, else error return -1

#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ... /* union semun arg */ );
    cmd:
        IPC_STAT, IPC_SET, IPC_RMID, 
        GETVAL,   SETVAL,  GETPID, 
        GETNCNT,  GETZCNT, GETALL, SETALL
    union semun {
        int               val;   /* for SETVAL */
        struct semid_ds * buf;   /* for IPC_STAT and IPC_SET */
        unsigned short  * array; /* for GETALL and SETALL */
    };

#include <sys/sem.h>
int semop(int semid, struct sembuf semoparray[], size_t nops);
    if success return 0, else error return -1
    struct sembuf {
        unsigned short  sem_num; /* member # in set (0, 1, ..., nsems-1) */
        short           sem_op;  /* operation (negative, 0, or positive) */
        short           sem_flg; /* IPC_NOWAIT, SEM_UNDO */
    };
    if sem_op > 0 free source, else if sem_op < 0 get source

#include <sys/shm.h>
int shmget(key_t key, size_t size, int flag);
    if success return shared-id, else error return -1

#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds * buf);
    cmd:
        IPC_STAT, IPC_SET, IPC_RMID, 
        SHM_LOCK, SHM_UNLOCK /* only for Linux and Solaris */
    if success return 0, else error return -1

#include <sys/shm.h>
void * shmat(int shmid, const void * addr, int flag);
    if error return (void *)-1

#include <sys/shm.h>
int shmdt(void * addr);
    if success return 0, else error return -1

示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define MAXLINE 1024

int 
main(void)
{
    int     n;
    int     int1;
    int     int2;
    char    line[MAXLINE];

    while ((n = read(STDIN_FILENO, line, MAXLINE)) > 0) {
        line[n] = '\0'; /* null terminate */
        if (sscanf(line, "%d%d", &int1, &int2) == 2) {
            sprintf(line, "%d\n", int1 + int2);
            n = strlen(line);
            if (write(STDOUT_FILENO, line, n) != n) {
                printf("write error\n");
                exit(1);
            }
        }
        else {
            if (write(STDOUT_FILENO, "invalid args\n", 13) != 13) {
                printf("write error\n");
                exit(1);
            }
        }
    }
    exit(0);
}

#include <stdio.h>
#include <stdlib.h>

#define MAXLINE 1024

int 
main(void)
{
    int     int1;
    int     int2;
    char    line[MAXLINE];

    if (setvbuf(stdin, NULL, _IOLBF, 0) != 0) {   /* pipe is _IOFBF */
        printf("setvbuf error\n");
        exit(1);
    }
    if (setvbuf(stdout, NULL, _IOLBF, 0) != 0) {  /* pipe is _IOFBF */
        printf("setvbuf error\n");
        exit(1);
    }
    while (fgets(line, MAXLINE, stdin) != NULL) {
        if (sscanf(line, "%d%d", &int1, &int2) == 2) {
            if (printf("%d\n", int1 + int2) == EOF) {
                printf("printf error\n");
                exit(1);
            }
        }
        else {
            if (printf("invalid args\n") == EOF) {
                printf("printf error\n");
                exit(1);
            }
        }
    }
    exit(0);
}

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>

int 
main(void)
{
    int     c;

    while ((c = getchar()) != EOF) {
        if (isupper(c)) {
            c = tolower(c);
        }
        if (putchar(c) == EOF) {
            printf("output error\n");
            exit(1);
        }
        if (c == '\n') {
            fflush(stdout);
        }
    }
    exit(0);
}

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static int pfd1[2];
static int pfd2[2];

void 
TELL_WAIT(void)
{
    if (pipe(pfd1) < 0 || pipe(pfd2) < 0) {
        printf("pipe error\n");
        exit(1);
    }
}

void 
TELL_PARENT(pid_t pid)
{
    if (write(pfd2[1], "c", 1) != 1) {
        printf("write error\n");
        exit(1);
    }
}

void 
WAIT_PARENT(void)
{
    char    c;

    if (read(pfd1[0], &c, 1) != 1) {
        printf("read error\n");
        exit(1);
    }

    if (c != 'p') {
        printf("WAIT_PARENT: incorrent data\n");
        exit(1);
    }
}

void 
TELL_CHILD(pid_t pid)
{
    if (write(pfd1[1], "p", 1) != 1) {
        printf("write error\n");
        exit(1);
    }
}

void 
WAIT_CHILD(void)
{
    char    c;

    if (read(pfd2[0], &c, 1) != 1) {
        printf("read error\n");
        exit(1);
    }

    if (c != 'c') {
        printf("WAIT_CHILD: incorrect data\n");
        exit(1);
    }
}

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#define MAXLINE 1024

int 
main(void)
{
    int     n;
    int     fd[2];
    pid_t   pid;
    char    line[MAXLINE];

    if (pipe(fd) < 0) {
        printf("pipe error\n");
        exit(1);
    }
    if ((pid = fork()) < 0) {
        printf("fork error\n");
        exit(1);
    }
    else if (pid > 0) {
        close(fd[0]);
        write(fd[1], "hello world\n", 12);
    }
    else {
        close(fd[1]);
        n = read(fd[0], line, MAXLINE);
        write(STDOUT_FILENO, line, n);
    }
    exit(0);
}

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define MAXLINE     1024
#define DEF_PAGER   "/bin/more" /* default pager program */

int 
main(int argc, char * argv[])
{
    int       n;
    int       fd[2];
    pid_t     pid;
    char    * pager;
    char    * argv0;
    char      line[MAXLINE];
    FILE    * fp;

    if (argc != 2) {
        printf("usage: %s <pathname>\n", argv[0]);
        exit(1);
    }

    if ((fp = fopen(argv[1], "r")) == NULL) {
        printf("cannot open %s\n", argv[1]);
        exit(1);
    }
    if (pipe(fd) < 0) {
        printf("pipe error\n");
        exit(1);
    }

    if ((pid = fork()) < 0) {
        printf("fork error\n");
        exit(1);
    }
    else if (pid > 0) {
        close(fd[0]); /* close read end */

        /* parent copies argv[1] to pipe */
        while (fgets(line, MAXLINE, fp) != NULL) {
            n = strlen(line);
            if (write(fd[1], line, n) != n) {
                printf("write error\n");
                exit(1);
            }
        }
        if (ferror(fp)) {
            printf("fgets error\n");
            exit(1);
        }

        close(fd[1]); /* close write end of pipe for reader */

        if (waitpid(pid, NULL, 0) < 0) {
            printf("waitpid error\n");
            exit(1);
        }
        exit(0);
    }
    else {
        close(fd[1]); /* close write end */
        if (fd[0] != STDIN_FILENO) {
            if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO) {
                printf("dup2 error to stdin\n");
                exit(1);
            }
            close(fd[0]); /* donnot need this after dup2 */
        }

        /* get arguments for execl() */
        if ((pager = getenv("PAGER")) == NULL) {
            pager = DEF_PAGER;
        }
        if ((argv0 = strrchr(pager, '/')) != NULL) {
            ++argv0; /* step past rightmost slash */
        }
        else {
            argv0 = pager; /* no slash in pager */
        }

        if (execl(pager, argv0, (char *)0) < 0) {
            printf("execl error for %s\n", pager);
        }
    }
    exit(0);
}

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#define MAXLINE 1024
#define PAGER   "${PAGER:-more}" /* environment variable, or default */

int 
main(int argc, char * argv[])
{
    char   line[MAXLINE];
    FILE * fpin;
    FILE * fpout;

    if (argc != 2) {
        printf("usage: %s <pathname>\n", argv[0]);
        exit(1);
    }
    if ((fpin = fopen(argv[1], "r")) == NULL) {
        printf("cannot open %s\n", argv[1]);
        exit(1);
    }

    if ((fpout = popen(PAGER, "w")) == NULL) {
        printf("popen error\n");
        exit(1);
    }

    /* copy argv[1] to pager */
    while (fgets(line, MAXLINE, fpin) != NULL) {
        if (fputs(line, fpout) == EOF) {
            printf("fputs error to pipe\n");
            exit(1);
        }
    }
    if (ferror(fpin)) {
        printf("fgets error\n");
        exit(1);
    }
    if (pclose(fpout) == -1) {
        printf("pclose error\n");
        exit(1);
    }
    exit(0);
}

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

/*
 * pointer to array allocated at run-time
 */
pid_t * childpid = NULL;

/*
 * from our open_max()
 */
int maxfd;

FILE * 
popen(const char * cmdstring, const char * type)
{
    int     i;
    int     pfd[2];
    pid_t   pid;
    FILE  * fp;

    /* only allow "r" or "w" */
    if ((type[0] != 'r' && type[0] != 'w') || type[1] != '\0') {
        errno = EINVAL; /* required by POSIX */
        return(NULL);
    }

    if (childpid == NULL) { /* first time through */
        /* allocate zeroed out array for child pids */
        maxfd = open_max();
        if ((childpid = calloc(maxfd, sizeof(pid_t))) == NULL) {
            return(NULL);
        }
    }

    if (pipe(pfd) < 0) {
        return(NULL); /* error set by pipe() */
    }

    if ((pid = fork()) < 0) {
        return(NULL); /* error set by fork() */
    }
    else if (pid == 0) {
        if (*type == 'r') {
            close(pfd[0]);
            if (pfd[1] != STDOUT_FILENO) {
                dup2(pfd[1], STDOUT_FILENO);
                close(pfd[1]);
            }
        }
        else {
            close(pfd[1]);
            if (pfd[0] != STDIN_FILENO) {
                dup2(pfd[0], STDIN_FILENO);
                close(pfd[0]);
            }
        }

        /* close all descriptors in childpid[] */
        for (i = 0; i < maxfd; ++i) {
            if (childpid[i] > 0) {
                close(i);
            }
        }

        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        _exit(127);
    }

    /* parent continues */
    if (*type == 'r') {
        close(pfd[1]);
        if ((fp = fdopen(pfd[0], type)) == NULL) {
            return(NULL);
        }
    }
    else {
        close(pfd[0]);
        if ((fp = fdopen(pfd[1], type)) == NULL) {
            return(NULL);
        }
    }

    childpid[fileno(fp)] = pid; /* remember child pid for this fd */
    return(fp);
}

int 
pclose(FILE * fp)
{
    int     fd;
    int     stat;
    pid_t   pid;

    if (childpid == NULL) {
        errno = EINVAL;
        return(-1); /* popen() has never been called */
    }

    fd = fileno(fp);
    if ((pid = childpid[fd]) == 0) {
        errno = EINVAL;
        return(-1); /* fp was not opened by popen() */
    }

    childpid[fd] = 0;
    if (fclose(fp) == EOF) {
        return(-1);
    }

    while (waitpid(pid, &stat, 0) < 0) {
        if (errno != EINTR) {
            return(-1); /* error other than EINTR from waitpid() */
        }
    }

    return(stat); /* return child's termination status */
}

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>

#define MAXLINE 1024

int 
main(void)
{
    char    line[MAXLINE];
    FILE  * fpin;

    if ((fpin = popen("./tolower", "r")) == NULL) {
        printf("popen error\n");
        exit(0);
    }
    for (;;) {
        fputs("prompt> ", stdout);
        fflush(stdout);
        if (fgets(line, MAXLINE, fpin) == NULL) { /* read from pipe */
            break;
        }
        if (fputs(line, stdout) == EOF) {
            printf("fputs error to pipe\n");
            exit(1);
        }
    }
    if (pclose(fpin) == -1) {
        printf("pclose error\n");
        exit(1);
    }
    putchar('\n');
    exit(0);
}

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

#define MAXLINE 1024

void 
sig_pipe(int signo)
{
    printf("SIGPIPE caught\n");
    exit(1);
}

int 
main(void)
{
    int     n;
    int     fd1[2];
    int     fd2[2];
    pid_t   pid;
    char    line[MAXLINE];

    if (signal(SIGPIPE, sig_pipe) == SIG_ERR) {
        printf("signal error\n");
        exit(0);
    }

    if (pipe(fd1) < 0 || pipe(fd2) < 0) {
        printf("pipe error\n");
        exit(0);
    }

    if ((pid = fork()) < 0) {
        printf("fork error\n");
        exit(0);
    }
    else if (pid > 0) {
        close(fd1[0]);
        close(fd2[1]);
        while (fgets(line, MAXLINE, stdin) != NULL) {
            n = strlen(line);
            if (write(fd1[1], line, n) != n) {
                printf("write error to pipe\n");
                exit(1);
            }
            if ((n = read(fd2[0], line, MAXLINE)) < 0) {
                printf("read error from pipe\n");
                exit(1);
            }
            else if (n == 0) {
                printf("child closed pipe\n");
                break;
            }
            line[n] = '\0'; /* null terminate */
            if (fputs(line, stdout) == EOF) {
                printf("fputs error\n");
                exit(1);
            }
        }

        if (ferror(stdin)) {
            printf("fgets error on stdin");
            exit(1);
        }
        exit(0);
    }
    else {
        close(fd1[1]);
        close(fd2[0]);
        if (fd1[0] != STDIN_FILENO) {
            if (dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO) {
                printf("dup2 error to stdin\n");
                exit(1);
            }
            close(fd1[0]);
        }
        if (fd2[1] != STDOUT_FILENO) {
            if (dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO) {
                printf("dup2 error to stdin\n");
                exit(1);
            }
            close(fd2[1]);
        }
        if (execl("./add", "add", (char *)0) < 0) {
            printf("execl error\n");
            exit(1);
        }
    }
    exit(0);
}

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>

#include "changefileflag.h"

#define FIFO  "temp.fifo"

int 
main(void)
{
    int     fdread;
    int     fdwrite;

    unlink(FIFO);
    if (mkfifo(FIFO, 0622) < 0) {
        printf("mkfifo error\n");
        exit(1);
    }
    if ((fdread = open(FIFO, O_RDONLY | O_NONBLOCK)) < 0) {
        printf("open error for reading\n");
        exit(1);
    }
    if ((fdwrite = open(FIFO, O_WRONLY)) < 0) {
        printf("open error for writing\n");
        exit(1);
    }
    clr_fl(fdread, O_NONBLOCK);
    exit(0);
}

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

#define MAXLINE 1024

void 
sig_pipe(int signo)
{
    printf("SIGPIPE caught\n");
    exit(1);
}

int 
main(void)
{
    int     n;
    int     fd1[2];
    int     fd2[2];
    pid_t   pid;
    char    line[MAXLINE];
    FILE *  fpin;
    FILE *  fpout;

    if (signal(SIGPIPE, sig_pipe) == SIG_ERR) {
        printf("signal error\n");
        exit(0);
    }

    if (pipe(fd1) < 0 || pipe(fd2) < 0) {
        printf("pipe error\n");
        exit(0);
    }

    if ((pid = fork()) < 0) {
        printf("fork error\n");
        exit(0);
    }
    else if (pid > 0) {
        close(fd1[0]);
        close(fd2[1]);
        if ((fpin = fdopen(fd2[0], "r")) == NULL) {
            printf("fdopen error\n");
            exit(1);
        }
        if ((fpout = fdopen(fd1[1], "w")) == NULL) {
            printf("fdopen error\n");
            exit(1);
        }
        if (setvbuf(fpin, NULL, _IOLBF, 0) < 0) {
            printf("setvbuf error\n");
            exit(1);
        }
        if (setvbuf(fpout, NULL, _IOLBF, 0) < 0) {
            printf("setvbuf error\n");
            exit(1);
        }
        while (fgets(line, MAXLINE, stdin) != NULL) {
            n = strlen(line);
            if (fputs(line, fpout) == EOF) {
                printf("write error to pipe\n");
                exit(1);
            }
            if (fgets(line, MAXLINE, fpin) == NULL) {
                printf("read error from pipe\n");
                exit(1);
            }
            if (n == 0) {
                printf("child closed pipe\n");
                break;
            }
            line[n] = '\0'; /* null terminate */
            if (fputs(line, stdout) == EOF) {
                printf("fputs error\n");
                exit(1);
            }
        }

        if (ferror(stdin)) {
            printf("fgets error on stdin");
            exit(1);
        }
        exit(0);
    }
    else {
        close(fd1[1]);
        close(fd2[0]);
        if (fd1[0] != STDIN_FILENO) {
            if (dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO) {
                printf("dup2 error to stdin\n");
                exit(1);
            }
            close(fd1[0]);
        }
        if (fd2[1] != STDOUT_FILENO) {
            if (dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO) {
                printf("dup2 error to stdin\n");
                exit(1);
            }
            close(fd2[1]);
        }
        if (execl("./add", "add", (char *)0) < 0) {
            printf("execl error\n");
            exit(1);
        }
    }
    exit(0);
}

#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>

#define ARRAY_SIZE   40000
#define MALLOC_SIZE  100000
#define SHM_SIZE     100000
#define SHM_MODE     0600    /* user read/write */

char  array[ARRAY_SIZE];     /* uninitialized data = bss */

int 
main(void)
{
    int     shmid;
    char  * ptr;
    char  * shmptr;

    printf("array[] from %lx to %lx\n", (unsigned long)&array[0], 
           (unsigned long)&array[ARRAY_SIZE]);
    printf("stack around %lx\n", (unsigned long)&shmid);

    if ((ptr = malloc(MALLOC_SIZE)) == NULL) {
        printf("malloc error\n");
        exit(1);
    }
    printf("malloced from %lx to %lx\n", (unsigned long)ptr, 
           (unsigned long)ptr+MALLOC_SIZE);

    if ((shmid = shmget(IPC_PRIVATE, SHM_SIZE, SHM_MODE)) < 0) {
        printf("shmget error\n");
        exit(1);
    }
    if ((shmptr = shmat(shmid, 0, 0)) == (void *)-1) {
        printf("shmat error\n");
        exit(1);
    }
    printf("shared momory attached from %lx to %lx\n", 
           (unsigned long)shmptr, (unsigned long)shmptr+SHM_SIZE);

    if (shmctl(shmid, IPC_RMID, 0) < 0) {
        printf("shmctl error\n");
        exit(1);
    }

    exit(0);
}

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/mman.h>

#include "waitfor.h"

#define NLOOPS  1000
#define SIZE    sizeof(long)  /* size of shared memory area */

int 
updata(long * ptr)
{
    return((*ptr)++);
}

int 
main(void)
{
    int      fd;
    int      i;
    int      counter;
    pid_t    pid;
    void   * area;

    if ((fd = open("/dev/zero", O_RDWR)) < 0) {
        printf("open error\n");
        exit(1);
    }
    if ((area = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, 
                     fd, 0)) == MAP_FAILED) { /* will init area with 0 */
        printf("mmap error\n");
        exit(1);
    }
    close(fd); /* can close /dev/zero now that it is mapped */

    TELL_WAIT();

    if ((pid = fork()) < 0) {
        printf("fork error\n");
        exit(1);
    }
    else if (pid > 0) {
        for (i = 0; i < NLOOPS; i += 2) {
            if ((counter = updata((long *)area)) != i) {
                printf("parent: excepted %d, got %d\n", i, counter);
                exit(1);
            }

            TELL_CHILD(pid);
            WAIT_CHILD();
        }
    }
    else {
        for (i = 1; i < NLOOPS + 1; i += 2) {
            WAIT_PARENT();

            if ((counter = updata((long *)area)) != i) {
                printf("child: excepted %d, got %d\n", i, counter);
                exit(1);
            }

            TELL_PARENT(getppid());
        }
    }

    exit(0);
}

#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>

#include "waitfor.h"

#define NLOOPS    1000
#define SIZE      sizeof(long)  /* size of shared memory area */
#define SHM_MODE  0600

int 
updata(long * ptr)
{
    return((*ptr)++);
}

int 
main(void)
{
    int      i;
    int      shmid;
    int      counter;
    pid_t    pid;
    void   * area;

    if ((shmid = shmget(IPC_PRIVATE, SIZE, SHM_MODE)) < 0) {
        printf("shmget error\n");
        exit(1);
    }
    if ((area = shmat(shmid, 0, 0)) == (void *)-1) {
        printf("shmat error\n");
        exit(1);
    }

    TELL_WAIT();

    if ((pid = fork()) < 0) {
        printf("fork error\n");
        exit(1);
    }
    else if (pid > 0) {
        for (i = 0; i < NLOOPS; i += 2) {
            if ((counter = updata((long *)area)) != i) {
                printf("parent: excepted %d, got %d\n", i, counter);
                exit(1);
            }

            TELL_CHILD(pid);
            WAIT_CHILD();
        }

        if (waitpid(pid, NULL, 0) != pid) {
            printf("waitpid error\n");
            exit(1);
        }

        if (shmctl(shmid, IPC_RMID, 0) < 0) {
            printf("shmctl error\n");
            exit(1);
        }
    }
    else {
        for (i = 1; i < NLOOPS + 1; i += 2) {
            WAIT_PARENT();

            if ((counter = updata((long *)area)) != i) {
                printf("child: excepted %d, got %d\n", i, counter);
                exit(1);
            }

            TELL_PARENT(getppid());
        }
    }

    exit(0);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值