From: yu.dongliang <18588496441@163.com> Date: Thu, 16 Nov 2023 12:19:15 +0000 (+0800) Subject: Linunx API examples X-Git-Url: http://baseworks.info/?a=commitdiff_plain;h=f8e9e8db822512d232a05553e5d0303f241a3b50;p=Linux_API.git Linunx API examples --- f8e9e8db822512d232a05553e5d0303f241a3b50 diff --git a/1.txt b/1.txt new file mode 100644 index 0000000..8ad79bd --- /dev/null +++ b/1.txt @@ -0,0 +1 @@ +1111111 diff --git a/2.txt b/2.txt new file mode 100644 index 0000000..60d4a44 --- /dev/null +++ b/2.txt @@ -0,0 +1 @@ +2222222 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fb5b52d --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +all: + gcc -g -O3 socket_epoll.c -lrt diff --git a/sendmmsg.c b/sendmmsg.c new file mode 100644 index 0000000..c0c9271 --- /dev/null +++ b/sendmmsg.c @@ -0,0 +1,73 @@ + + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + + +int main() +{ + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + printf("socket error, errno: %d\n", errno); + return -1; + } + + struct iovec msgs[3] = { + {"sendmmsg ", strlen("sendmmsg ")}, + {"hello ", strlen("hello ")}, + {"world", strlen("world") + 1}, + }; + + struct sockaddr_in addr0; + addr0.sin_family = AF_INET; + addr0.sin_port = htons(2000); + addr0.sin_addr.s_addr = inet_addr("127.0.0.1"); + + struct sockaddr_in addr1; + addr1.sin_family = AF_INET; + addr1.sin_port = htons(3000); + addr1.sin_addr.s_addr = inet_addr("127.0.0.1"); + + struct mmsghdr h[2]; + h[0].msg_hdr.msg_name = &addr0; + h[0].msg_hdr.msg_namelen = sizeof(addr0); + h[0].msg_hdr.msg_iov = msgs; + h[0].msg_hdr.msg_iovlen = 3; + h[0].msg_hdr.msg_control = NULL; + h[0].msg_hdr.msg_controllen = 0; + h[0].msg_hdr.msg_flags = 0; + h[0].msg_len = 0; + + h[1].msg_hdr.msg_name = &addr1; + h[1].msg_hdr.msg_namelen = sizeof(addr1); + h[1].msg_hdr.msg_iov = msgs; + h[1].msg_hdr.msg_iovlen = 3; + h[1].msg_hdr.msg_control = NULL; + h[1].msg_hdr.msg_controllen = 0; + h[1].msg_hdr.msg_flags = 0; + h[1].msg_len = 0; + + int ret = sendmmsg(fd, h, 2, 0); + if (ret < 0) { + printf("sendmmsg error, errno: %d\n", errno); + return -1; + } + printf(" sendmmsg ret: %d\n", ret); + + printf(" h[0].msg_len: %d\n", h[0].msg_len); + printf(" h[1].msg_len: %d\n", h[1].msg_len); + + return 0; +} + diff --git a/shm.c b/shm.c new file mode 100644 index 0000000..d6d576e --- /dev/null +++ b/shm.c @@ -0,0 +1,178 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* 1, Linux API (syscall), 系统提供给用户的一些编程接口 (函数) + + 2, 硬件 对 软件的 "对接框架", OS. + + OS 1970 C语言之父 unix --> posix --> Linux. + + DOS 1980s --> win 95, win 98 --> windows xp (经典) + + Linux 1991 --> 5.4.x --> ubuntu, suse, arch, deepin 深度, debain, fedora + + "硬件越来越多,CPU 型号 越来越多", OS框架 做为 硬件厂商 跟 用户之间的 "缓冲区". + + 2003年,缓冲区溢出 可以执行各种代码,Linux 2.5 + + 3, 内存管理、进程管理,文件系统、"设备管理"、网络管理! + + 4, 内存管理: brk() 堆内存 --> sbrk() --> malloc() / free(), mmap() 文件映射, + + 5, 进程管理:fork() / sched_yield() / wait() / exit(), + + pthread_create() / pthread_join() / pthread_yield() / pthread_mutex_lock(). + + Linux 内核 在数据结构上不区分 进程和线程,区别在于 内存的共享模式. + + 6, 文件系统,C语言之父的设计模式:"一切都是文件", unix / Linux. + +vfs: virtual file system. 函数指针 ==== C++的虚基类, OOP. C风格. + + 内存文件系统,/proc + 设备文件, /dev + 硬盘文件, / /boot /home + + 总线的驱动, pci / usb + + 网络socket 文件系统 --> 网络子系统 + + open() / close() / read() / write() / fcntl() / mmap() + + 共享内存, socketpair(). + + 文件API. + */ + +int xchg(volatile int* p) +{ + // 理查德 布鲁姆,IA-32,汇编语言程序设计 + int r = 1; + asm volatile("xchg %1, %0" + :"=m"(*p), "=r"(r) + :"1"(r) + :); + return r; +} + +int spin_trylock(volatile int* p) +{ +// Nginx "listen socket 80", 多个进程 监听 同一个socket, + +// 一个用户连上来 多个进程同时 "被唤醒",但只有一个进程能截取到用户的连接, "惊群" + + int n = 0; + while (1 == xchg(p)) { + n++; + if (n > 1000) + return 0; + } + return 1; +} + +void spin_lock(volatile int* p) +{ + while (1 == xchg(p)) + sched_yield(); +} + +void spin_unlock(volatile int* p) +{ + *p = 0; +} + +typedef struct { + volatile int lock; + volatile int count; +} shm_t; + +// 共享内存里的数据,怎么布局 + +int main() +{// shm: shared memory +/* flags: + O_RDWR 可读可写, O_RDONLY 只读, O_WRONLY 只写. + + O_CREAT 如果不存在就创建 + + Linux 进程间通信的机制之一,共享内存, socketpair. + */ + int fd = shm_open("shm_obj", O_RDWR | O_CREAT | O_TRUNC, 0666); + if (fd < 0) { + printf("shm_open error, errno: %d\n", errno); + return -1; + } + + if (ftruncate(fd, sizeof(shm_t)) < 0) { + printf("ftruncate error, errno: %d\n", errno); + return -1; + } + + shm_t* shm = mmap(NULL, sizeof(shm_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (MAP_FAILED == shm) { + printf("mmap error, errno: %d\n", errno); + return -1; + } + + shm->lock = 0; + shm->count = 0; + + int n = 0; +//----------------- +// 多进程 程序,需要在父子进程之间 "共享的数据",全在fork()之前申请 + +// 程序员 对 内核 + 编译器 填坑! + +// 人脑的思维 至少 比电脑高一个维度! + + pid_t cpid = fork(); // 把 父进程 复制 一份 到子进程 + if (-1 == cpid) { + printf("fork error\n"); + return -1; + } + + // 父子 进程的 资源 都是一样的 + + if (0 == cpid) { + while (n < 1000 * 1000) { + spin_lock(&shm->lock); // 自旋锁 + shm->count++; + spin_unlock(&shm->lock); + + n++; + } +// 子进程 + + exit(0); + } else { +// 父进程 + while (n < 1000 * 1000) { + spin_lock(&shm->lock); + shm->count++; + spin_unlock(&shm->lock); + + n++; + } + + int status; + pid_t pid = wait(&status); // 等待子进程退出 + assert(pid == cpid); + + printf("shm->count: %d\n", shm->count); + } + + return 0; +} + diff --git a/socket_epoll.c b/socket_epoll.c new file mode 100644 index 0000000..8e10c00 --- /dev/null +++ b/socket_epoll.c @@ -0,0 +1,376 @@ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +typedef struct client_s client_t; + +#define CLIENT_START 0 +#define CLIENT_CONNECTING 1 +#define CLIENT_CONNECTED 2 +#define CLIENT_HTTP_SENT 3 +#define CLIENT_CLOSE 4 + +struct client_s +{ + int status; + + int epfd; + int fd; + + uint32_t events; + + int (*handler)(client_t* c); + + char* write_buf; + int write_buf_size; + int write_buf_pos; + + int read_bytes; + + time_t time; +}; + +int client_connect(client_t* c) +{ + c->fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (c->fd < 0) { + printf("socket error, errno: %d\n", errno); + return -1; + } + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(80); + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + + int ret = connect(c->fd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)); + if (ret < 0) { + if (EINPROGRESS != errno) { + printf("connect error, errno: %d\n", errno); + return -1; + } + } + + struct epoll_event ev; + ev.events = EPOLLOUT | EPOLLRDHUP | EPOLLET; + ev.data.ptr = c; + + ret = epoll_ctl(c->epfd, EPOLL_CTL_ADD, c->fd, &ev); + if (ret < 0) { + printf("epoll_ctl error, errno: %d\n", errno); + return -1; + } + + printf("c: %p, c->fd: %d, c->status: %d, received: %d\n", c, c->fd, c->status, c->read_bytes); + c->status = CLIENT_CONNECTING; + return 0; +} + +int client_connect_check(client_t* c) +{ + if (!(EPOLLOUT & c->events)) + return 0; + + int err = -1; + int err_len = sizeof(int); + + int ret = getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &err_len); + if (ret < 0) { + printf("getsockopt error, errno: %d\n", errno); + return -1; + } + + if (0 == err) { + printf("connect ok, err: %d, c: %p, c->fd: %d, c->status: %d, received: %d\n", err, c, c->fd, c->status, c->read_bytes); + } else { + printf("err: %d, connect error\n", err); + return -1; + } + +#define BUF_SIZE 1024 + + c->write_buf = malloc(BUF_SIZE); + if (!c->write_buf) { + printf("err: malloc error\n"); + return -1; + } +// printf("c: %p, c->fd: %d, c->status: %d, received: %d\n", c, c->fd, c->status, c->read_bytes); + + struct sockaddr_in addr; + socklen_t addr_len = sizeof(addr); + + ret = getsockname(c->fd, (struct sockaddr*)&addr, &addr_len); + if (ret < 0) { + printf("err: getsockname error, errno: %d\n", errno); + return -1; + } + + ret = snprintf(c->write_buf, BUF_SIZE - 1, "GET / HTTP/1.1\r\n"); + assert(ret > 0); + int len = ret; + + ret = snprintf(c->write_buf + len, BUF_SIZE - 1 - len, "Host: localhost%d\r\n", ntohs(addr.sin_port)); + assert(ret > 0); + len += ret; + + ret = snprintf(c->write_buf + len, BUF_SIZE - 1 - len, "User-Agent: curl/7.66.0\r\n"); + assert(ret > 0); + len += ret; + + ret = snprintf(c->write_buf + len, BUF_SIZE - 1 - len, "Connection: keep-alive\r\n"); + assert(ret > 0); + len += ret; + + ret = snprintf(c->write_buf + len, BUF_SIZE - 1 - len, "Accept: */*\r\n"); + assert(ret > 0); + len += ret; + + ret = snprintf(c->write_buf + len, BUF_SIZE - 1 - len, "\r\n"); + assert(ret > 0); + len += ret; + + c->write_buf[len] = '\0'; +// printf("http: %s\n", c->write_buf); + + c->write_buf_size = len; + c->write_buf_pos = 0; + +// c->read_bytes = 0; + + struct epoll_event ev; + ev.events = EPOLLOUT | EPOLLRDHUP | EPOLLET; + ev.data.ptr = c; + + ret = epoll_ctl(c->epfd, EPOLL_CTL_MOD, c->fd, &ev); + if (ret < 0) { + printf("epoll_ctl error, errno: %d\n", errno); + return -1; + } + + c->status = CLIENT_CONNECTED; + return 0; +} + +int client_http_send(client_t* c) +{ + if (EPOLLOUT & c->events) { + printf("send c: %p, c->fd: %d, c->status: %d, received: %d\n", c, c->fd, c->status, c->read_bytes); + + while (c->write_buf_pos < c->write_buf_size) { + + int ret = send(c->fd, c->write_buf + c->write_buf_pos, c->write_buf_size - c->write_buf_pos, 0); + if (ret < 0) { + if (EAGAIN == errno) { + break; + } else if (EINTR == errno) { + continue; + } else { + printf("send error, errno: %d\n", errno); + return -1; + } + } + assert(ret > 0); + c->write_buf_pos += ret; + } + + if (c->write_buf_pos == c->write_buf_size) { + c->status = CLIENT_HTTP_SENT; + + struct epoll_event ev; + ev.events = EPOLLIN | EPOLLRDHUP | EPOLLET; + ev.data.ptr = c; + + int ret = epoll_ctl(c->epfd, EPOLL_CTL_MOD, c->fd, &ev); + if (ret < 0) { + printf("epoll_ctl error, errno: %d\n", errno); + return -1; + } + } + } + + return 0; +} + +int client_http_recv(client_t* c) +{ + if (EPOLLIN & c->events) { + printf("recv c: %p, c->fd: %d, c->status: %d, received: %d\n", c, c->fd, c->status, c->read_bytes); + + while (1) { + char response[4096]; + int ret = recv(c->fd, response, sizeof(response) - 1, 0); + if (ret < 0) { + if (EAGAIN == errno) { + break; + } else if (EINTR == errno) { + continue; + } else { + printf("recv error, errno: %d\n", errno); + return -1; + } + } else if (0 == ret) { + // printf("ret: %d\n", ret); + break; + } + + assert(ret > 0); + response[ret] = '\0'; + + //printf("ret: %d, response: %s\n", ret, response); + + c->read_bytes += ret; + } + } + return 0; +} + +int client_close(client_t* c) +{ + printf("close c: %p, c->fd: %d, c->status: %d, received: %d\n", c, c->fd, c->status, c->read_bytes); + + int ret = epoll_ctl(c->epfd, EPOLL_CTL_DEL, c->fd, NULL); + if (ret < 0) { + printf("epoll_ctl del error, errno: %d\n", errno); + return -1; + } + + close(c->fd); + c->fd = -1; + + free(c->write_buf); + c->write_buf = NULL; + c->write_buf_pos = 0; + c->write_buf_size = 0; + + c->events = 0; + c->status = CLIENT_START; + return 0; +} + +int client_handler(client_t* c) +{ + time_t t; + time(&t); + + int ret = -1; + + switch (c->status) { + case CLIENT_START: + ret = client_connect(c); + break; + case CLIENT_CONNECTING: + ret = client_connect_check(c); + break; + case CLIENT_CONNECTED: + ret = client_http_send(c); + break; + case CLIENT_HTTP_SENT: + ret = client_http_recv(c); + break; + case CLIENT_CLOSE: + ret = client_close(c); + break; + default: + printf("default error\n"); + break; + }; + + if (-1 == ret) { + c->status = CLIENT_CLOSE; + return -1; + } + + if (t - c->time > 3 && CLIENT_HTTP_SENT == c->status) { + printf("stat c: %p, c->fd: %d, c->status: %d, received: %d\n", c, c->fd, c->status, c->read_bytes); + + c->status = CLIENT_CLOSE; + time(&c->time); + } + + return ret; +} + +int main() +{ + signal(SIGPIPE, SIG_IGN); + + int epfd = epoll_create(1024); + if (epfd < 0) { + printf("epoll_create error, errno: %d\n", errno); + return -1; + } + +#define N_CLIENTS 4 + client_t clients[N_CLIENTS] = {0}; + + int i; + for (i = 0; i < N_CLIENTS; i++) { + + client_t* c = &clients[i]; + + c->epfd = epfd; + c->status = CLIENT_START; + c->fd = -1; + c->events = 0; + c->handler = client_handler; + + c->read_bytes = 0; + + time(&c->time); + } + + while (1) { + struct epoll_event events[N_CLIENTS]; + + int ret = epoll_wait(epfd, events, N_CLIENTS, 500); + if (ret < 0) { + printf("epoll_wait error, errno: %d\n", errno); + return -1; + } + + int i; + for (i = 0; i < ret; i++) { + struct epoll_event* ev = &events[i]; + + client_t* c = ev->data.ptr; + + c->events = ev->events; +// printf("client_handler i: %d, ret: %d, ev: %p, c: %p, c->fd: %d\n", i, ret, ev, c, c->fd); + + int ret2 = client_handler(c); + if (ret2 < 0) { + printf("client_handler error, ret2: %d\n", ret2); + } + } + + for (i = 0; i < N_CLIENTS; i++) { + + client_t* c = &clients[i]; + + c->events = 0; + + ret = client_handler(c); + if (ret < 0) { + printf("client_handler error2, ret: %d\n", ret); + } + } + printf("\n"); + } + + return 0; +} + diff --git a/socketpair.c b/socketpair.c new file mode 100644 index 0000000..3bfd896 --- /dev/null +++ b/socketpair.c @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + + +int main() +{ + int sv[2]; + + int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + if (ret < 0) { + printf("socketpair error, errno: %d\n", errno); + return -1; + } + + pid_t cpid = fork(); + if (-1 == cpid) { + printf("fork error\n"); + return -1; + + } else if (0 == cpid) { + close(sv[0]); + sv[0] = -1; + + char* msg = "hello"; + size_t len = strlen(msg); + + ret = send(sv[1], msg, len + 1, 0); + if (ret < 0) { + printf("send error, errno: %d\n", errno); + return -1; + } + + printf(" send ret: %d\n", ret); + printf(" pid: %d, send: %s\n", getpid(), msg); + } else { + pid_t cpid2 = fork(); + if (-1 == cpid2) { + printf("fork error\n"); + return -1; + + } else if (0 == cpid2) { + close(sv[1]); + sv[1] = -1; + + usleep(10 * 100); + + char buf[1024] = {0}; + + ret = recv(sv[0], buf, sizeof(buf) - 1, 0); + if (ret < 0) { + printf("recv error, errno: %d\n", errno); + return -1; + } + + printf(" recv ret: %d\n", ret); + printf(" pid: %d, received: %s\n", getpid(), buf); + } else { + sleep(2); + } + } + + return 0; +} + diff --git a/socketpair_fd.c b/socketpair_fd.c new file mode 100644 index 0000000..5a0f409 --- /dev/null +++ b/socketpair_fd.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +int main() +{ + int sv[2]; + + int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + if (ret < 0) { + printf("socketpair error, errno: %d\n", errno); + return -1; + } + + pid_t cpid = fork(); + if (-1 == cpid) { + printf("fork error\n"); + return -1; + + } else if (0 == cpid) { + close(sv[0]); + sv[0] = -1; + + int send_fd = open("./1.txt", O_RDONLY); + if (send_fd < 0) { + printf("open send_fd error\n"); + return -1; + } + + int send_fd2 = open("./2.txt", O_RDONLY); + if (send_fd2 < 0) { + printf("open send_fd2 error\n"); + return -1; + } + + struct msghdr h = {0}; + h.msg_name = NULL; + h.msg_namelen = 0; + h.msg_flags = 0; + + struct iovec io = { "f", 1}; + h.msg_iov = &io; + h.msg_iovlen = 1; + + struct cmsghdr* cmsg; + union { + char buf[CMSG_SPACE(sizeof(int))]; + struct cmsghdr align; + } u; + + h.msg_control = u.buf; + h.msg_controllen = sizeof(u.buf); + + cmsg = CMSG_FIRSTHDR(&h); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + *(int*)CMSG_DATA(cmsg) = send_fd2; + + ret = sendmsg(sv[1], &h, 0); + if (ret < 0) { + printf("sendmsg error, errno: %d\n", errno); + return -1; + } + printf(" sendmsg ret: %d, pid: %d, send_fd2: %d\n", ret, getpid(), send_fd2); + + } else { + close(sv[1]); + sv[1] = -1; + + usleep(10 * 1000); + + struct msghdr h = {0}; +#if 1 + h.msg_name = NULL; + h.msg_namelen = 0; + h.msg_flags = 0; + + char c; + struct iovec io = {&c, 1}; + h.msg_iov = &io; + h.msg_iovlen = 1; +#endif +#if 1 + union { + char buf[CMSG_SPACE(sizeof(int))]; + struct cmsghdr align; + } u; + + h.msg_control = u.buf; + h.msg_controllen = sizeof(u.buf); +#endif + ret = recvmsg(sv[0], &h, 0); + if (ret < 0) { + printf("recvmsg error, errno: %d\n", errno); + return -1; + } + printf("recvmsg ret: %d\n", ret); + + struct cmsghdr* cmsg; + int recv_fd = -1; + + for (cmsg = CMSG_FIRSTHDR(&h); cmsg; cmsg = CMSG_NXTHDR(&h, cmsg)) { + + if (cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_RIGHTS) { + + recv_fd = *(int *) CMSG_DATA(cmsg); + + printf(" recvmsg ret: %d, pid: %d, recv_fd: %d\n", ret, getpid(), recv_fd); + + char buf[64] = {0}; + + ret = read(recv_fd, buf, sizeof(buf)); + printf(" read ret: %d, %s, pid: %d, recv_fd: %d\n", ret, buf, getpid(), recv_fd); + } + } + } + + return 0; +} + diff --git a/socketpair_v.c b/socketpair_v.c new file mode 100644 index 0000000..3183655 --- /dev/null +++ b/socketpair_v.c @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +int main() +{ + int sv[2]; + + int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + if (ret < 0) { + printf("socketpair error, errno: %d\n", errno); + return -1; + } + + pid_t cpid = fork(); + if (-1 == cpid) { + printf("fork error\n"); + return -1; + + } else if (0 == cpid) { + close(sv[0]); + sv[0] = -1; + + struct iovec msgs[3] = { + {"writev ", strlen("writev ")}, + {"hello ", strlen("hello ")}, + {"world", strlen("world") + 1}, + }; + + ret = writev(sv[1], msgs, 3); + if (ret < 0) { + printf("writev error, errno: %d\n", errno); + return -1; + } + printf(" writev ret: %d, pid: %d\n", ret, getpid()); + + sleep(1); + + struct msghdr h; + h.msg_name = NULL; + h.msg_namelen = 0; + h.msg_iov = msgs; + h.msg_iovlen = 3; + h.msg_control = NULL; + h.msg_controllen = 0; + h.msg_flags = 0; + + msgs[0].iov_base = "sendmsg "; + msgs[0].iov_len = strlen("sendmsg "); + + ret = sendmsg(sv[1], &h, 0); + if (ret < 0) { + printf("sendmsg error, errno: %d\n", errno); + return -1; + } + printf(" sendmsg ret: %d, pid: %d\n", ret, getpid()); + + } else { + pid_t cpid2 = fork(); + if (-1 == cpid2) { + printf("fork error\n"); + return -1; + + } else if (0 == cpid2) { + close(sv[1]); + sv[1] = -1; + + usleep(10 * 100); + + char buf[1024] = {0}; + + ret = recv(sv[0], buf, sizeof(buf) - 1, 0); + if (ret < 0) { + printf("recv error, errno: %d\n", errno); + return -1; + } + printf(" pid: %d, ret: %d, received: %s\n", getpid(), ret, buf); + + ret = recv(sv[0], buf, sizeof(buf) - 1, 0); + if (ret < 0) { + printf("recv error, errno: %d\n", errno); + return -1; + } + printf(" pid: %d, ret: %d, received: %s\n", getpid(), ret, buf); + + } else { + sleep(2); + } + } + + return 0; +} +